| Index: chrome/browser/chrome_browser_application_mac.mm
|
| diff --git a/chrome/browser/chrome_browser_application_mac.mm b/chrome/browser/chrome_browser_application_mac.mm
|
| index 4f5dc4b41f7b190b61215c7319b2ff0292123d82..02479c015e5dfdb370534c34c5613b2dd5c99325 100644
|
| --- a/chrome/browser/chrome_browser_application_mac.mm
|
| +++ b/chrome/browser/chrome_browser_application_mac.mm
|
| @@ -4,11 +4,14 @@
|
|
|
| #import "chrome/browser/chrome_browser_application_mac.h"
|
|
|
| +#include <objc/objc-exception.h>
|
| +
|
| #import "base/auto_reset.h"
|
| #include "base/command_line.h"
|
| #include "base/debug/crash_logging.h"
|
| #include "base/debug/stack_trace.h"
|
| #import "base/logging.h"
|
| +#include "base/mac/call_with_eh_frame.h"
|
| #import "base/mac/scoped_nsexception_enabler.h"
|
| #import "base/mac/scoped_nsobject.h"
|
| #import "base/mac/scoped_objc_class_swizzler.h"
|
| @@ -25,154 +28,6 @@
|
| #include "content/public/browser/render_view_host.h"
|
| #include "content/public/browser/web_contents.h"
|
|
|
| -namespace {
|
| -
|
| -// Tracking for cases being hit by -crInitWithName:reason:userInfo:.
|
| -enum ExceptionEventType {
|
| - EXCEPTION_ACCESSIBILITY = 0,
|
| - EXCEPTION_MENU_ITEM_BOUNDS_CHECK,
|
| - EXCEPTION_VIEW_NOT_IN_WINDOW,
|
| - EXCEPTION_NSURL_INIT_NIL,
|
| - EXCEPTION_NSDATADETECTOR_NIL_STRING,
|
| - EXCEPTION_NSREGULAREXPRESSION_NIL_STRING,
|
| -
|
| - // Always keep this at the end.
|
| - EXCEPTION_MAX,
|
| -};
|
| -
|
| -void RecordExceptionEvent(ExceptionEventType event_type) {
|
| - UMA_HISTOGRAM_ENUMERATION("OSX.ExceptionHandlerEvents",
|
| - event_type, EXCEPTION_MAX);
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -// The implementation of NSExceptions break various assumptions in the
|
| -// Chrome code. This category defines a replacement for
|
| -// -initWithName:reason:userInfo: for purposes of forcing a break in
|
| -// the debugger when an exception is raised. -raise sounds more
|
| -// obvious to intercept, but it doesn't catch the original throw
|
| -// because the objc runtime doesn't use it.
|
| -@interface NSException (CrNSExceptionSwizzle)
|
| -- (id)crInitWithName:(NSString*)aName
|
| - reason:(NSString*)aReason
|
| - userInfo:(NSDictionary*)someUserInfo;
|
| -@end
|
| -
|
| -static IMP gOriginalInitIMP = NULL;
|
| -
|
| -@implementation NSException (CrNSExceptionSwizzle)
|
| -- (id)crInitWithName:(NSString*)aName
|
| - reason:(NSString*)aReason
|
| - userInfo:(NSDictionary*)someUserInfo {
|
| - // Method only called when swizzled.
|
| - DCHECK(_cmd == @selector(initWithName:reason:userInfo:));
|
| - DCHECK(gOriginalInitIMP);
|
| -
|
| - // Parts of Cocoa rely on creating and throwing exceptions. These are not
|
| - // worth bugging-out over. It is very important that there be zero chance that
|
| - // any Chromium code is on the stack; these must be created by Apple code and
|
| - // then immediately consumed by Apple code.
|
| - static NSString* const kAcceptableNSExceptionNames[] = {
|
| - // If an object does not support an accessibility attribute, this will
|
| - // get thrown.
|
| - NSAccessibilityException,
|
| - };
|
| -
|
| - BOOL found = NO;
|
| - for (size_t i = 0; i < arraysize(kAcceptableNSExceptionNames); ++i) {
|
| - if (aName == kAcceptableNSExceptionNames[i]) {
|
| - found = YES;
|
| - RecordExceptionEvent(EXCEPTION_ACCESSIBILITY);
|
| - break;
|
| - }
|
| - }
|
| -
|
| - if (!found) {
|
| - // Update breakpad with the exception info.
|
| - std::string value = base::StringPrintf("%s reason %s",
|
| - [aName UTF8String], [aReason UTF8String]);
|
| - base::debug::SetCrashKeyValue(crash_keys::mac::kNSException, value);
|
| - base::debug::SetCrashKeyToStackTrace(crash_keys::mac::kNSExceptionTrace,
|
| - base::debug::StackTrace());
|
| -
|
| - // Force crash for selected exceptions to generate crash dumps.
|
| - BOOL fatal = NO;
|
| - if (aName == NSInternalInconsistencyException) {
|
| - NSString* const kNSMenuItemArrayBoundsCheck =
|
| - @"Invalid parameter not satisfying: (index >= 0) && "
|
| - @"(index < [_itemArray count])";
|
| - if ([aReason isEqualToString:kNSMenuItemArrayBoundsCheck]) {
|
| - RecordExceptionEvent(EXCEPTION_MENU_ITEM_BOUNDS_CHECK);
|
| - fatal = YES;
|
| - }
|
| -
|
| - NSString* const kNoWindowCheck = @"View is not in any window";
|
| - if ([aReason isEqualToString:kNoWindowCheck]) {
|
| - RecordExceptionEvent(EXCEPTION_VIEW_NOT_IN_WINDOW);
|
| - fatal = YES;
|
| - }
|
| - }
|
| -
|
| - // Mostly "unrecognized selector sent to (instance|class)". A
|
| - // very small number of things like inappropriate nil being passed.
|
| - if (aName == NSInvalidArgumentException) {
|
| - fatal = YES;
|
| -
|
| - // TODO(shess): http://crbug.com/85463 throws this exception
|
| - // from ImageKit. Our code is not on the stack, so it needs to
|
| - // be whitelisted for now.
|
| - NSString* const kNSURLInitNilCheck =
|
| - @"*** -[NSURL initFileURLWithPath:isDirectory:]: "
|
| - @"nil string parameter";
|
| - if ([aReason isEqualToString:kNSURLInitNilCheck]) {
|
| - RecordExceptionEvent(EXCEPTION_NSURL_INIT_NIL);
|
| - fatal = NO;
|
| - }
|
| -
|
| - // <http://crbug.com/316759> OSX 10.9 fails trying to extract
|
| - // structure from a string.
|
| - NSString* const kNSDataDetectorNilCheck =
|
| - @"*** -[NSDataDetector enumerateMatchesInString:"
|
| - @"options:range:usingBlock:]: nil argument";
|
| - if ([aReason isEqualToString:kNSDataDetectorNilCheck]) {
|
| - RecordExceptionEvent(EXCEPTION_NSDATADETECTOR_NIL_STRING);
|
| - fatal = NO;
|
| - }
|
| -
|
| - // <http://crbug.com/466076> OSX 10.10 moved the method.
|
| - NSString* const kNSRegularExpressionNilCheck =
|
| - @"*** -[NSRegularExpression enumerateMatchesInString:"
|
| - @"options:range:usingBlock:]: nil argument";
|
| - if ([aReason isEqualToString:kNSRegularExpressionNilCheck]) {
|
| - RecordExceptionEvent(EXCEPTION_NSREGULAREXPRESSION_NIL_STRING);
|
| - fatal = NO;
|
| - }
|
| - }
|
| -
|
| - // Dear reader: Something you just did provoked an NSException.
|
| - // NSException is implemented in terms of setjmp()/longjmp(),
|
| - // which does poor things when combined with C++ scoping
|
| - // (destructors are skipped). Chrome should be NSException-free,
|
| - // please check your backtrace and see if you can't file a bug
|
| - // with a repro case.
|
| - const bool allow = base::mac::GetNSExceptionsAllowed();
|
| - if (fatal && !allow) {
|
| - LOG(FATAL) << "Someone is trying to raise an exception! "
|
| - << value;
|
| - } else {
|
| - // Make sure that developers see when their code throws
|
| - // exceptions.
|
| - DCHECK(allow) << "Someone is trying to raise an exception! "
|
| - << value;
|
| - }
|
| - }
|
| -
|
| - // Forward to the original version.
|
| - return gOriginalInitIMP(self, _cmd, aName, aReason, someUserInfo);
|
| -}
|
| -@end
|
| -
|
| namespace chrome_browser_application_mac {
|
|
|
| // Maximum number of known named exceptions we'll support. There is
|
| @@ -229,6 +84,40 @@ void RecordExceptionWithUma(NSException* exception) {
|
| BinForException(exception), kUnknownNSException);
|
| }
|
|
|
| +namespace {
|
| +
|
| +objc_exception_preprocessor g_next_preprocessor = nullptr;
|
| +
|
| +id ExceptionPreprocessor(id exception) {
|
| + static bool seen_first_exception = false;
|
| +
|
| + RecordExceptionWithUma(exception);
|
| +
|
| + const char* const kExceptionKey =
|
| + seen_first_exception ? crash_keys::mac::kLastNSException
|
| + : crash_keys::mac::kFirstNSException;
|
| + NSString* value = [NSString stringWithFormat:@"%@ reason %@",
|
| + [exception name], [exception reason]];
|
| + base::debug::SetCrashKeyValue(kExceptionKey, [value UTF8String]);
|
| +
|
| + const char* const kExceptionTraceKey =
|
| + seen_first_exception ? crash_keys::mac::kLastNSExceptionTrace
|
| + : crash_keys::mac::kFirstNSExceptionTrace;
|
| + // This exception preprocessor runs prior to the one in libobjc, which sets
|
| + // the -[NSException callStackReturnAddresses].
|
| + base::debug::SetCrashKeyToStackTrace(kExceptionTraceKey,
|
| + base::debug::StackTrace());
|
| +
|
| + seen_first_exception = true;
|
| +
|
| + // Forward to the original version.
|
| + if (g_next_preprocessor)
|
| + return g_next_preprocessor(exception);
|
| + return exception;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| void RegisterBrowserCrApp() {
|
| [BrowserCrApplication sharedApplication];
|
| };
|
| @@ -243,22 +132,6 @@ void CancelTerminate() {
|
|
|
| } // namespace chrome_browser_application_mac
|
|
|
| -namespace {
|
| -
|
| -void SwizzleInit() {
|
| - // Do-nothing wrapper so that we can arrange to only swizzle
|
| - // -[NSException raise] when DCHECK() is turned on (as opposed to
|
| - // replicating the preprocess logic which turns DCHECK() on).
|
| - CR_DEFINE_STATIC_LOCAL(base::mac::ScopedObjCClassSwizzler,
|
| - swizzle_exception,
|
| - ([NSException class],
|
| - @selector(initWithName:reason:userInfo:),
|
| - @selector(crInitWithName:reason:userInfo:)));
|
| - gOriginalInitIMP = swizzle_exception.GetOriginalImplementation();
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| // These methods are being exposed for the purposes of overriding.
|
| // Used to determine when a Panel window can become the key window.
|
| @interface NSApplication (PanelsCanBecomeKey)
|
| @@ -280,10 +153,15 @@ void SwizzleInit() {
|
| // Turn all deallocated Objective-C objects into zombies, keeping
|
| // the most recent 10,000 of them on the treadmill.
|
| ObjcEvilDoers::ZombieEnable(true, 10000);
|
| +
|
| + if (!chrome_browser_application_mac::g_next_preprocessor) {
|
| + chrome_browser_application_mac::g_next_preprocessor =
|
| + objc_setExceptionPreprocessor(
|
| + &chrome_browser_application_mac::ExceptionPreprocessor);
|
| + }
|
| }
|
|
|
| - (id)init {
|
| - SwizzleInit();
|
| self = [super init];
|
|
|
| // Sanity check to alert if overridden methods are not supported.
|
| @@ -471,172 +349,101 @@ void SwizzleInit() {
|
| }
|
|
|
| - (void)sendEvent:(NSEvent*)event {
|
| - // tracked_objects::ScopedTracker does not support parameterized
|
| - // instrumentations, so a big switch with each bunch instrumented is required.
|
| - switch (event.type) {
|
| - case NSLeftMouseDown:
|
| - case NSRightMouseDown: {
|
| - // In kiosk mode, we want to prevent context menus from appearing,
|
| - // so simply discard menu-generating events instead of passing them along.
|
| - bool kioskMode = base::CommandLine::ForCurrentProcess()->HasSwitch(
|
| - switches::kKioskMode);
|
| - bool ctrlDown = [event modifierFlags] & NSControlKeyMask;
|
| - if (kioskMode && ([event type] == NSRightMouseDown || ctrlDown))
|
| + base::mac::CallWithEHFrame(^{
|
| + // tracked_objects::ScopedTracker does not support parameterized
|
| + // instrumentations, so a big switch with each bunch instrumented is
|
| + // required.
|
| + switch (event.type) {
|
| + case NSLeftMouseDown:
|
| + case NSRightMouseDown: {
|
| + // In kiosk mode, we want to prevent context menus from appearing,
|
| + // so simply discard menu-generating events instead of passing them
|
| + // along.
|
| + bool kioskMode = base::CommandLine::ForCurrentProcess()->HasSwitch(
|
| + switches::kKioskMode);
|
| + bool ctrlDown = [event modifierFlags] & NSControlKeyMask;
|
| + if (kioskMode && ([event type] == NSRightMouseDown || ctrlDown))
|
| + break;
|
| + }
|
| + // FALL THROUGH
|
| + case NSLeftMouseUp:
|
| + case NSRightMouseUp:
|
| + case NSMouseMoved:
|
| + case NSLeftMouseDragged:
|
| + case NSRightMouseDragged:
|
| + case NSMouseEntered:
|
| + case NSMouseExited:
|
| + case NSOtherMouseDown:
|
| + case NSOtherMouseUp:
|
| + case NSOtherMouseDragged: {
|
| + tracked_objects::ScopedTracker tracking_profile(
|
| + FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| + "463272 -[BrowserCrApplication sendEvent:] Mouse"));
|
| + base::mac::ScopedSendingEvent sendingEventScoper;
|
| + [super sendEvent:event];
|
| break;
|
| - }
|
| - // FALL THROUGH
|
| - case NSLeftMouseUp:
|
| - case NSRightMouseUp:
|
| - case NSMouseMoved:
|
| - case NSLeftMouseDragged:
|
| - case NSRightMouseDragged:
|
| - case NSMouseEntered:
|
| - case NSMouseExited:
|
| - case NSOtherMouseDown:
|
| - case NSOtherMouseUp:
|
| - case NSOtherMouseDragged: {
|
| - tracked_objects::ScopedTracker tracking_profile(
|
| - FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| - "463272 -[BrowserCrApplication sendEvent:] Mouse"));
|
| - base::mac::ScopedSendingEvent sendingEventScoper;
|
| - [super sendEvent:event];
|
| - break;
|
| - }
|
| -
|
| - case NSKeyDown:
|
| - case NSKeyUp: {
|
| - tracked_objects::ScopedTracker tracking_profile(
|
| - FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| - "463272 -[BrowserCrApplication sendEvent:] Key"));
|
| - base::mac::ScopedSendingEvent sendingEventScoper;
|
| - [super sendEvent:event];
|
| - break;
|
| - }
|
| + }
|
|
|
| - case NSScrollWheel: {
|
| - tracked_objects::ScopedTracker tracking_profile(
|
| - FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| - "463272 -[BrowserCrApplication sendEvent:] ScrollWheel"));
|
| - base::mac::ScopedSendingEvent sendingEventScoper;
|
| - [super sendEvent:event];
|
| - break;
|
| - }
|
| + case NSKeyDown:
|
| + case NSKeyUp: {
|
| + tracked_objects::ScopedTracker tracking_profile(
|
| + FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| + "463272 -[BrowserCrApplication sendEvent:] Key"));
|
| + base::mac::ScopedSendingEvent sendingEventScoper;
|
| + [super sendEvent:event];
|
| + break;
|
| + }
|
|
|
| - case NSEventTypeGesture:
|
| - case NSEventTypeMagnify:
|
| - case NSEventTypeSwipe:
|
| - case NSEventTypeRotate:
|
| - case NSEventTypeBeginGesture:
|
| - case NSEventTypeEndGesture: {
|
| - tracked_objects::ScopedTracker tracking_profile(
|
| - FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| - "463272 -[BrowserCrApplication sendEvent:] Gesture"));
|
| - base::mac::ScopedSendingEvent sendingEventScoper;
|
| - [super sendEvent:event];
|
| - break;
|
| - }
|
| + case NSScrollWheel: {
|
| + tracked_objects::ScopedTracker tracking_profile(
|
| + FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| + "463272 -[BrowserCrApplication sendEvent:] ScrollWheel"));
|
| + base::mac::ScopedSendingEvent sendingEventScoper;
|
| + [super sendEvent:event];
|
| + break;
|
| + }
|
|
|
| - case NSAppKitDefined: {
|
| - tracked_objects::ScopedTracker tracking_profile(
|
| - FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| - "463272 -[BrowserCrApplication sendEvent:] AppKit"));
|
| - base::mac::ScopedSendingEvent sendingEventScoper;
|
| - [super sendEvent:event];
|
| - break;
|
| - }
|
| + case NSEventTypeGesture:
|
| + case NSEventTypeMagnify:
|
| + case NSEventTypeSwipe:
|
| + case NSEventTypeRotate:
|
| + case NSEventTypeBeginGesture:
|
| + case NSEventTypeEndGesture: {
|
| + tracked_objects::ScopedTracker tracking_profile(
|
| + FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| + "463272 -[BrowserCrApplication sendEvent:] Gesture"));
|
| + base::mac::ScopedSendingEvent sendingEventScoper;
|
| + [super sendEvent:event];
|
| + break;
|
| + }
|
|
|
| - case NSSystemDefined: {
|
| - tracked_objects::ScopedTracker tracking_profile(
|
| - FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| - "463272 -[BrowserCrApplication sendEvent:] System"));
|
| - base::mac::ScopedSendingEvent sendingEventScoper;
|
| - [super sendEvent:event];
|
| - break;
|
| - }
|
| + case NSAppKitDefined: {
|
| + tracked_objects::ScopedTracker tracking_profile(
|
| + FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| + "463272 -[BrowserCrApplication sendEvent:] AppKit"));
|
| + base::mac::ScopedSendingEvent sendingEventScoper;
|
| + [super sendEvent:event];
|
| + break;
|
| + }
|
|
|
| - default: {
|
| - tracked_objects::ScopedTracker tracking_profile(
|
| - FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| - "463272 -[BrowserCrApplication sendEvent:] Other"));
|
| - base::mac::ScopedSendingEvent sendingEventScoper;
|
| - [super sendEvent:event];
|
| - }
|
| - }
|
| -}
|
| + case NSSystemDefined: {
|
| + tracked_objects::ScopedTracker tracking_profile(
|
| + FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| + "463272 -[BrowserCrApplication sendEvent:] System"));
|
| + base::mac::ScopedSendingEvent sendingEventScoper;
|
| + [super sendEvent:event];
|
| + break;
|
| + }
|
|
|
| -// NSExceptions which are caught by the event loop are logged here.
|
| -// NSException uses setjmp/longjmp, which can be very bad for C++, so
|
| -// we attempt to track and report them.
|
| -- (void)reportException:(NSException *)anException {
|
| - // If we throw an exception in this code, we can create an infinite
|
| - // loop. If we throw out of the if() without resetting
|
| - // |reportException|, we'll stop reporting exceptions for this run.
|
| - static BOOL reportingException = NO;
|
| - DCHECK(!reportingException);
|
| - if (!reportingException) {
|
| - reportingException = YES;
|
| - chrome_browser_application_mac::RecordExceptionWithUma(anException);
|
| -
|
| - // http://crbug.com/45928 is a bug about needing to double-close
|
| - // windows sometimes. One theory is that |-isHandlingSendEvent|
|
| - // gets latched to always return |YES|. Since scopers are used to
|
| - // manipulate that value, that should not be possible. One way to
|
| - // sidestep scopers is setjmp/longjmp (see above). The following
|
| - // is to "fix" this while the more fundamental concern is
|
| - // addressed elsewhere.
|
| - [self setHandlingSendEvent:NO];
|
| -
|
| - // If |ScopedNSExceptionEnabler| is used to allow exceptions, and an
|
| - // uncaught exception is thrown, it will throw past all of the scopers.
|
| - // Reset the flag so that future exceptions are not masked.
|
| - base::mac::SetNSExceptionsAllowed(false);
|
| -
|
| - // Store some human-readable information in breakpad keys in case
|
| - // there is a crash. Since breakpad does not provide infinite
|
| - // storage, we track two exceptions. The first exception thrown
|
| - // is tracked because it may be the one which caused the system to
|
| - // go off the rails. The last exception thrown is tracked because
|
| - // it may be the one most directly associated with the crash.
|
| - static BOOL trackedFirstException = NO;
|
| -
|
| - const char* const kExceptionKey =
|
| - trackedFirstException ? crash_keys::mac::kLastNSException
|
| - : crash_keys::mac::kFirstNSException;
|
| - NSString* value = [NSString stringWithFormat:@"%@ reason %@",
|
| - [anException name], [anException reason]];
|
| - base::debug::SetCrashKeyValue(kExceptionKey, [value UTF8String]);
|
| -
|
| - // Encode the callstack from point of throw.
|
| - // TODO(shess): Our swizzle plus the 23-frame limit plus Cocoa
|
| - // overhead may make this less than useful. If so, perhaps skip
|
| - // some items and/or use two keys.
|
| - const char* const kExceptionBtKey =
|
| - trackedFirstException ? crash_keys::mac::kLastNSExceptionTrace
|
| - : crash_keys::mac::kFirstNSExceptionTrace;
|
| - NSArray* addressArray = [anException callStackReturnAddresses];
|
| - NSUInteger addressCount = [addressArray count];
|
| - if (addressCount) {
|
| - // SetCrashKeyFromAddresses() only encodes 23, so that's a natural limit.
|
| - const NSUInteger kAddressCountMax = 23;
|
| - void* addresses[kAddressCountMax];
|
| - if (addressCount > kAddressCountMax)
|
| - addressCount = kAddressCountMax;
|
| -
|
| - for (NSUInteger i = 0; i < addressCount; ++i) {
|
| - addresses[i] = reinterpret_cast<void*>(
|
| - [[addressArray objectAtIndex:i] unsignedIntegerValue]);
|
| + default: {
|
| + tracked_objects::ScopedTracker tracking_profile(
|
| + FROM_HERE_WITH_EXPLICIT_FUNCTION(
|
| + "463272 -[BrowserCrApplication sendEvent:] Other"));
|
| + base::mac::ScopedSendingEvent sendingEventScoper;
|
| + [super sendEvent:event];
|
| }
|
| - base::debug::SetCrashKeyFromAddresses(
|
| - kExceptionBtKey, addresses, static_cast<size_t>(addressCount));
|
| - } else {
|
| - base::debug::ClearCrashKey(kExceptionBtKey);
|
| }
|
| - trackedFirstException = YES;
|
| -
|
| - reportingException = NO;
|
| - }
|
| -
|
| - [super reportException:anException];
|
| + });
|
| }
|
|
|
| - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
|
|
|