diff options
author | shess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-21 20:33:43 +0000 |
---|---|---|
committer | shess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-21 20:33:43 +0000 |
commit | 2e8a901895bfb5a62d546674db8c75591f83a12d (patch) | |
tree | 329a1e56a66fc5acebd02055b6e13119d11c2acf /chrome | |
parent | d6393755b19a3d6337b6c7a5a30ff7f64661995e (diff) | |
download | chromium_src-2e8a901895bfb5a62d546674db8c75591f83a12d.zip chromium_src-2e8a901895bfb5a62d546674db8c75591f83a12d.tar.gz chromium_src-2e8a901895bfb5a62d546674db8c75591f83a12d.tar.bz2 |
[Mac] DCHECK when raising NSException.
NSException == badness. This causes a DCHECK when someone is setting
out to raise an NSException.
http://crbug.com/24463
TEST=none
Provoke some exceptions.
Review URL: http://codereview.chromium.org/292004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29700 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/chrome_application_mac.mm | 58 | ||||
-rw-r--r-- | chrome/browser/cocoa/objc_method_swizzle.h | 27 | ||||
-rw-r--r-- | chrome/browser/cocoa/objc_method_swizzle.mm | 60 | ||||
-rw-r--r-- | chrome/browser/cocoa/objc_method_swizzle_unittest.mm | 76 | ||||
-rwxr-xr-x | chrome/chrome.gyp | 3 |
5 files changed, 224 insertions, 0 deletions
diff --git a/chrome/browser/chrome_application_mac.mm b/chrome/browser/chrome_application_mac.mm index 135a77e..94c975b 100644 --- a/chrome/browser/chrome_application_mac.mm +++ b/chrome/browser/chrome_application_mac.mm @@ -7,8 +7,47 @@ #import "base/histogram.h" #import "base/logging.h" #import "base/scoped_nsobject.h" +#import "base/sys_string_conversions.h" #import "chrome/app/breakpad_mac.h" #import "chrome/browser/cocoa/chrome_event_processing_window.h" +#import "chrome/browser/cocoa/objc_method_swizzle.h" +#import "chrome/browser/renderer_host/render_widget_host_view_mac.h" + +// 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 (NSExceptionSwizzle) +- (id)chromeInitWithName:(NSString *)aName + reason:(NSString *)aReason + userInfo:(NSDictionary *)someUserInfo; +@end + +static IMP gOriginalInitIMP = NULL; + +@implementation NSException (NSExceptionSwizzle) +- (id)chromeInitWithName:(NSString *)aName + reason:(NSString *)aReason + userInfo:(NSDictionary *)someUserInfo { + // Method only called when swizzled. + DCHECK(_cmd == @selector(initWithName:reason:userInfo:)); + + // Dear reader: something you just did provoked an NSException. + // Please check your backtrace and see if you can't file a bug with + // a repro case. You should be able to safely continue past the + // NOTREACHED(), but feel free to comment it out locally if it is + // making your job hard. + DLOG(ERROR) << "Someone is preparing to raise an exception! " + << base::SysNSStringToUTF8(aName) << " *** " + << base::SysNSStringToUTF8(aReason); + NOTREACHED(); + + // Forward to the original version. + return gOriginalInitIMP(self, _cmd, aName, aReason, someUserInfo); +} +@end namespace CrApplicationNSException { @@ -81,10 +120,29 @@ class ScopedCrashKey { scoped_nsobject<NSString> crash_key_; }; +// 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). +BOOL SwizzleNSExceptionInit() { + gOriginalInitIMP = ObjcEvilDoers::SwizzleImplementedInstanceMethods( + [NSException class], + @selector(initWithName:reason:userInfo:), + @selector(chromeInitWithName:reason:userInfo:)); + return YES; +} + } // namespace @implementation CrApplication +- init { + // TODO(shess): Push this somewhere where it can apply to the plugin + // and renderer processes, and where it can intercept uncaught + // exceptions. + DCHECK(SwizzleNSExceptionInit()); + return [super init]; +} + // -terminate: is the entry point for orderly "quit" operations in Cocoa. // This includes the application menu's quit menu item and keyboard // equivalent, the application's dock icon menu's quit menu item, "quit" (not diff --git a/chrome/browser/cocoa/objc_method_swizzle.h b/chrome/browser/cocoa/objc_method_swizzle.h new file mode 100644 index 0000000..328e79f --- /dev/null +++ b/chrome/browser/cocoa/objc_method_swizzle.h @@ -0,0 +1,27 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_COCOA_OBJC_METHOD_SWIZZLE_H_ +#define CHROME_BROWSER_COCOA_OBJC_METHOD_SWIZZLE_H_ + +#import <objc/objc-class.h> + +// You should think twice every single time you use anything from this +// namespace. +namespace ObjcEvilDoers { + +// This is similar to class_getInstanceMethod(), except that it +// returns NULL if |aClass| does not directly implement |aSelector|. +Method GetImplementedInstanceMethod(Class aClass, SEL aSelector); + +// Exchanges the implementation of |originalSelector| and +// |alternateSelector| within |aClass|. Both selectors must be +// implemented directly by |aClass|, not inherited. The IMP returned +// is for |originalSelector| (for purposes of forwarding). +IMP SwizzleImplementedInstanceMethods( + Class aClass, const SEL originalSelector, const SEL alternateSelector); + +} // namespace ObjcEvilDoers + +#endif // CHROME_BROWSER_COCOA_OBJC_METHOD_SWIZZLE_H_ diff --git a/chrome/browser/cocoa/objc_method_swizzle.mm b/chrome/browser/cocoa/objc_method_swizzle.mm new file mode 100644 index 0000000..4471efc --- /dev/null +++ b/chrome/browser/cocoa/objc_method_swizzle.mm @@ -0,0 +1,60 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome/browser/cocoa/objc_method_swizzle.h" + +#import "base/histogram.h" +#import "base/logging.h" +#import "base/scoped_nsobject.h" +#import "chrome/app/breakpad_mac.h" + +namespace ObjcEvilDoers { + +Method GetImplementedInstanceMethod(Class aClass, SEL aSelector) { + Method method = NULL; + unsigned int methodCount = 0; + Method* methodList = class_copyMethodList(aClass, &methodCount); + if (methodList) { + for (unsigned int i = 0; i < methodCount; ++i) { + if (method_getName(methodList[i]) == aSelector) { + method = methodList[i]; + break; + } + } + free(methodList); + } + return method; +} + +IMP SwizzleImplementedInstanceMethods( + Class aClass, const SEL originalSelector, const SEL alternateSelector) { + // The methods must both be implemented by the target class, not + // inherited from a superclass. + Method original = GetImplementedInstanceMethod(aClass, originalSelector); + Method alternate = GetImplementedInstanceMethod(aClass, alternateSelector); + DCHECK(original); + DCHECK(alternate); + if (!original || !alternate) { + return NULL; + } + + // The argument and return types must match exactly. + const char* originalTypes = method_getTypeEncoding(original); + const char* alternateTypes = method_getTypeEncoding(alternate); + DCHECK(originalTypes); + DCHECK(alternateTypes); + DCHECK(0 == strcmp(originalTypes, alternateTypes)); + if (!originalTypes || !alternateTypes || + strcmp(originalTypes, alternateTypes)) { + return NULL; + } + + IMP ret = method_getImplementation(original); + if (ret) { + method_exchangeImplementations(original, alternate); + } + return ret; +} + +} // namespace ObjcEvilDoers diff --git a/chrome/browser/cocoa/objc_method_swizzle_unittest.mm b/chrome/browser/cocoa/objc_method_swizzle_unittest.mm new file mode 100644 index 0000000..09aee70 --- /dev/null +++ b/chrome/browser/cocoa/objc_method_swizzle_unittest.mm @@ -0,0 +1,76 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome/browser/cocoa/objc_method_swizzle.h" + +#include "base/scoped_nsobject.h" +#include "testing/gtest/include/gtest/gtest.h" + +@interface ObjcMethodSwizzleTest : NSObject +- (id)self; + +- (NSInteger)one; +- (NSInteger)two; +@end + +@implementation ObjcMethodSwizzleTest : NSObject +- (id)self { + return [super self]; +} + +- (NSInteger)one { + return 1; +} +- (NSInteger)two { + return 2; +} +@end + +@interface ObjcMethodSwizzleTest (ObjcMethodSwizzleTestCategory) +- (NSUInteger)hash; +@end + +@implementation ObjcMethodSwizzleTest (ObjcMethodSwizzleTestCategory) +- (NSUInteger)hash { + return [super hash]; +} +@end + +namespace ObjcEvilDoers { + +TEST(ObjcMethodSwizzleTest, GetImplementedInstanceMethod) { + EXPECT_EQ(class_getInstanceMethod([NSObject class], @selector(dealloc)), + GetImplementedInstanceMethod([NSObject class], @selector(dealloc))); + EXPECT_EQ(class_getInstanceMethod([NSObject class], @selector(self)), + GetImplementedInstanceMethod([NSObject class], @selector(self))); + EXPECT_EQ(class_getInstanceMethod([NSObject class], @selector(hash)), + GetImplementedInstanceMethod([NSObject class], @selector(hash))); + + Class testClass = [ObjcMethodSwizzleTest class]; + EXPECT_EQ(class_getInstanceMethod(testClass, @selector(self)), + GetImplementedInstanceMethod(testClass, @selector(self))); + EXPECT_NE(class_getInstanceMethod([NSObject class], @selector(self)), + class_getInstanceMethod(testClass, @selector(self))); + + EXPECT_TRUE(class_getInstanceMethod(testClass, @selector(dealloc))); + EXPECT_FALSE(GetImplementedInstanceMethod(testClass, @selector(dealloc))); +} + +TEST(ObjcMethodSwizzleTest, SwizzleImplementedInstanceMethods) { + scoped_nsobject<ObjcMethodSwizzleTest> object( + [[ObjcMethodSwizzleTest alloc] init]); + EXPECT_EQ([object one], 1); + EXPECT_EQ([object two], 2); + + Class testClass = [object class]; + SwizzleImplementedInstanceMethods(testClass, @selector(one), @selector(two)); + EXPECT_EQ([object one], 2); + EXPECT_EQ([object two], 1); + + SwizzleImplementedInstanceMethods(testClass, @selector(one), @selector(two)); + EXPECT_EQ([object one], 1); + EXPECT_EQ([object two], 2); +} + +} // namespace ObjcEvilDoers diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 23e79ad..4b5782f 100755 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1148,6 +1148,8 @@ 'browser/cocoa/multi_key_equivalent_button.mm', 'browser/cocoa/nswindow_local_state.h', 'browser/cocoa/nswindow_local_state.mm', + 'browser/cocoa/objc_method_swizzle.h', + 'browser/cocoa/objc_method_swizzle.mm', 'browser/cocoa/page_info_window_controller.h', 'browser/cocoa/page_info_window_controller.mm', 'browser/cocoa/page_info_window_mac.h', @@ -4430,6 +4432,7 @@ 'browser/cocoa/menu_button_unittest.mm', 'browser/cocoa/nsimage_cache_unittest.mm', 'browser/cocoa/nswindow_local_state_unittest.mm', + 'browser/cocoa/objc_method_swizzle_unittest.mm', 'browser/cocoa/page_info_window_controller_unittest.mm', 'browser/cocoa/preferences_window_controller_unittest.mm', 'browser/cocoa/rwhvm_editcommand_helper_unittest.mm', |