xref: /aosp_15_r20/external/cronet/base/apple/scoped_nsautorelease_pool.mm (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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