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
17package dalvik.system;
18
19/**
20 * CloseGuard is a mechanism for flagging implicit finalizer cleanup of
21 * resources that should have been cleaned up by explicit close
22 * methods (aka "explicit termination methods" in Effective Java).
23 * <p>
24 * A simple example: <pre>   {@code
25 *   class Foo {
26 *
27 *       private final CloseGuard guard = CloseGuard.get();
28 *
29 *       ...
30 *
31 *       public Foo() {
32 *           ...;
33 *           guard.open("cleanup");
34 *       }
35 *
36 *       public void cleanup() {
37 *          guard.close();
38 *          ...;
39 *       }
40 *
41 *       protected void finalize() throws Throwable {
42 *           try {
43 *               // Note that guard could be null if the constructor threw.
44 *               if (guard != null) {
45 *                   guard.warnIfOpen();
46 *               }
47 *               cleanup();
48 *           } finally {
49 *               super.finalize();
50 *           }
51 *       }
52 *   }
53 * }</pre>
54 *
55 * In usage where the resource to be explicitly cleaned up are
56 * allocated after object construction, CloseGuard protection can
57 * be deferred. For example: <pre>   {@code
58 *   class Bar {
59 *
60 *       private final CloseGuard guard = CloseGuard.get();
61 *
62 *       ...
63 *
64 *       public Bar() {
65 *           ...;
66 *       }
67 *
68 *       public void connect() {
69 *          ...;
70 *          guard.open("cleanup");
71 *       }
72 *
73 *       public void cleanup() {
74 *          guard.close();
75 *          ...;
76 *       }
77 *
78 *       protected void finalize() throws Throwable {
79 *           try {
80 *               // Note that guard could be null if the constructor threw.
81 *               if (guard != null) {
82 *                   guard.warnIfOpen();
83 *               }
84 *               cleanup();
85 *           } finally {
86 *               super.finalize();
87 *           }
88 *       }
89 *   }
90 * }</pre>
91 *
92 * When used in a constructor calls to {@code open} should occur at
93 * the end of the constructor since an exception that would cause
94 * abrupt termination of the constructor will mean that the user will
95 * not have a reference to the object to cleanup explicitly. When used
96 * in a method, the call to {@code open} should occur just after
97 * resource acquisition.
98 *
99 * @hide
100 */
101public final class CloseGuard {
102
103    /**
104     * Instance used when CloseGuard is disabled to avoid allocation.
105     */
106    private static final CloseGuard NOOP = new CloseGuard();
107
108    /**
109     * Enabled by default so we can catch issues early in VM startup.
110     * Note, however, that Android disables this early in its startup,
111     * but enables it with DropBoxing for system apps on debug builds.
112     */
113    private static volatile boolean ENABLED = true;
114
115    /**
116     * Hook for customizing how CloseGuard issues are reported.
117     */
118    private static volatile Reporter REPORTER = new DefaultReporter();
119
120    /**
121     * The default {@link Tracker}.
122     */
123    private static final DefaultTracker DEFAULT_TRACKER = new DefaultTracker();
124
125    /**
126     * Hook for customizing how CloseGuard issues are tracked.
127     */
128    private static volatile Tracker currentTracker = DEFAULT_TRACKER;
129
130    /**
131     * Returns a CloseGuard instance. If CloseGuard is enabled, {@code
132     * #open(String)} can be used to set up the instance to warn on
133     * failure to close. If CloseGuard is disabled, a non-null no-op
134     * instance is returned.
135     */
136    public static CloseGuard get() {
137        if (!ENABLED) {
138            return NOOP;
139        }
140        return new CloseGuard();
141    }
142
143    /**
144     * Used to enable or disable CloseGuard. Note that CloseGuard only
145     * warns if it is enabled for both allocation and finalization.
146     */
147    public static void setEnabled(boolean enabled) {
148        ENABLED = enabled;
149    }
150
151    /**
152     * True if CloseGuard mechanism is enabled.
153     */
154    public static boolean isEnabled() {
155        return ENABLED;
156    }
157
158    /**
159     * Used to replace default Reporter used to warn of CloseGuard
160     * violations. Must be non-null.
161     */
162    public static void setReporter(Reporter reporter) {
163        if (reporter == null) {
164            throw new NullPointerException("reporter == null");
165        }
166        REPORTER = reporter;
167    }
168
169    /**
170     * Returns non-null CloseGuard.Reporter.
171     */
172    public static Reporter getReporter() {
173        return REPORTER;
174    }
175
176    /**
177     * Sets the {@link Tracker} that is notified when resources are allocated and released.
178     *
179     * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
180     * MUST NOT be used for any other purposes.
181     *
182     * @throws NullPointerException if tracker is null
183     */
184    public static void setTracker(Tracker tracker) {
185        if (tracker == null) {
186            throw new NullPointerException("tracker == null");
187        }
188        currentTracker = tracker;
189    }
190
191    /**
192     * Returns {@link #setTracker(Tracker) last Tracker that was set}, or otherwise a default
193     * Tracker that does nothing.
194     *
195     * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
196     * MUST NOT be used for any other purposes.
197     */
198    public static Tracker getTracker() {
199        return currentTracker;
200    }
201
202    private CloseGuard() {}
203
204    /**
205     * If CloseGuard is enabled, {@code open} initializes the instance
206     * with a warning that the caller should have explicitly called the
207     * {@code closer} method instead of relying on finalization.
208     *
209     * @param closer non-null name of explicit termination method
210     * @throws NullPointerException if closer is null, regardless of
211     * whether or not CloseGuard is enabled
212     */
213    public void open(String closer) {
214        // always perform the check for valid API usage...
215        if (closer == null) {
216            throw new NullPointerException("closer == null");
217        }
218        // ...but avoid allocating an allocationSite if disabled
219        if (this == NOOP || !ENABLED) {
220            return;
221        }
222        String message = "Explicit termination method '" + closer + "' not called";
223        allocationSite = new Throwable(message);
224        currentTracker.open(allocationSite);
225    }
226
227    private Throwable allocationSite;
228
229    /**
230     * Marks this CloseGuard instance as closed to avoid warnings on
231     * finalization.
232     */
233    public void close() {
234        currentTracker.close(allocationSite);
235        allocationSite = null;
236    }
237
238    /**
239     * If CloseGuard is enabled, logs a warning if the caller did not
240     * properly cleanup by calling an explicit close method
241     * before finalization. If CloseGuard is disabled, no action is
242     * performed.
243     */
244    public void warnIfOpen() {
245        if (allocationSite == null || !ENABLED) {
246            return;
247        }
248
249        String message =
250                ("A resource was acquired at attached stack trace but never released. "
251                 + "See java.io.Closeable for information on avoiding resource leaks.");
252
253        REPORTER.report(message, allocationSite);
254    }
255
256    /**
257     * Interface to allow customization of tracking behaviour.
258     *
259     * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
260     * MUST NOT be used for any other purposes.
261     */
262    public interface Tracker {
263        void open(Throwable allocationSite);
264        void close(Throwable allocationSite);
265    }
266
267    /**
268     * Default tracker which does nothing special and simply leaves it up to the GC to detect a
269     * leak.
270     */
271    private static final class DefaultTracker implements Tracker {
272        @Override
273        public void open(Throwable allocationSite) {
274        }
275
276        @Override
277        public void close(Throwable allocationSite) {
278        }
279    }
280
281    /**
282     * Interface to allow customization of reporting behavior.
283     */
284    public interface Reporter {
285        void report (String message, Throwable allocationSite);
286    }
287
288    /**
289     * Default Reporter which reports CloseGuard violations to the log.
290     */
291    private static final class DefaultReporter implements Reporter {
292        @Override public void report (String message, Throwable allocationSite) {
293            System.logW(message, allocationSite);
294        }
295    }
296}
297