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 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; 20 21 import android.annotation.SystemApi; 22 import android.compat.annotation.UnsupportedAppUsage; 23 24 /** 25 * CloseGuard is a mechanism for flagging implicit finalizer cleanup of 26 * resources that should have been cleaned up by explicit close 27 * methods (aka "explicit termination methods" in Effective Java). 28 * <p> 29 * A simple example: <pre> {@code 30 * class Foo { 31 * 32 * {@literal @}ReachabilitySensitive 33 * private final CloseGuard guard = CloseGuard.get(); 34 * 35 * ... 36 * 37 * public Foo() { 38 * ...; 39 * guard.open("cleanup"); 40 * } 41 * 42 * public void cleanup() { 43 * guard.close(); 44 * ...; 45 * } 46 * 47 * protected void finalize() throws Throwable { 48 * try { 49 * // Note that guard could be null if the constructor threw. 50 * if (guard != null) { 51 * guard.warnIfOpen(); 52 * } 53 * cleanup(); 54 * } finally { 55 * super.finalize(); 56 * } 57 * } 58 * } 59 * }</pre> 60 * 61 * In usage where the resource to be explicitly cleaned up is 62 * allocated after object construction, CloseGuard protection can 63 * be deferred. For example: <pre> {@code 64 * class Bar { 65 * 66 * {@literal @}ReachabilitySensitive 67 * private final CloseGuard guard = CloseGuard.get(); 68 * 69 * ... 70 * 71 * public Bar() { 72 * ...; 73 * } 74 * 75 * public void connect() { 76 * ...; 77 * guard.open("cleanup"); 78 * } 79 * 80 * public void cleanup() { 81 * guard.close(); 82 * ...; 83 * } 84 * 85 * protected void finalize() throws Throwable { 86 * try { 87 * // Note that guard could be null if the constructor threw. 88 * if (guard != null) { 89 * guard.warnIfOpen(); 90 * } 91 * cleanup(); 92 * } finally { 93 * super.finalize(); 94 * } 95 * } 96 * } 97 * }</pre> 98 * 99 * When used in a constructor, calls to {@code open} should occur at 100 * the end of the constructor since an exception that would cause 101 * abrupt termination of the constructor will mean that the user will 102 * not have a reference to the object to cleanup explicitly. When used 103 * in a method, the call to {@code open} should occur just after 104 * resource acquisition. 105 * 106 * The @ReachabilitySensitive annotation ensures that finalize() cannot be 107 * called during the explicit call to cleanup(), prior to the guard.close call. 108 * There is an extremely small chance that, for code that neglects to call 109 * cleanup(), finalize() and thus cleanup() will be called while a method on 110 * the object is still active, but the "this" reference is no longer required. 111 * If missing cleanup() calls are expected, additional @ReachabilitySensitive 112 * annotations or reachabilityFence() calls may be required. 113 * 114 * @hide 115 */ 116 @SystemApi(client = MODULE_LIBRARIES) 117 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) 118 @libcore.api.IntraCoreApi 119 public final class CloseGuard { 120 121 /** 122 * True if collection of call-site information (the expensive operation 123 * here) and tracking via a Tracker (see below) are enabled. 124 * Enabled by default so we can diagnose issues early in VM startup. 125 * Note, however, that Android disables this early in its startup, 126 * but enables it with DropBoxing for system apps on debug builds. 127 */ 128 private static volatile boolean stackAndTrackingEnabled = true; 129 130 /** 131 * Hook for customizing how CloseGuard issues are reported. 132 * Bypassed if stackAndTrackingEnabled was false when open was called. 133 */ 134 private static volatile Reporter reporter = new DefaultReporter(); 135 136 /** 137 * Hook for customizing how CloseGuard issues are tracked. 138 */ 139 private static volatile Tracker currentTracker = null; // Disabled by default. 140 141 private static final String MESSAGE = "A resource was acquired at attached stack trace but never released. " + 142 "See java.io.Closeable for information on avoiding resource leaks."; 143 144 /** 145 * Returns a CloseGuard instance. {@code #open(String)} can be used to set 146 * up the instance to warn on failure to close. 147 * 148 * @return {@link CloseGuard} instance. 149 * 150 * @hide 151 */ 152 @UnsupportedAppUsage(trackingBug=111170242) 153 @SystemApi(client = MODULE_LIBRARIES) 154 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) 155 @libcore.api.IntraCoreApi get()156 public static CloseGuard get() { 157 return new CloseGuard(); 158 } 159 160 /** 161 * Enables/disables stack capture and tracking. A call stack is captured 162 * during open(), and open/close events are reported to the Tracker, only 163 * if enabled is true. If a stack trace was captured, the {@link 164 * #getReporter() reporter} is informed of unclosed resources; otherwise a 165 * one-line warning is logged. 166 * 167 * @param enabled whether stack capture and tracking is enabled. 168 * 169 * @hide 170 */ 171 @UnsupportedAppUsage 172 @SystemApi(client = MODULE_LIBRARIES) 173 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) setEnabled(boolean enabled)174 public static void setEnabled(boolean enabled) { 175 CloseGuard.stackAndTrackingEnabled = enabled; 176 } 177 178 /** 179 * True if CloseGuard stack capture and tracking are enabled. 180 * 181 * @hide 182 */ isEnabled()183 public static boolean isEnabled() { 184 return stackAndTrackingEnabled; 185 } 186 187 /** 188 * Used to replace default Reporter used to warn of CloseGuard 189 * violations when stack tracking is enabled. Must be non-null. 190 * 191 * @param rep replacement for default Reporter. 192 * 193 * @hide 194 */ 195 @UnsupportedAppUsage 196 @SystemApi(client = MODULE_LIBRARIES) 197 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) setReporter(Reporter rep)198 public static void setReporter(Reporter rep) { 199 if (rep == null) { 200 throw new NullPointerException("reporter == null"); 201 } 202 CloseGuard.reporter = rep; 203 } 204 205 /** 206 * Returns non-null CloseGuard.Reporter. 207 * 208 * @return CloseGuard's Reporter. 209 * 210 * @hide 211 */ 212 @SystemApi(client = MODULE_LIBRARIES) 213 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) getReporter()214 public static Reporter getReporter() { 215 return reporter; 216 } 217 218 /** 219 * Sets the {@link Tracker} that is notified when resources are allocated and released. 220 * The Tracker is invoked only if CloseGuard {@link #isEnabled()} held when {@link #open()} 221 * was called. A null argument disables tracking. 222 * 223 * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so 224 * MUST NOT be used for any other purposes. 225 * 226 * @hide 227 */ setTracker(Tracker tracker)228 public static void setTracker(Tracker tracker) { 229 currentTracker = tracker; 230 } 231 232 /** 233 * Returns {@link #setTracker(Tracker) last Tracker that was set}, or null to indicate 234 * there is none. 235 * 236 * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so 237 * MUST NOT be used for any other purposes. 238 * 239 * @hide 240 */ getTracker()241 public static Tracker getTracker() { 242 return currentTracker; 243 } 244 245 @UnsupportedAppUsage CloseGuard()246 private CloseGuard() {} 247 248 /** 249 * {@code open} initializes the instance with a warning that the caller 250 * should have explicitly called the {@code closer} method instead of 251 * relying on finalization. 252 * 253 * @param closer non-null name of explicit termination method. Printed by warnIfOpen. 254 * @throws NullPointerException if closer is null. 255 * 256 * @hide 257 */ 258 @UnsupportedAppUsage(trackingBug=111170242) 259 @SystemApi(client = MODULE_LIBRARIES) 260 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) 261 @libcore.api.IntraCoreApi open(String closer)262 public void open(String closer) { 263 openWithCallSite(closer, null /* callsite */); 264 } 265 266 /** 267 * Like {@link #open(String)}, but with explicit callsite string being passed in for better 268 * performance. 269 * <p> 270 * This only has better performance than {@link #open(String)} if {@link #isEnabled()} returns {@code true}, which 271 * usually shouldn't happen on release builds. 272 * 273 * @param closer Non-null name of explicit termination method. Printed by warnIfOpen. 274 * @param callsite Non-null string uniquely identifying the callsite. 275 * 276 * @hide 277 */ 278 @SystemApi(client = MODULE_LIBRARIES) 279 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) openWithCallSite(String closer, String callsite)280 public void openWithCallSite(String closer, String callsite) { 281 // always perform the check for valid API usage... 282 if (closer == null) { 283 throw new NullPointerException("closer == null"); 284 } 285 // ...but avoid allocating an allocation stack if "disabled" 286 if (!stackAndTrackingEnabled) { 287 closerNameOrAllocationInfo = closer; 288 return; 289 } 290 // Always record stack trace when tracker installed, which only happens in tests. Otherwise, skip expensive 291 // stack trace creation when explicit callsite is passed in for better performance. 292 Tracker tracker = currentTracker; 293 if (callsite == null || tracker != null) { 294 String message = "Explicit termination method '" + closer + "' not called"; 295 Throwable stack = new Throwable(message); 296 closerNameOrAllocationInfo = stack; 297 if (tracker != null) { 298 tracker.open(stack); 299 } 300 } else { 301 closerNameOrAllocationInfo = callsite; 302 } 303 } 304 305 // We keep either an allocation stack containing the closer String or, when 306 // in disabled state, just the closer String. 307 // We keep them in a single field only to minimize overhead. 308 private Object /* String or Throwable */ closerNameOrAllocationInfo; 309 310 /** 311 * Marks this CloseGuard instance as closed to avoid warnings on 312 * finalization. 313 * 314 * @hide 315 */ 316 @UnsupportedAppUsage 317 @SystemApi(client = MODULE_LIBRARIES) 318 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) 319 @libcore.api.IntraCoreApi close()320 public void close() { 321 Tracker tracker = currentTracker; 322 if (tracker != null && closerNameOrAllocationInfo instanceof Throwable) { 323 // Invoke tracker on close only if we invoked it on open. Tracker may have changed. 324 tracker.close((Throwable) closerNameOrAllocationInfo); 325 } 326 closerNameOrAllocationInfo = null; 327 } 328 329 /** 330 * Logs a warning if the caller did not properly cleanup by calling an 331 * explicit close method before finalization. If CloseGuard was enabled 332 * when the CloseGuard was created, passes the stacktrace associated with 333 * the allocation to the current reporter. If it was not enabled, it just 334 * directly logs a brief message. 335 * 336 * @hide 337 */ 338 @UnsupportedAppUsage(trackingBug=111170242) 339 @SystemApi(client = MODULE_LIBRARIES) 340 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) 341 @libcore.api.IntraCoreApi warnIfOpen()342 public void warnIfOpen() { 343 if (closerNameOrAllocationInfo != null) { 344 if (closerNameOrAllocationInfo instanceof Throwable) { 345 reporter.report(MESSAGE, (Throwable) closerNameOrAllocationInfo); 346 } else if (stackAndTrackingEnabled) { 347 reporter.report(MESSAGE + " Callsite: " + closerNameOrAllocationInfo); 348 } else { 349 System.logW("A resource failed to call " 350 + (String) closerNameOrAllocationInfo + ". "); 351 } 352 } 353 } 354 355 356 /** 357 * Interface to allow customization of tracking behaviour. 358 * 359 * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so 360 * MUST NOT be used for any other purposes. 361 * 362 * @hide 363 */ 364 public interface Tracker { open(Throwable allocationSite)365 void open(Throwable allocationSite); close(Throwable allocationSite)366 void close(Throwable allocationSite); 367 } 368 369 /** 370 * Interface to allow customization of reporting behavior. 371 * @hide 372 */ 373 @SystemApi(client = MODULE_LIBRARIES) 374 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) 375 public interface Reporter { 376 /** 377 * 378 * @hide 379 */ 380 @UnsupportedAppUsage 381 @SystemApi(client = MODULE_LIBRARIES) 382 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) report(String message, Throwable allocationSite)383 void report(String message, Throwable allocationSite); 384 385 /** 386 * 387 * @hide 388 */ 389 @SystemApi(client = MODULE_LIBRARIES) 390 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) report(String message)391 default void report(String message) {} 392 } 393 394 /** 395 * Default Reporter which reports CloseGuard violations to the log. 396 */ 397 private static final class DefaultReporter implements Reporter { 398 @UnsupportedAppUsage DefaultReporter()399 private DefaultReporter() {} 400 report(String message, Throwable allocationSite)401 @Override public void report (String message, Throwable allocationSite) { 402 System.logW(message, allocationSite); 403 } 404 405 @Override report(String message)406 public void report(String message) { 407 System.logW(message); 408 } 409 } 410 } 411