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