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.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
118 @libcore.api.IntraCoreApi
119 public final class CloseGuard {
120 
121     /**
122      * True if collection of call-site information (the expensive operation
123      * here)  and tracking via a Tracker (see below) are enabled.
124      * Enabled by default so we can diagnose issues early in VM startup.
125      * Note, however, that Android disables this early in its startup,
126      * but enables it with DropBoxing for system apps on debug builds.
127      */
128     private static volatile boolean stackAndTrackingEnabled = true;
129 
130     /**
131      * Hook for customizing how CloseGuard issues are reported.
132      * Bypassed if stackAndTrackingEnabled was false when open was called.
133      */
134     private static volatile Reporter reporter = new DefaultReporter();
135 
136     /**
137      * Hook for customizing how CloseGuard issues are tracked.
138      */
139     private static volatile Tracker currentTracker = null; // Disabled by default.
140 
141     private static final String MESSAGE = "A resource was acquired at attached stack trace but never released. " +
142             "See java.io.Closeable for information on avoiding resource leaks.";
143 
144     /**
145      * Returns a CloseGuard instance. {@code #open(String)} can be used to set
146      * up the instance to warn on failure to close.
147      *
148      * @return {@link CloseGuard} instance.
149      *
150      * @hide
151      */
152     @UnsupportedAppUsage(trackingBug=111170242)
153     @SystemApi(client = MODULE_LIBRARIES)
154     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
155     @libcore.api.IntraCoreApi
get()156     public static CloseGuard get() {
157         return new CloseGuard();
158     }
159 
160     /**
161      * Enables/disables stack capture and tracking. A call stack is captured
162      * during open(), and open/close events are reported to the Tracker, only
163      * if enabled is true. If a stack trace was captured, the {@link
164      * #getReporter() reporter} is informed of unclosed resources; otherwise a
165      * one-line warning is logged.
166      *
167      * @param enabled whether stack capture and tracking is enabled.
168      *
169      * @hide
170      */
171     @UnsupportedAppUsage
172     @SystemApi(client = MODULE_LIBRARIES)
173     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
setEnabled(boolean enabled)174     public static void setEnabled(boolean enabled) {
175         CloseGuard.stackAndTrackingEnabled = enabled;
176     }
177 
178     /**
179      * True if CloseGuard stack capture and tracking are enabled.
180      *
181      * @hide
182      */
isEnabled()183     public static boolean isEnabled() {
184         return stackAndTrackingEnabled;
185     }
186 
187     /**
188      * Used to replace default Reporter used to warn of CloseGuard
189      * violations when stack tracking is enabled. Must be non-null.
190      *
191      * @param rep replacement for default Reporter.
192      *
193      * @hide
194      */
195     @UnsupportedAppUsage
196     @SystemApi(client = MODULE_LIBRARIES)
197     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
setReporter(Reporter rep)198     public static void setReporter(Reporter rep) {
199         if (rep == null) {
200             throw new NullPointerException("reporter == null");
201         }
202         CloseGuard.reporter = rep;
203     }
204 
205     /**
206      * Returns non-null CloseGuard.Reporter.
207      *
208      * @return CloseGuard's Reporter.
209      *
210      * @hide
211      */
212     @SystemApi(client = MODULE_LIBRARIES)
213     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
getReporter()214     public static Reporter getReporter() {
215         return reporter;
216     }
217 
218     /**
219      * Sets the {@link Tracker} that is notified when resources are allocated and released.
220      * The Tracker is invoked only if CloseGuard {@link #isEnabled()} held when {@link #open()}
221      * was called. A null argument disables tracking.
222      *
223      * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
224      * MUST NOT be used for any other purposes.
225      *
226      * @hide
227      */
setTracker(Tracker tracker)228     public static void setTracker(Tracker tracker) {
229         currentTracker = tracker;
230     }
231 
232     /**
233      * Returns {@link #setTracker(Tracker) last Tracker that was set}, or null to indicate
234      * there is none.
235      *
236      * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
237      * MUST NOT be used for any other purposes.
238      *
239      * @hide
240      */
getTracker()241     public static Tracker getTracker() {
242         return currentTracker;
243     }
244 
245     @UnsupportedAppUsage
CloseGuard()246     private CloseGuard() {}
247 
248     /**
249      * {@code open} initializes the instance with a warning that the caller
250      * should have explicitly called the {@code closer} method instead of
251      * relying on finalization.
252      *
253      * @param closer non-null name of explicit termination method. Printed by warnIfOpen.
254      * @throws NullPointerException if closer is null.
255      *
256      * @hide
257      */
258     @UnsupportedAppUsage(trackingBug=111170242)
259     @SystemApi(client = MODULE_LIBRARIES)
260     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
261     @libcore.api.IntraCoreApi
open(String closer)262     public void open(String closer) {
263         openWithCallSite(closer, null /* callsite */);
264     }
265 
266     /**
267      * Like {@link #open(String)}, but with explicit callsite string being passed in for better
268      * performance.
269      * <p>
270      * This only has better performance than {@link #open(String)} if {@link #isEnabled()} returns {@code true}, which
271      * usually shouldn't happen on release builds.
272      *
273      * @param closer Non-null name of explicit termination method. Printed by warnIfOpen.
274      * @param callsite Non-null string uniquely identifying the callsite.
275      *
276      * @hide
277      */
278     @SystemApi(client = MODULE_LIBRARIES)
279     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
openWithCallSite(String closer, String callsite)280     public void openWithCallSite(String closer, String callsite) {
281         // always perform the check for valid API usage...
282         if (closer == null) {
283             throw new NullPointerException("closer == null");
284         }
285         // ...but avoid allocating an allocation stack if "disabled"
286         if (!stackAndTrackingEnabled) {
287             closerNameOrAllocationInfo = closer;
288             return;
289         }
290         // Always record stack trace when tracker installed, which only happens in tests. Otherwise, skip expensive
291         // stack trace creation when explicit callsite is passed in for better performance.
292         Tracker tracker = currentTracker;
293         if (callsite == null || tracker != null) {
294             String message = "Explicit termination method '" + closer + "' not called";
295             Throwable stack = new Throwable(message);
296             closerNameOrAllocationInfo = stack;
297             if (tracker != null) {
298                 tracker.open(stack);
299             }
300         } else {
301             closerNameOrAllocationInfo = callsite;
302         }
303     }
304 
305     // We keep either an allocation stack containing the closer String or, when
306     // in disabled state, just the closer String.
307     // We keep them in a single field only to minimize overhead.
308     private Object /* String or Throwable */ closerNameOrAllocationInfo;
309 
310     /**
311      * Marks this CloseGuard instance as closed to avoid warnings on
312      * finalization.
313      *
314      * @hide
315      */
316     @UnsupportedAppUsage
317     @SystemApi(client = MODULE_LIBRARIES)
318     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
319     @libcore.api.IntraCoreApi
close()320     public void close() {
321         Tracker tracker = currentTracker;
322         if (tracker != null && closerNameOrAllocationInfo instanceof Throwable) {
323             // Invoke tracker on close only if we invoked it on open. Tracker may have changed.
324             tracker.close((Throwable) closerNameOrAllocationInfo);
325         }
326         closerNameOrAllocationInfo = null;
327     }
328 
329     /**
330      * Logs a warning if the caller did not properly cleanup by calling an
331      * explicit close method before finalization. If CloseGuard was enabled
332      * when the CloseGuard was created, passes the stacktrace associated with
333      * the allocation to the current reporter. If it was not enabled, it just
334      * directly logs a brief message.
335      *
336      * @hide
337      */
338     @UnsupportedAppUsage(trackingBug=111170242)
339     @SystemApi(client = MODULE_LIBRARIES)
340     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
341     @libcore.api.IntraCoreApi
warnIfOpen()342     public void warnIfOpen() {
343         if (closerNameOrAllocationInfo != null) {
344             if (closerNameOrAllocationInfo instanceof Throwable) {
345                 reporter.report(MESSAGE, (Throwable) closerNameOrAllocationInfo);
346             } else if (stackAndTrackingEnabled) {
347                 reporter.report(MESSAGE + " Callsite: " + closerNameOrAllocationInfo);
348             } else {
349                 System.logW("A resource failed to call "
350                         + (String) closerNameOrAllocationInfo + ". ");
351             }
352         }
353     }
354 
355 
356     /**
357      * Interface to allow customization of tracking behaviour.
358      *
359      * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
360      * MUST NOT be used for any other purposes.
361      *
362      * @hide
363      */
364     public interface Tracker {
open(Throwable allocationSite)365         void open(Throwable allocationSite);
close(Throwable allocationSite)366         void close(Throwable allocationSite);
367     }
368 
369     /**
370      * Interface to allow customization of reporting behavior.
371      * @hide
372      */
373     @SystemApi(client = MODULE_LIBRARIES)
374     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
375     public interface Reporter {
376         /**
377          *
378          * @hide
379          */
380         @UnsupportedAppUsage
381         @SystemApi(client = MODULE_LIBRARIES)
382         @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
report(String message, Throwable allocationSite)383         void report(String message, Throwable allocationSite);
384 
385         /**
386          *
387          * @hide
388          */
389         @SystemApi(client = MODULE_LIBRARIES)
390         @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
report(String message)391         default void report(String message) {}
392     }
393 
394     /**
395      * Default Reporter which reports CloseGuard violations to the log.
396      */
397     private static final class DefaultReporter implements Reporter {
398         @UnsupportedAppUsage
DefaultReporter()399         private DefaultReporter() {}
400 
report(String message, Throwable allocationSite)401         @Override public void report (String message, Throwable allocationSite) {
402             System.logW(message, allocationSite);
403         }
404 
405         @Override
report(String message)406         public void report(String message) {
407             System.logW(message);
408         }
409     }
410 }
411