1// Copyright 2010 The Chromium Authors 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/apple/scoped_nsautorelease_pool.h" 6 7#include "base/dcheck_is_on.h" 8 9#if DCHECK_IS_ON() 10#import <Foundation/Foundation.h> 11 12#include "base/debug/crash_logging.h" 13#include "base/debug/stack_trace.h" 14#include "base/immediate_crash.h" 15#include "base/strings/sys_string_conversions.h" 16#endif 17 18// Note that this uses the direct runtime interface to the autorelease pool. 19// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#runtime-support 20// This is so this can work when compiled for ARC. 21extern "C" { 22void* objc_autoreleasePoolPush(void); 23void objc_autoreleasePoolPop(void* pool); 24} 25 26namespace base::apple { 27 28#if DCHECK_IS_ON() 29namespace { 30 31using BlockReturningStackTrace = debug::StackTrace (^)(); 32 33// Because //base is not allowed to define Objective-C classes, which would be 34// the most reasonable way to wrap a C++ object like base::debug::StackTrace, do 35// it in a much more absurd, yet not completely unreasonable, way. 36// 37// This uses a default argument for the stack trace so that the creation of the 38// stack trace is attributed to the parent function. 39BlockReturningStackTrace MakeBlockReturningStackTrace( 40 debug::StackTrace stack_trace = debug::StackTrace()) { 41 // Return a block that references the stack trace. That will cause a copy of 42 // the stack trace to be made by the block, and because blocks are effectively 43 // Objective-C objects, they can be used in the NSThread thread dictionary. 44 return ^() { 45 return stack_trace; 46 }; 47} 48 49// For each NSThread, maintain an array of stack traces, one for the state of 50// the stack for each invocation of an autorelease pool push. Even though one is 51// allowed to clear out an entire stack of autorelease pools by releasing one 52// near the bottom, because the stack abstraction is mapped to C++ classes, this 53// cannot be allowed. 54NSMutableArray<BlockReturningStackTrace>* GetLevelStackTraces() { 55 NSMutableArray* traces = 56 NSThread.currentThread 57 .threadDictionary[@"CrScopedNSAutoreleasePoolTraces"]; 58 if (traces) { 59 return traces; 60 } 61 62 traces = [NSMutableArray array]; 63 NSThread.currentThread.threadDictionary[@"CrScopedNSAutoreleasePoolTraces"] = 64 traces; 65 return traces; 66} 67 68} // namespace 69#endif 70 71ScopedNSAutoreleasePool::ScopedNSAutoreleasePool() { 72 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 73 PushImpl(); 74} 75 76ScopedNSAutoreleasePool::~ScopedNSAutoreleasePool() { 77 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 78 PopImpl(); 79} 80 81void ScopedNSAutoreleasePool::Recycle() { 82 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); 83 // Cycle the internal pool, allowing everything there to get cleaned up and 84 // start anew. 85 PopImpl(); 86 PushImpl(); 87} 88 89void ScopedNSAutoreleasePool::PushImpl() { 90#if DCHECK_IS_ON() 91 [GetLevelStackTraces() addObject:MakeBlockReturningStackTrace()]; 92 level_ = GetLevelStackTraces().count; 93#endif 94 autorelease_pool_ = objc_autoreleasePoolPush(); 95} 96 97void ScopedNSAutoreleasePool::PopImpl() { 98#if DCHECK_IS_ON() 99 auto level_count = GetLevelStackTraces().count; 100 if (level_ != level_count) { 101 NSLog(@"Popping autorelease pool at level %lu while pools exist through " 102 @"level %lu", 103 level_, level_count); 104 if (level_ < level_count) { 105 NSLog(@"WARNING: This abandons ScopedNSAutoreleasePool objects which now " 106 @"have no corresponding implementation."); 107 } else { 108 NSLog(@"ERROR: This is an abandoned ScopedNSAutoreleasePool that cannot " 109 @"release; expect the autorelease machinery to crash."); 110 } 111 NSLog(@"===================="); 112 NSString* current_stack = SysUTF8ToNSString(debug::StackTrace().ToString()); 113 NSLog(@"Pop:\n%@", current_stack); 114 [GetLevelStackTraces() 115 enumerateObjectsWithOptions:NSEnumerationReverse 116 usingBlock:^(BlockReturningStackTrace obj, 117 NSUInteger idx, BOOL* stop) { 118 NSLog(@"===================="); 119 NSLog(@"Autorelease pool level %lu was pushed:\n%@", 120 idx + 1, SysUTF8ToNSString(obj().ToString())); 121 }]; 122 // Assume an interactive use of Chromium where crashing immediately is 123 // desirable, and die. When investigating a failing automated test that dies 124 // here, remove these crash keys and call to ImmediateCrash() to reveal 125 // where the abandoned ScopedNSAutoreleasePool was expected to be released. 126 SCOPED_CRASH_KEY_NUMBER("ScopedNSAutoreleasePool", "currentlevel", level_); 127 SCOPED_CRASH_KEY_NUMBER("ScopedNSAutoreleasePool", "levelcount", 128 level_count); 129 SCOPED_CRASH_KEY_STRING1024("ScopedNSAutoreleasePool", "currentstack", 130 SysNSStringToUTF8(current_stack)); 131 SCOPED_CRASH_KEY_STRING1024("ScopedNSAutoreleasePool", "recentstack", 132 GetLevelStackTraces().lastObject().ToString()); 133 ImmediateCrash(); 134 } 135 [GetLevelStackTraces() removeLastObject]; 136#endif 137 objc_autoreleasePoolPop(autorelease_pool_); 138} 139 140} // namespace base::apple 141