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

base / apple / scoped_nsautorelease_pool.mm [blame]

// 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