• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *               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  */
105 public 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      */
get()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      */
setEnabled(boolean enabled)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      */
setReporter(Reporter reporter)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      */
getReporter()159     public static Reporter getReporter() {
160         return REPORTER;
161     }
162 
CloseGuard()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      */
open(String closer)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      */
close()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      */
warnIfOpen()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 {
report(String message, Throwable allocationSite)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 {
report(String message, Throwable allocationSite)226         @Override public void report (String message, Throwable allocationSite) {
227             System.logW(message, allocationSite);
228         }
229     }
230 }
231