summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorshess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-21 20:33:43 +0000
committershess@chromium.org <shess@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-21 20:33:43 +0000
commit2e8a901895bfb5a62d546674db8c75591f83a12d (patch)
tree329a1e56a66fc5acebd02055b6e13119d11c2acf /chrome/browser
parentd6393755b19a3d6337b6c7a5a30ff7f64661995e (diff)
downloadchromium_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/browser')
-rw-r--r--chrome/browser/chrome_application_mac.mm58
-rw-r--r--chrome/browser/cocoa/objc_method_swizzle.h27
-rw-r--r--chrome/browser/cocoa/objc_method_swizzle.mm60
-rw-r--r--chrome/browser/cocoa/objc_method_swizzle_unittest.mm76
4 files changed, 221 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