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 *               if (guard != null) {
44 *                   guard.warnIfOpen();
45 *               }
46 *               cleanup();
47 *           } finally {
48 *               super.finalize();
49 *           }
50 *       }
51 *   }
52 * }</pre>
53 *
54 * In usage where the resource to be explicitly cleaned up are
55 * allocated after object construction, CloseGuard protection can
56 * be deferred. For example: <pre>   {@code
57 *   class Bar {
58 *
59 *       private final CloseGuard guard = CloseGuard.get();
60 *
61 *       ...
62 *
63 *       public Bar() {
64 *           ...;
65 *       }
66 *
67 *       public void connect() {
68 *          ...;
69 *          guard.open("cleanup");
70 *       }
71 *
72 *       public void cleanup() {
73 *          guard.close();
74 *          ...;
75 *       }
76 *
77 *       protected void finalize() throws Throwable {
78 *           try {
79 *               if (guard != null) {
80 *                   guard.warnIfOpen();
81 *               }
82 *               cleanup();
83 *           } finally {
84 *               super.finalize();
85 *           }
86 *       }
87 *   }
88 * }</pre>
89 *
90 * When used in a constructor calls to {@code open} should occur at
91 * the end of the constructor since an exception that would cause
92 * abrupt termination of the constructor will mean that the user will
93 * not have a reference to the object to cleanup explicitly. When used
94 * in a method, the call to {@code open} should occur just after
95 * resource acquisition.
96 *
97 * <p>
98 *
99 * Note that the null check on {@code guard} in the finalizer is to
100 * cover cases where a constructor throws an exception causing the
101 * {@code guard} to be uninitialized.
102 *
103 * @hide
104 */
105public final class CloseGuard {
106
107    /**
108     * Instance used when CloseGuard is disabled to avoid allocation.
109     */
110    private static final CloseGuard NOOP = new CloseGuard();
111
112    /**
113     * Enabled by default so we can catch issues early in VM startup.
114     * Note, however, that Android disables this early in its startup,
115     * but enables it with DropBoxing for system apps on debug builds.
116     */
117    private static volatile boolean ENABLED = true;
118
119    /**
120     * Hook for customizing how CloseGuard issues are reported.
121     */
122    private static volatile Reporter REPORTER = new DefaultReporter();
123
124    /**
125     * Returns a CloseGuard instance. If CloseGuard is enabled, {@code
126     * #open(String)} can be used to set up the instance to warn on
127     * failure to close. If CloseGuard is disabled, a non-null no-op
128     * instance is returned.
129     */
130    public static CloseGuard get() {
131        if (!ENABLED) {
132            return NOOP;
133        }
134        return new CloseGuard();
135    }
136
137    /**
138     * Used to enable or disable CloseGuard. Note that CloseGuard only
139     * warns if it is enabled for both allocation and finalization.
140     */
141    public static void setEnabled(boolean enabled) {
142        ENABLED = enabled;
143    }
144
145    /**
146     * Used to replace default Reporter used to warn of CloseGuard
147     * violations. Must be non-null.
148     */
149    public static void setReporter(Reporter reporter) {
150        if (reporter == null) {
151            throw new NullPointerException("reporter == null");
152        }
153        REPORTER = reporter;
154    }
155
156    /**
157     * Returns non-null CloseGuard.Reporter.
158     */
159    public static Reporter getReporter() {
160        return REPORTER;
161    }
162
163    private CloseGuard() {}
164
165    /**
166     * If CloseGuard is enabled, {@code open} initializes the instance
167     * with a warning that the caller should have explicitly called the
168     * {@code closer} method instead of relying on finalization.
169     *
170     * @param closer non-null name of explicit termination method
171     * @throws NullPointerException if closer is null, regardless of
172     * whether or not CloseGuard is enabled
173     */
174    public void open(String closer) {
175        // always perform the check for valid API usage...
176        if (closer == null) {
177            throw new NullPointerException("closer == null");
178        }
179        // ...but avoid allocating an allocationSite if disabled
180        if (this == NOOP || !ENABLED) {
181            return;
182        }
183        String message = "Explicit termination method '" + closer + "' not called";
184        allocationSite = new Throwable(message);
185    }
186
187    private Throwable allocationSite;
188
189    /**
190     * Marks this CloseGuard instance as closed to avoid warnings on
191     * finalization.
192     */
193    public void close() {
194        allocationSite = null;
195    }
196
197    /**
198     * If CloseGuard is enabled, logs a warning if the caller did not
199     * properly cleanup by calling an explicit close method
200     * before finalization. If CloseGuard is disabled, no action is
201     * performed.
202     */
203    public void warnIfOpen() {
204        if (allocationSite == null || !ENABLED) {
205            return;
206        }
207
208        String message =
209                ("A resource was acquired at attached stack trace but never released. "
210                 + "See java.io.Closeable for information on avoiding resource leaks.");
211
212        REPORTER.report(message, allocationSite);
213    }
214
215    /**
216     * Interface to allow customization of reporting behavior.
217     */
218    public static interface Reporter {
219        public void report (String message, Throwable allocationSite);
220    }
221
222    /**
223     * Default Reporter which reports CloseGuard violations to the log.
224     */
225    private static final class DefaultReporter implements Reporter {
226        @Override public void report (String message, Throwable allocationSite) {
227            System.logW(message, allocationSite);
228        }
229    }
230}
231