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