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.IntraCoreApi 118 public final class CloseGuard { 119 120 /** 121 * True if collection of call-site information (the expensive operation 122 * here) and tracking via a Tracker (see below) are enabled. 123 * Enabled by default so we can diagnose issues early in VM startup. 124 * Note, however, that Android disables this early in its startup, 125 * but enables it with DropBoxing for system apps on debug builds. 126 */ 127 private static volatile boolean stackAndTrackingEnabled = true; 128 129 /** 130 * Hook for customizing how CloseGuard issues are reported. 131 * Bypassed if stackAndTrackingEnabled was false when open was called. 132 */ 133 private static volatile Reporter reporter = new DefaultReporter(); 134 135 /** 136 * Hook for customizing how CloseGuard issues are tracked. 137 */ 138 private static volatile Tracker currentTracker = null; // Disabled by default. 139 140 private static final String MESSAGE = "A resource was acquired at attached stack trace but never released. " + 141 "See java.io.Closeable for information on avoiding resource leaks."; 142 143 /** 144 * Returns a CloseGuard instance. {@code #open(String)} can be used to set 145 * up the instance to warn on failure to close. 146 * 147 * @return {@link CloseGuard} instance. 148 * 149 * @hide 150 */ 151 @UnsupportedAppUsage(trackingBug=111170242) 152 @SystemApi(client = MODULE_LIBRARIES) 153 @libcore.api.IntraCoreApi get()154 public static CloseGuard get() { 155 return new CloseGuard(); 156 } 157 158 /** 159 * Enables/disables stack capture and tracking. A call stack is captured 160 * during open(), and open/close events are reported to the Tracker, only 161 * if enabled is true. If a stack trace was captured, the {@link 162 * #getReporter() reporter} is informed of unclosed resources; otherwise a 163 * one-line warning is logged. 164 * 165 * @param enabled whether stack capture and tracking is enabled. 166 * 167 * @hide 168 */ 169 @UnsupportedAppUsage 170 @SystemApi(client = MODULE_LIBRARIES) setEnabled(boolean enabled)171 public static void setEnabled(boolean enabled) { 172 CloseGuard.stackAndTrackingEnabled = enabled; 173 } 174 175 /** 176 * True if CloseGuard stack capture and tracking are enabled. 177 * 178 * @hide 179 */ isEnabled()180 public static boolean isEnabled() { 181 return stackAndTrackingEnabled; 182 } 183 184 /** 185 * Used to replace default Reporter used to warn of CloseGuard 186 * violations when stack tracking is enabled. Must be non-null. 187 * 188 * @param rep replacement for default Reporter. 189 * 190 * @hide 191 */ 192 @UnsupportedAppUsage 193 @SystemApi(client = MODULE_LIBRARIES) setReporter(Reporter rep)194 public static void setReporter(Reporter rep) { 195 if (rep == null) { 196 throw new NullPointerException("reporter == null"); 197 } 198 CloseGuard.reporter = rep; 199 } 200 201 /** 202 * Returns non-null CloseGuard.Reporter. 203 * 204 * @return CloseGuard's Reporter. 205 * 206 * @hide 207 */ 208 @SystemApi(client = MODULE_LIBRARIES) getReporter()209 public static Reporter getReporter() { 210 return reporter; 211 } 212 213 /** 214 * Sets the {@link Tracker} that is notified when resources are allocated and released. 215 * The Tracker is invoked only if CloseGuard {@link #isEnabled()} held when {@link #open()} 216 * was called. A null argument disables tracking. 217 * 218 * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so 219 * MUST NOT be used for any other purposes. 220 * 221 * @hide 222 */ setTracker(Tracker tracker)223 public static void setTracker(Tracker tracker) { 224 currentTracker = tracker; 225 } 226 227 /** 228 * Returns {@link #setTracker(Tracker) last Tracker that was set}, or null to indicate 229 * there is none. 230 * 231 * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so 232 * MUST NOT be used for any other purposes. 233 * 234 * @hide 235 */ getTracker()236 public static Tracker getTracker() { 237 return currentTracker; 238 } 239 240 @UnsupportedAppUsage CloseGuard()241 private CloseGuard() {} 242 243 /** 244 * {@code open} initializes the instance with a warning that the caller 245 * should have explicitly called the {@code closer} method instead of 246 * relying on finalization. 247 * 248 * @param closer non-null name of explicit termination method. Printed by warnIfOpen. 249 * @throws NullPointerException if closer is null. 250 * 251 * @hide 252 */ 253 @UnsupportedAppUsage(trackingBug=111170242) 254 @SystemApi(client = MODULE_LIBRARIES) 255 @libcore.api.IntraCoreApi open(String closer)256 public void open(String closer) { 257 openWithCallSite(closer, null /* callsite */); 258 } 259 260 /** 261 * Like {@link #open(String)}, but with explicit callsite string being passed in for better 262 * performance. 263 * <p> 264 * This only has better performance than {@link #open(String)} if {@link #isEnabled()} returns {@code true}, which 265 * usually shouldn't happen on release builds. 266 * 267 * @param closer Non-null name of explicit termination method. Printed by warnIfOpen. 268 * @param callsite Non-null string uniquely identifying the callsite. 269 * 270 * @hide 271 */ 272 @SystemApi(client = MODULE_LIBRARIES) openWithCallSite(String closer, String callsite)273 public void openWithCallSite(String closer, String callsite) { 274 // always perform the check for valid API usage... 275 if (closer == null) { 276 throw new NullPointerException("closer == null"); 277 } 278 // ...but avoid allocating an allocation stack if "disabled" 279 if (!stackAndTrackingEnabled) { 280 closerNameOrAllocationInfo = closer; 281 return; 282 } 283 // Always record stack trace when tracker installed, which only happens in tests. Otherwise, skip expensive 284 // stack trace creation when explicit callsite is passed in for better performance. 285 Tracker tracker = currentTracker; 286 if (callsite == null || tracker != null) { 287 String message = "Explicit termination method '" + closer + "' not called"; 288 Throwable stack = new Throwable(message); 289 closerNameOrAllocationInfo = stack; 290 if (tracker != null) { 291 tracker.open(stack); 292 } 293 } else { 294 closerNameOrAllocationInfo = callsite; 295 } 296 } 297 298 // We keep either an allocation stack containing the closer String or, when 299 // in disabled state, just the closer String. 300 // We keep them in a single field only to minimize overhead. 301 private Object /* String or Throwable */ closerNameOrAllocationInfo; 302 303 /** 304 * Marks this CloseGuard instance as closed to avoid warnings on 305 * finalization. 306 * 307 * @hide 308 */ 309 @UnsupportedAppUsage 310 @SystemApi(client = MODULE_LIBRARIES) 311 @libcore.api.IntraCoreApi close()312 public void close() { 313 Tracker tracker = currentTracker; 314 if (tracker != null && closerNameOrAllocationInfo instanceof Throwable) { 315 // Invoke tracker on close only if we invoked it on open. Tracker may have changed. 316 tracker.close((Throwable) closerNameOrAllocationInfo); 317 } 318 closerNameOrAllocationInfo = null; 319 } 320 321 /** 322 * Logs a warning if the caller did not properly cleanup by calling an 323 * explicit close method before finalization. If CloseGuard was enabled 324 * when the CloseGuard was created, passes the stacktrace associated with 325 * the allocation to the current reporter. If it was not enabled, it just 326 * directly logs a brief message. 327 * 328 * @hide 329 */ 330 @UnsupportedAppUsage(trackingBug=111170242) 331 @SystemApi(client = MODULE_LIBRARIES) 332 @libcore.api.IntraCoreApi warnIfOpen()333 public void warnIfOpen() { 334 if (closerNameOrAllocationInfo != null) { 335 if (closerNameOrAllocationInfo instanceof Throwable) { 336 reporter.report(MESSAGE, (Throwable) closerNameOrAllocationInfo); 337 } else if (stackAndTrackingEnabled) { 338 reporter.report(MESSAGE + " Callsite: " + closerNameOrAllocationInfo); 339 } else { 340 System.logW("A resource failed to call " 341 + (String) closerNameOrAllocationInfo + ". "); 342 } 343 } 344 } 345 346 347 /** 348 * Interface to allow customization of tracking behaviour. 349 * 350 * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so 351 * MUST NOT be used for any other purposes. 352 * 353 * @hide 354 */ 355 public interface Tracker { open(Throwable allocationSite)356 void open(Throwable allocationSite); close(Throwable allocationSite)357 void close(Throwable allocationSite); 358 } 359 360 /** 361 * Interface to allow customization of reporting behavior. 362 * @hide 363 */ 364 @SystemApi(client = MODULE_LIBRARIES) 365 public interface Reporter { 366 /** 367 * 368 * @hide 369 */ 370 @UnsupportedAppUsage 371 @SystemApi(client = MODULE_LIBRARIES) report(String message, Throwable allocationSite)372 void report(String message, Throwable allocationSite); 373 374 /** 375 * 376 * @hide 377 */ 378 @SystemApi(client = MODULE_LIBRARIES) report(String message)379 default void report(String message) {} 380 } 381 382 /** 383 * Default Reporter which reports CloseGuard violations to the log. 384 */ 385 private static final class DefaultReporter implements Reporter { 386 @UnsupportedAppUsage DefaultReporter()387 private DefaultReporter() {} 388 report(String message, Throwable allocationSite)389 @Override public void report (String message, Throwable allocationSite) { 390 System.logW(message, allocationSite); 391 } 392 393 @Override report(String message)394 public void report(String message) { 395 System.logW(message); 396 } 397 } 398 } 399