1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dalvik.system;
18 
19 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
20 
21 import android.annotation.SystemApi;
22 import android.compat.annotation.UnsupportedAppUsage;
23 
24 /**
25  * CloseGuard is a mechanism for flagging implicit finalizer cleanup of
26  * resources that should have been cleaned up by explicit close
27  * methods (aka "explicit termination methods" in Effective Java).
28  * <p>
29  * A simple example: <pre>   {@code
30  *   class Foo {
31  *
32  *       {@literal @}ReachabilitySensitive
33  *       private final CloseGuard guard = CloseGuard.get();
34  *
35  *       ...
36  *
37  *       public Foo() {
38  *           ...;
39  *           guard.open("cleanup");
40  *       }
41  *
42  *       public void cleanup() {
43  *          guard.close();
44  *          ...;
45  *       }
46  *
47  *       protected void finalize() throws Throwable {
48  *           try {
49  *               // Note that guard could be null if the constructor threw.
50  *               if (guard != null) {
51  *                   guard.warnIfOpen();
52  *               }
53  *               cleanup();
54  *           } finally {
55  *               super.finalize();
56  *           }
57  *       }
58  *   }
59  * }</pre>
60  *
61  * In usage where the resource to be explicitly cleaned up is
62  * allocated after object construction, CloseGuard protection can
63  * be deferred. For example: <pre>   {@code
64  *   class Bar {
65  *
66  *       {@literal @}ReachabilitySensitive
67  *       private final CloseGuard guard = CloseGuard.get();
68  *
69  *       ...
70  *
71  *       public Bar() {
72  *           ...;
73  *       }
74  *
75  *       public void connect() {
76  *          ...;
77  *          guard.open("cleanup");
78  *       }
79  *
80  *       public void cleanup() {
81  *          guard.close();
82  *          ...;
83  *       }
84  *
85  *       protected void finalize() throws Throwable {
86  *           try {
87  *               // Note that guard could be null if the constructor threw.
88  *               if (guard != null) {
89  *                   guard.warnIfOpen();
90  *               }
91  *               cleanup();
92  *           } finally {
93  *               super.finalize();
94  *           }
95  *       }
96  *   }
97  * }</pre>
98  *
99  * When used in a constructor, calls to {@code open} should occur at
100  * the end of the constructor since an exception that would cause
101  * abrupt termination of the constructor will mean that the user will
102  * not have a reference to the object to cleanup explicitly. When used
103  * in a method, the call to {@code open} should occur just after
104  * resource acquisition.
105  *
106  * The @ReachabilitySensitive annotation ensures that finalize() cannot be
107  * called during the explicit call to cleanup(), prior to the guard.close call.
108  * There is an extremely small chance that, for code that neglects to call
109  * cleanup(), finalize() and thus cleanup() will be called while a method on
110  * the object is still active, but the "this" reference is no longer required.
111  * If missing cleanup() calls are expected, additional @ReachabilitySensitive
112  * annotations or reachabilityFence() calls may be required.
113  *
114  * @hide
115  */
116 @SystemApi(client = MODULE_LIBRARIES)
117 @libcore.api.IntraCoreApi
118 public final class CloseGuard {
119 
120     /**
121      * True if collection of call-site information (the expensive operation
122      * here)  and tracking via a Tracker (see below) are enabled.
123      * Enabled by default so we can diagnose issues early in VM startup.
124      * Note, however, that Android disables this early in its startup,
125      * but enables it with DropBoxing for system apps on debug builds.
126      */
127     private static volatile boolean stackAndTrackingEnabled = true;
128 
129     /**
130      * Hook for customizing how CloseGuard issues are reported.
131      * Bypassed if stackAndTrackingEnabled was false when open was called.
132      */
133     private static volatile Reporter reporter = new DefaultReporter();
134 
135     /**
136      * Hook for customizing how CloseGuard issues are tracked.
137      */
138     private static volatile Tracker currentTracker = null; // Disabled by default.
139 
140     private static final String MESSAGE = "A resource was acquired at attached stack trace but never released. " +
141             "See java.io.Closeable for information on avoiding resource leaks.";
142 
143     /**
144      * Returns a CloseGuard instance. {@code #open(String)} can be used to set
145      * up the instance to warn on failure to close.
146      *
147      * @return {@link CloseGuard} instance.
148      *
149      * @hide
150      */
151     @UnsupportedAppUsage(trackingBug=111170242)
152     @SystemApi(client = MODULE_LIBRARIES)
153     @libcore.api.IntraCoreApi
get()154     public static CloseGuard get() {
155         return new CloseGuard();
156     }
157 
158     /**
159      * Enables/disables stack capture and tracking. A call stack is captured
160      * during open(), and open/close events are reported to the Tracker, only
161      * if enabled is true. If a stack trace was captured, the {@link
162      * #getReporter() reporter} is informed of unclosed resources; otherwise a
163      * one-line warning is logged.
164      *
165      * @param enabled whether stack capture and tracking is enabled.
166      *
167      * @hide
168      */
169     @UnsupportedAppUsage
170     @SystemApi(client = MODULE_LIBRARIES)
setEnabled(boolean enabled)171     public static void setEnabled(boolean enabled) {
172         CloseGuard.stackAndTrackingEnabled = enabled;
173     }
174 
175     /**
176      * True if CloseGuard stack capture and tracking are enabled.
177      *
178      * @hide
179      */
isEnabled()180     public static boolean isEnabled() {
181         return stackAndTrackingEnabled;
182     }
183 
184     /**
185      * Used to replace default Reporter used to warn of CloseGuard
186      * violations when stack tracking is enabled. Must be non-null.
187      *
188      * @param rep replacement for default Reporter.
189      *
190      * @hide
191      */
192     @UnsupportedAppUsage
193     @SystemApi(client = MODULE_LIBRARIES)
setReporter(Reporter rep)194     public static void setReporter(Reporter rep) {
195         if (rep == null) {
196             throw new NullPointerException("reporter == null");
197         }
198         CloseGuard.reporter = rep;
199     }
200 
201     /**
202      * Returns non-null CloseGuard.Reporter.
203      *
204      * @return CloseGuard's Reporter.
205      *
206      * @hide
207      */
208     @SystemApi(client = MODULE_LIBRARIES)
getReporter()209     public static Reporter getReporter() {
210         return reporter;
211     }
212 
213     /**
214      * Sets the {@link Tracker} that is notified when resources are allocated and released.
215      * The Tracker is invoked only if CloseGuard {@link #isEnabled()} held when {@link #open()}
216      * was called. A null argument disables tracking.
217      *
218      * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
219      * MUST NOT be used for any other purposes.
220      *
221      * @hide
222      */
setTracker(Tracker tracker)223     public static void setTracker(Tracker tracker) {
224         currentTracker = tracker;
225     }
226 
227     /**
228      * Returns {@link #setTracker(Tracker) last Tracker that was set}, or null to indicate
229      * there is none.
230      *
231      * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
232      * MUST NOT be used for any other purposes.
233      *
234      * @hide
235      */
getTracker()236     public static Tracker getTracker() {
237         return currentTracker;
238     }
239 
240     @UnsupportedAppUsage
CloseGuard()241     private CloseGuard() {}
242 
243     /**
244      * {@code open} initializes the instance with a warning that the caller
245      * should have explicitly called the {@code closer} method instead of
246      * relying on finalization.
247      *
248      * @param closer non-null name of explicit termination method. Printed by warnIfOpen.
249      * @throws NullPointerException if closer is null.
250      *
251      * @hide
252      */
253     @UnsupportedAppUsage(trackingBug=111170242)
254     @SystemApi(client = MODULE_LIBRARIES)
255     @libcore.api.IntraCoreApi
open(String closer)256     public void open(String closer) {
257         openWithCallSite(closer, null /* callsite */);
258     }
259 
260     /**
261      * Like {@link #open(String)}, but with explicit callsite string being passed in for better
262      * performance.
263      * <p>
264      * This only has better performance than {@link #open(String)} if {@link #isEnabled()} returns {@code true}, which
265      * usually shouldn't happen on release builds.
266      *
267      * @param closer Non-null name of explicit termination method. Printed by warnIfOpen.
268      * @param callsite Non-null string uniquely identifying the callsite.
269      *
270      * @hide
271      */
272     @SystemApi(client = MODULE_LIBRARIES)
openWithCallSite(String closer, String callsite)273     public void openWithCallSite(String closer, String callsite) {
274         // always perform the check for valid API usage...
275         if (closer == null) {
276             throw new NullPointerException("closer == null");
277         }
278         // ...but avoid allocating an allocation stack if "disabled"
279         if (!stackAndTrackingEnabled) {
280             closerNameOrAllocationInfo = closer;
281             return;
282         }
283         // Always record stack trace when tracker installed, which only happens in tests. Otherwise, skip expensive
284         // stack trace creation when explicit callsite is passed in for better performance.
285         Tracker tracker = currentTracker;
286         if (callsite == null || tracker != null) {
287             String message = "Explicit termination method '" + closer + "' not called";
288             Throwable stack = new Throwable(message);
289             closerNameOrAllocationInfo = stack;
290             if (tracker != null) {
291                 tracker.open(stack);
292             }
293         } else {
294             closerNameOrAllocationInfo = callsite;
295         }
296     }
297 
298     // We keep either an allocation stack containing the closer String or, when
299     // in disabled state, just the closer String.
300     // We keep them in a single field only to minimize overhead.
301     private Object /* String or Throwable */ closerNameOrAllocationInfo;
302 
303     /**
304      * Marks this CloseGuard instance as closed to avoid warnings on
305      * finalization.
306      *
307      * @hide
308      */
309     @UnsupportedAppUsage
310     @SystemApi(client = MODULE_LIBRARIES)
311     @libcore.api.IntraCoreApi
close()312     public void close() {
313         Tracker tracker = currentTracker;
314         if (tracker != null && closerNameOrAllocationInfo instanceof Throwable) {
315             // Invoke tracker on close only if we invoked it on open. Tracker may have changed.
316             tracker.close((Throwable) closerNameOrAllocationInfo);
317         }
318         closerNameOrAllocationInfo = null;
319     }
320 
321     /**
322      * Logs a warning if the caller did not properly cleanup by calling an
323      * explicit close method before finalization. If CloseGuard was enabled
324      * when the CloseGuard was created, passes the stacktrace associated with
325      * the allocation to the current reporter. If it was not enabled, it just
326      * directly logs a brief message.
327      *
328      * @hide
329      */
330     @UnsupportedAppUsage(trackingBug=111170242)
331     @SystemApi(client = MODULE_LIBRARIES)
332     @libcore.api.IntraCoreApi
warnIfOpen()333     public void warnIfOpen() {
334         if (closerNameOrAllocationInfo != null) {
335             if (closerNameOrAllocationInfo instanceof Throwable) {
336                 reporter.report(MESSAGE, (Throwable) closerNameOrAllocationInfo);
337             } else if (stackAndTrackingEnabled) {
338                 reporter.report(MESSAGE + " Callsite: " + closerNameOrAllocationInfo);
339             } else {
340                 System.logW("A resource failed to call "
341                         + (String) closerNameOrAllocationInfo + ". ");
342             }
343         }
344     }
345 
346 
347     /**
348      * Interface to allow customization of tracking behaviour.
349      *
350      * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
351      * MUST NOT be used for any other purposes.
352      *
353      * @hide
354      */
355     public interface Tracker {
open(Throwable allocationSite)356         void open(Throwable allocationSite);
close(Throwable allocationSite)357         void close(Throwable allocationSite);
358     }
359 
360     /**
361      * Interface to allow customization of reporting behavior.
362      * @hide
363      */
364     @SystemApi(client = MODULE_LIBRARIES)
365     public interface Reporter {
366         /**
367          *
368          * @hide
369          */
370         @UnsupportedAppUsage
371         @SystemApi(client = MODULE_LIBRARIES)
report(String message, Throwable allocationSite)372         void report(String message, Throwable allocationSite);
373 
374         /**
375          *
376          * @hide
377          */
378         @SystemApi(client = MODULE_LIBRARIES)
report(String message)379         default void report(String message) {}
380     }
381 
382     /**
383      * Default Reporter which reports CloseGuard violations to the log.
384      */
385     private static final class DefaultReporter implements Reporter {
386         @UnsupportedAppUsage
DefaultReporter()387         private DefaultReporter() {}
388 
report(String message, Throwable allocationSite)389         @Override public void report (String message, Throwable allocationSite) {
390             System.logW(message, allocationSite);
391         }
392 
393         @Override
report(String message)394         public void report(String message) {
395             System.logW(message);
396         }
397     }
398 }
399