| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #import "chrome/browser/chrome_application_mac.h" | |
| 6 | |
| 7 #import "base/histogram.h" | |
| 8 #import "base/logging.h" | |
| 9 #import "base/scoped_nsobject.h" | |
| 10 #import "base/sys_string_conversions.h" | |
| 11 #import "chrome/app/breakpad_mac.h" | |
| 12 #import "chrome/browser/cocoa/chrome_event_processing_window.h" | |
| 13 #import "chrome/browser/cocoa/objc_method_swizzle.h" | |
| 14 #import "chrome/browser/renderer_host/render_widget_host_view_mac.h" | |
| 15 | |
| 16 // The implementation of NSExceptions break various assumptions in the | |
| 17 // Chrome code. This category defines a replacement for | |
| 18 // -initWithName:reason:userInfo: for purposes of forcing a break in | |
| 19 // the debugger when an exception is raised. -raise sounds more | |
| 20 // obvious to intercept, but it doesn't catch the original throw | |
| 21 // because the objc runtime doesn't use it. | |
| 22 @interface NSException (NSExceptionSwizzle) | |
| 23 - (id)chromeInitWithName:(NSString *)aName | |
| 24 reason:(NSString *)aReason | |
| 25 userInfo:(NSDictionary *)someUserInfo; | |
| 26 @end | |
| 27 | |
| 28 static IMP gOriginalInitIMP = NULL; | |
| 29 | |
| 30 @implementation NSException (NSExceptionSwizzle) | |
| 31 - (id)chromeInitWithName:(NSString *)aName | |
| 32 reason:(NSString *)aReason | |
| 33 userInfo:(NSDictionary *)someUserInfo { | |
| 34 // Method only called when swizzled. | |
| 35 DCHECK(_cmd == @selector(initWithName:reason:userInfo:)); | |
| 36 | |
| 37 // Dear reader: something you just did provoked an NSException. | |
| 38 // Please check your backtrace and see if you can't file a bug with | |
| 39 // a repro case. You should be able to safely continue past the | |
| 40 // NOTREACHED(), but feel free to comment it out locally if it is | |
| 41 // making your job hard. | |
| 42 DLOG(ERROR) << "Someone is preparing to raise an exception! " | |
| 43 << base::SysNSStringToUTF8(aName) << " *** " | |
| 44 << base::SysNSStringToUTF8(aReason); | |
| 45 NOTREACHED(); | |
| 46 | |
| 47 // Forward to the original version. | |
| 48 return gOriginalInitIMP(self, _cmd, aName, aReason, someUserInfo); | |
| 49 } | |
| 50 @end | |
| 51 | |
| 52 namespace CrApplicationNSException { | |
| 53 | |
| 54 // Maximum number of known named exceptions we'll support. There is | |
| 55 // no central registration, but I only find about 75 possibilities in | |
| 56 // the system frameworks, and many of them are probably not | |
| 57 // interesting to track in aggregate (those relating to distributed | |
| 58 // objects, for instance). | |
| 59 const size_t kKnownNSExceptionCount = 25; | |
| 60 | |
| 61 const size_t kUnknownNSException = kKnownNSExceptionCount; | |
| 62 | |
| 63 size_t BinForException(NSException* exception) { | |
| 64 // A list of common known exceptions. The list position will | |
| 65 // determine where they live in the histogram, so never move them | |
| 66 // around, only add to the end. | |
| 67 static const NSString* kKnownNSExceptionNames[] = { | |
| 68 // ??? | |
| 69 NSGenericException, | |
| 70 | |
| 71 // Out-of-range on NSString or NSArray. | |
| 72 NSRangeException, | |
| 73 | |
| 74 // Invalid arg to method, unrecognized selector. | |
| 75 NSInvalidArgumentException, | |
| 76 | |
| 77 // malloc() returned null in object creation, I think. | |
| 78 NSMallocException, | |
| 79 | |
| 80 nil | |
| 81 }; | |
| 82 | |
| 83 // Make sure our array hasn't outgrown our abilities to track it. | |
| 84 DCHECK_LE(arraysize(kKnownNSExceptionNames), kKnownNSExceptionCount); | |
| 85 | |
| 86 const NSString* name = [exception name]; | |
| 87 for (int i = 0; kKnownNSExceptionNames[i]; ++i) { | |
| 88 if (name == kKnownNSExceptionNames[i]) { | |
| 89 return i; | |
| 90 } | |
| 91 } | |
| 92 return kUnknownNSException; | |
| 93 } | |
| 94 | |
| 95 void RecordExceptionWithUma(NSException* exception) { | |
| 96 static LinearHistogram histogram("OSX.NSException", 0, kUnknownNSException, | |
| 97 kUnknownNSException + 1); | |
| 98 histogram.SetFlags(kUmaTargetedHistogramFlag); | |
| 99 histogram.Add(BinForException(exception)); | |
| 100 } | |
| 101 | |
| 102 } // CrApplicationNSException | |
| 103 | |
| 104 namespace { | |
| 105 | |
| 106 // Helper to make it easy to get crash keys right. | |
| 107 // TODO(shess): Find a better home for this. app/breakpad_mac.h | |
| 108 // doesn't work. | |
| 109 class ScopedCrashKey { | |
| 110 public: | |
| 111 ScopedCrashKey(NSString* key, NSString* value) | |
| 112 : crash_key_([key retain]) { | |
| 113 SetCrashKeyValue(crash_key_.get(), value); | |
| 114 } | |
| 115 ~ScopedCrashKey() { | |
| 116 ClearCrashKeyValue(crash_key_.get()); | |
| 117 } | |
| 118 | |
| 119 private: | |
| 120 scoped_nsobject<NSString> crash_key_; | |
| 121 }; | |
| 122 | |
| 123 // Do-nothing wrapper so that we can arrange to only swizzle | |
| 124 // -[NSException raise] when DCHECK() is turned on (as opposed to | |
| 125 // replicating the preprocess logic which turns DCHECK() on). | |
| 126 BOOL SwizzleNSExceptionInit() { | |
| 127 gOriginalInitIMP = ObjcEvilDoers::SwizzleImplementedInstanceMethods( | |
| 128 [NSException class], | |
| 129 @selector(initWithName:reason:userInfo:), | |
| 130 @selector(chromeInitWithName:reason:userInfo:)); | |
| 131 return YES; | |
| 132 } | |
| 133 | |
| 134 } // namespace | |
| 135 | |
| 136 @implementation CrApplication | |
| 137 | |
| 138 - init { | |
| 139 // TODO(shess): Push this somewhere where it can apply to the plugin | |
| 140 // and renderer processes, and where it can intercept uncaught | |
| 141 // exceptions. | |
| 142 DCHECK(SwizzleNSExceptionInit()); | |
| 143 return [super init]; | |
| 144 } | |
| 145 | |
| 146 // -terminate: is the entry point for orderly "quit" operations in Cocoa. | |
| 147 // This includes the application menu's quit menu item and keyboard | |
| 148 // equivalent, the application's dock icon menu's quit menu item, "quit" (not | |
| 149 // "force quit") in the Activity Monitor, and quits triggered by user logout | |
| 150 // and system restart and shutdown. | |
| 151 // | |
| 152 // The default NSApplication -terminate: implementation will end the process | |
| 153 // by calling exit(), and thus never leave the main run loop. This is | |
| 154 // unsuitable for Chrome's purposes. Chrome depends on leaving the main | |
| 155 // run loop to perform a proper orderly shutdown. This design is ingrained | |
| 156 // in the application and the assumptions that its code makes, and is | |
| 157 // entirely reasonable and works well on other platforms, but it's not | |
| 158 // compatible with the standard Cocoa quit sequence. Quits originated from | |
| 159 // within the application can be redirected to not use -terminate:, but | |
| 160 // quits from elsewhere cannot be. | |
| 161 // | |
| 162 // To allow the Cocoa-based Chrome to support the standard Cocoa -terminate: | |
| 163 // interface, and allow all quits to cause Chrome to shut down properly | |
| 164 // regardless of their origin, -terminate: is overriden. The custom | |
| 165 // -terminate: does not end the application with exit(). Instead, it simply | |
| 166 // returns after posting the normal NSApplicationWillTerminateNotification | |
| 167 // notification. The application is responsible for exiting on its own in | |
| 168 // whatever way it deems appropriate. In Chrome's case, the main run loop will | |
| 169 // end and the applicaton will exit by returning from main(). | |
| 170 // | |
| 171 // This implementation of -terminate: is scaled back and is not as | |
| 172 // fully-featured as the implementation in NSApplication, nor is it a direct | |
| 173 // drop-in replacement -terminate: in most applications. It is | |
| 174 // purpose-specific to Chrome. | |
| 175 - (void)terminate:(id)sender { | |
| 176 NSApplicationTerminateReply shouldTerminate = NSTerminateNow; | |
| 177 SEL selector = @selector(applicationShouldTerminate:); | |
| 178 if ([[self delegate] respondsToSelector:selector]) | |
| 179 shouldTerminate = [[self delegate] applicationShouldTerminate:self]; | |
| 180 | |
| 181 // If shouldTerminate is NSTerminateLater, the application is expected to | |
| 182 // call -replyToApplicationShouldTerminate: when it knows whether or not it | |
| 183 // should terminate. If the argument is YES, | |
| 184 // -replyToApplicationShouldTerminate: will call -terminate:. This will | |
| 185 // result in another call to the delegate's -applicationShouldTerminate:, | |
| 186 // which would be expected to return NSTerminateNow at that point. | |
| 187 if (shouldTerminate != NSTerminateNow) | |
| 188 return; | |
| 189 | |
| 190 [[NSNotificationCenter defaultCenter] | |
| 191 postNotificationName:NSApplicationWillTerminateNotification | |
| 192 object:self]; | |
| 193 | |
| 194 // Return, don't exit. The application is responsible for exiting on its | |
| 195 // own. | |
| 196 } | |
| 197 | |
| 198 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender { | |
| 199 // The Dock menu contains an automagic section where you can select | |
| 200 // amongst open windows. If a window is closed via JavaScript while | |
| 201 // the menu is up, the menu item for that window continues to exist. | |
| 202 // When a window is selected this method is called with the | |
| 203 // now-freed window as |aTarget|. Short-circuit the call if | |
| 204 // |aTarget| is not a valid window. | |
| 205 if (anAction == @selector(_selectWindow:)) { | |
| 206 // Not using -[NSArray containsObject:] because |aTarget| may be a | |
| 207 // freed object. | |
| 208 BOOL found = NO; | |
| 209 for (NSWindow* window in [self windows]) { | |
| 210 if (window == aTarget) { | |
| 211 found = YES; | |
| 212 break; | |
| 213 } | |
| 214 } | |
| 215 if (!found) { | |
| 216 return NO; | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 // When a Cocoa control is wired to a freed object, we get crashers | |
| 221 // in the call to |super| with no useful information in the | |
| 222 // backtrace. Attempt to add some useful information. | |
| 223 static const NSString* kActionKey = @"sendaction"; | |
| 224 | |
| 225 // If the action is something generic like -commandDispatch:, then | |
| 226 // the tag is essential. | |
| 227 NSInteger tag = 0; | |
| 228 if ([sender isKindOfClass:[NSControl class]]) { | |
| 229 tag = [sender tag]; | |
| 230 if (tag == 0 || tag == -1) { | |
| 231 tag = [sender selectedTag]; | |
| 232 } | |
| 233 } else if ([sender isKindOfClass:[NSMenuItem class]]) { | |
| 234 tag = [sender tag]; | |
| 235 } | |
| 236 | |
| 237 NSString* actionString = NSStringFromSelector(anAction); | |
| 238 NSString* value = | |
| 239 [NSString stringWithFormat:@"%@ tag %d sending %@ to %p", | |
| 240 [sender className], tag, actionString, aTarget]; | |
| 241 | |
| 242 ScopedCrashKey key(kActionKey, value); | |
| 243 return [super sendAction:anAction to:aTarget from:sender]; | |
| 244 } | |
| 245 | |
| 246 - (void)sendEvent:(NSEvent*)event { | |
| 247 // The superclass's |sendEvent:| sends keyboard events to the menu and the key | |
| 248 // view loop before dispatching them to |keyDown:|. Since we want to send keys | |
| 249 // to the renderer before sending them to the menu, and we never want them to | |
| 250 // the kev view loop when the web is focussed, we change this behavior. | |
| 251 if ([[self keyWindow] | |
| 252 isKindOfClass:[ChromeEventProcessingWindow class]]) { | |
| 253 if ([static_cast<ChromeEventProcessingWindow*>([self keyWindow]) | |
| 254 shortcircuitEvent:event]) | |
| 255 return; | |
| 256 } | |
| 257 | |
| 258 [super sendEvent:event]; | |
| 259 } | |
| 260 | |
| 261 // NSExceptions which are caught by the event loop are logged here. | |
| 262 // NSException uses setjmp/longjmp, which can be very bad for C++, so | |
| 263 // we attempt to track and report them. | |
| 264 - (void)reportException:(NSException *)anException { | |
| 265 // If we throw an exception in this code, we can create an infinite | |
| 266 // loop. If we throw out of the if() without resetting | |
| 267 // |reportException|, we'll stop reporting exceptions for this run. | |
| 268 static BOOL reportingException = NO; | |
| 269 DCHECK(!reportingException); | |
| 270 if (!reportingException) { | |
| 271 reportingException = YES; | |
| 272 CrApplicationNSException::RecordExceptionWithUma(anException); | |
| 273 | |
| 274 // Store some human-readable information in breakpad keys in case | |
| 275 // there is a crash. Since breakpad does not provide infinite | |
| 276 // storage, we track two exceptions. The first exception thrown | |
| 277 // is tracked because it may be the one which caused the system to | |
| 278 // go off the rails. The last exception thrown is tracked because | |
| 279 // it may be the one most directly associated with the crash. | |
| 280 static const NSString* kFirstExceptionKey = @"firstexception"; | |
| 281 static BOOL trackedFirstException = NO; | |
| 282 static const NSString* kLastExceptionKey = @"lastexception"; | |
| 283 | |
| 284 // TODO(shess): It would be useful to post some stacktrace info | |
| 285 // from the exception. | |
| 286 // 10.6 has -[NSException callStackSymbols] | |
| 287 // 10.5 has -[NSException callStackReturnAddresses] | |
| 288 // 10.5 has backtrace_symbols(). | |
| 289 // I've tried to combine the latter two, but got nothing useful. | |
| 290 // The addresses are right, though, maybe we could train the crash | |
| 291 // server to decode them for us. | |
| 292 | |
| 293 NSString* value = [NSString stringWithFormat:@"%@ reason %@", | |
| 294 [anException name], [anException reason]]; | |
| 295 if (!trackedFirstException) { | |
| 296 SetCrashKeyValue(kFirstExceptionKey, value); | |
| 297 trackedFirstException = YES; | |
| 298 } else { | |
| 299 SetCrashKeyValue(kLastExceptionKey, value); | |
| 300 } | |
| 301 | |
| 302 reportingException = NO; | |
| 303 } | |
| 304 | |
| 305 [super reportException:anException]; | |
| 306 } | |
| 307 | |
| 308 @end | |
| 309 | |
| 310 namespace CrApplicationCC { | |
| 311 | |
| 312 void Terminate() { | |
| 313 [NSApp terminate:nil]; | |
| 314 } | |
| 315 | |
| 316 } // namespace CrApplicationCC | |
| OLD | NEW |