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 /**
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  */
101 public 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      * Returns a CloseGuard instance. If CloseGuard is enabled, {@code
122      * #open(String)} can be used to set up the instance to warn on
123      * failure to close. If CloseGuard is disabled, a non-null no-op
124      * instance is returned.
125      */
get()126     public static CloseGuard get() {
127         if (!ENABLED) {
128             return NOOP;
129         }
130         return new CloseGuard();
131     }
132 
133     /**
134      * Used to enable or disable CloseGuard. Note that CloseGuard only
135      * warns if it is enabled for both allocation and finalization.
136      */
setEnabled(boolean enabled)137     public static void setEnabled(boolean enabled) {
138         ENABLED = enabled;
139     }
140 
141     /**
142      * Used to replace default Reporter used to warn of CloseGuard
143      * violations. Must be non-null.
144      */
setReporter(Reporter reporter)145     public static void setReporter(Reporter reporter) {
146         if (reporter == null) {
147             throw new NullPointerException("reporter == null");
148         }
149         REPORTER = reporter;
150     }
151 
152     /**
153      * Returns non-null CloseGuard.Reporter.
154      */
getReporter()155     public static Reporter getReporter() {
156         return REPORTER;
157     }
158 
CloseGuard()159     private CloseGuard() {}
160 
161     /**
162      * If CloseGuard is enabled, {@code open} initializes the instance
163      * with a warning that the caller should have explicitly called the
164      * {@code closer} method instead of relying on finalization.
165      *
166      * @param closer non-null name of explicit termination method
167      * @throws NullPointerException if closer is null, regardless of
168      * whether or not CloseGuard is enabled
169      */
open(String closer)170     public void open(String closer) {
171         // always perform the check for valid API usage...
172         if (closer == null) {
173             throw new NullPointerException("closer == null");
174         }
175         // ...but avoid allocating an allocationSite if disabled
176         if (this == NOOP || !ENABLED) {
177             return;
178         }
179         String message = "Explicit termination method '" + closer + "' not called";
180         allocationSite = new Throwable(message);
181     }
182 
183     private Throwable allocationSite;
184 
185     /**
186      * Marks this CloseGuard instance as closed to avoid warnings on
187      * finalization.
188      */
close()189     public void close() {
190         allocationSite = null;
191     }
192 
193     /**
194      * If CloseGuard is enabled, logs a warning if the caller did not
195      * properly cleanup by calling an explicit close method
196      * before finalization. If CloseGuard is disabled, no action is
197      * performed.
198      */
warnIfOpen()199     public void warnIfOpen() {
200         if (allocationSite == null || !ENABLED) {
201             return;
202         }
203 
204         String message =
205                 ("A resource was acquired at attached stack trace but never released. "
206                  + "See java.io.Closeable for information on avoiding resource leaks.");
207 
208         REPORTER.report(message, allocationSite);
209     }
210 
211     /**
212      * Interface to allow customization of reporting behavior.
213      */
214     public static interface Reporter {
report(String message, Throwable allocationSite)215         public void report (String message, Throwable allocationSite);
216     }
217 
218     /**
219      * Default Reporter which reports CloseGuard violations to the log.
220      */
221     private static final class DefaultReporter implements Reporter {
report(String message, Throwable allocationSite)222         @Override public void report (String message, Throwable allocationSite) {
223             System.logW(message, allocationSite);
224         }
225     }
226 }
227