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