xpcom/base/nsObjCExceptions.h
author Simon Sapin <simon.sapin@exyr.org>
Mon, 31 Dec 2018 01:01:49 +0100
changeset 452667 91e7ea77c846ae753fb3a28ab81598d2dc7ac865
parent 448947 6f3709b3878117466168c40affa7bca0b60cf75b
child 454354 5f4630838d46dd81dadb13220a4af0da9e23a619
permissions -rw-r--r--
Bug 1518045 - Publish selectors 0.21.0 on crates.io. r=emilio This cherry-picks https://github.com/servo/servo/pull/22577, with the relevant lockfile update.

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Undo the damage that exception_defines.h does.
#undef try
#undef catch

#ifndef nsObjCExceptions_h_
#define nsObjCExceptions_h_

#import <Foundation/Foundation.h>

#ifdef DEBUG
#import <ExceptionHandling/NSExceptionHandler.h>
#endif

#if defined(__cplusplus)
#include "nsICrashReporter.h"
#include "nsCOMPtr.h"
#include "nsServiceManagerUtils.h"
#endif

#include <unistd.h>
#include <signal.h>
#include "nsError.h"

// Undo the damage that exception_defines.h does.
#undef try
#undef catch

/* NOTE: Macros that claim to abort no longer abort, see bug 486574.
 * If you actually want to log and abort, call "nsObjCExceptionLogAbort"
 * from an exception handler. At some point we will fix this by replacing
 * all macros in the tree with appropriately-named macros.
 */

// See Mozilla bug 163260.
// This file can only be included in an Objective-C context.

__attribute__((unused)) static void nsObjCExceptionLog(NSException* aException) {
  NSLog(@"Mozilla has caught an Obj-C exception [%@: %@]", [aException name], [aException reason]);

#if defined(__cplusplus)
  // Attach exception info to the crash report.
  nsCOMPtr<nsICrashReporter> crashReporter = do_GetService("@mozilla.org/toolkit/crash-reporter;1");
  if (crashReporter) {
    crashReporter->AppendObjCExceptionInfoToAppNotes(static_cast<void*>(aException));
  }
#endif

#ifdef DEBUG
  @try {
    // Try to get stack information out of the exception. 10.5 returns the stack
    // info with the callStackReturnAddresses selector.
    NSArray* stackTrace = nil;
    if ([aException respondsToSelector:@selector(callStackReturnAddresses)]) {
      NSArray* addresses =
          (NSArray*)[aException performSelector:@selector(callStackReturnAddresses)];
      if ([addresses count]) {
        stackTrace = addresses;
      }
    }

    // 10.4 doesn't respond to callStackReturnAddresses so we'll try to pull the
    // stack info out of the userInfo. It might not be there, sadly :(
    if (!stackTrace) {
      stackTrace = [[aException userInfo] objectForKey:NSStackTraceKey];
    }

    if (stackTrace) {
      // The command line should look like this:
      //   /usr/bin/atos -p <pid> -printHeader <stack frame addresses>
      NSMutableArray* args = [NSMutableArray arrayWithCapacity:[stackTrace count] + 3];

      [args addObject:@"-p"];
      int pid = [[NSProcessInfo processInfo] processIdentifier];
      [args addObject:[NSString stringWithFormat:@"%d", pid]];

      [args addObject:@"-printHeader"];

      unsigned int stackCount = [stackTrace count];
      unsigned int stackIndex = 0;
      for (; stackIndex < stackCount; stackIndex++) {
        unsigned long address = [[stackTrace objectAtIndex:stackIndex] unsignedLongValue];
        [args addObject:[NSString stringWithFormat:@"0x%lx", address]];
      }

      NSPipe* outPipe = [NSPipe pipe];

      NSTask* task = [[NSTask alloc] init];
      [task setLaunchPath:@"/usr/bin/atos"];
      [task setArguments:args];
      [task setStandardOutput:outPipe];
      [task setStandardError:outPipe];

      NSLog(@"Generating stack trace for Obj-C exception...");

      // This will throw an exception if the atos tool cannot be found, and in
      // that case we'll just hit our @catch block below.
      [task launch];

      [task waitUntilExit];
      [task release];

      NSData* outData = [[outPipe fileHandleForReading] readDataToEndOfFile];
      NSString* outString = [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];

      NSLog(@"Stack trace:\n%@", outString);

      [outString release];
    } else {
      NSLog(@"<No stack information available for Obj-C exception>");
    }
  } @catch (NSException* exn) {
    NSLog(@"Failed to generate stack trace for Obj-C exception [%@: %@]", [exn name], [exn reason]);
  }
#endif
}

__attribute__((unused)) static void nsObjCExceptionAbort() {
  // We need to raise a mach-o signal here, the Mozilla crash reporter on
  // Mac OS X does not respond to POSIX signals. Raising mach-o signals directly
  // is tricky so we do it by just derefing a null pointer.
  int* foo = nullptr;
  *foo = 1;
}

__attribute__((unused)) static void nsObjCExceptionLogAbort(NSException* aException) {
  nsObjCExceptionLog(aException);
  nsObjCExceptionAbort();
}

#define NS_OBJC_TRY(_e, _fail)    \
  @try {                          \
    _e;                           \
  } @catch (NSException * _exn) { \
    nsObjCExceptionLog(_exn);     \
    _fail;                        \
  }

#define NS_OBJC_TRY_EXPR(_e, _fail) \
  ({                                \
    typeof(_e) _tmp;                \
    @try {                          \
      _tmp = (_e);                  \
    } @catch (NSException * _exn) { \
      nsObjCExceptionLog(_exn);     \
      _fail;                        \
    }                               \
    _tmp;                           \
  })

#define NS_OBJC_TRY_EXPR_NULL(_e) NS_OBJC_TRY_EXPR(_e, 0)

#define NS_OBJC_TRY_IGNORE(_e) NS_OBJC_TRY(_e, )

// To reduce code size the abort versions do not reuse above macros. This allows
// catch blocks to only contain one call.

#define NS_OBJC_TRY_ABORT(_e)     \
  @try {                          \
    _e;                           \
  } @catch (NSException * _exn) { \
    nsObjCExceptionLog(_exn);     \
  }

#define NS_OBJC_TRY_EXPR_ABORT(_e)  \
  ({                                \
    typeof(_e) _tmp;                \
    @try {                          \
      _tmp = (_e);                  \
    } @catch (NSException * _exn) { \
      nsObjCExceptionLog(_exn);     \
    }                               \
    _tmp;                           \
  })

// For wrapping blocks of Obj-C calls. Does not actually terminate.
#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK @try {
#define NS_OBJC_END_TRY_ABORT_BLOCK \
  }                                 \
  @catch (NSException * _exn) {     \
    nsObjCExceptionLog(_exn);       \
  }

// Same as above ABORT_BLOCK but returns a value after the try/catch block to
// suppress compiler warnings. This allows us to avoid having to refactor code
// to get scoping right when wrapping an entire method.

#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL @try {
#define NS_OBJC_END_TRY_ABORT_BLOCK_NIL \
  }                                     \
  @catch (NSException * _exn) {         \
    nsObjCExceptionLog(_exn);           \
  }                                     \
  return nil;

#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL @try {
#define NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL \
  }                                        \
  @catch (NSException * _exn) {            \
    nsObjCExceptionLog(_exn);              \
  }                                        \
  return nullptr;

#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT @try {
#define NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT \
  }                                          \
  @catch (NSException * _exn) {              \
    nsObjCExceptionLog(_exn);                \
  }                                          \
  return NS_ERROR_FAILURE;

#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN @try {
#define NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(_rv) \
  }                                             \
  @catch (NSException * _exn) {                 \
    nsObjCExceptionLog(_exn);                   \
  }                                             \
  return _rv;

#endif  // nsObjCExceptions_h_