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