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