As I went on something of a voyage of discovery with this, I thought I would
write up my findings. Maybe this will save someone the pain I went through.
It's rather a long post, sorry about that.
My basic tenet is that I want to catch unexpected exceptions and terminate my
app in a way that produces a crash report containing a proper stack trace. I
then use Uli Kusterer's UKCrashReporter class - somewhat modified - to send
these off to my webserver when the app is relaunched. This has proven to be a
very successful strategy, and I have found (and fixed) a lot of bugs this way.
The alternative is to let exceptions pass by unnoticed, the effect of which is
that in a code sequence A, B, C, D, an unexpected exception thrown in step B
will mean that C and D are never executed (the app just mysteriously returns to
the event loop) and that could cause some really obscure bugs in the field
which it would be impossible to diagnose. I for one am not satisified with
that.
But, it turns out, there is no way of trapping uncaught exceptions without also
trapping exceptions that would otherwise be caught and handled within an @try
block. This is because, for whatever reason, the delegate you pass to
[NSExceptionHandler setDelegate:] is never called, even if you pass
NSHandleTopLevelExceptionMask to [NSExceptionHandler
setExceptionHandlingMask:], unless you also specify NSHandleOtherExceptionMask.
The documentation promises that the delegate will be called, should an
exception percolate through to the top level exception handler in
NSApplication, but it isn't. At least, it isn't on Leopard. They seem to have
fixed this on Snow Leopard.
However, you are no further forward even on Snow Leopard because
[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:] (which appears
in the call stack when you respond to the user selecting an entry from the
menu) catches and rethrows any otherwise uncaught exception. Result? You lose
the stack trace in your crash report, rendering it useless.
So, here is my solution. This only works because the frameworks don't seem to
ever use exception handlers to recover from exceptions gracefully, and neither
do I:
1. Pass *all* mask bits, except for NSLogUncaughtSystemExceptionMask and
NSHandleUncaughtSystemExceptionMask, to [NSExceptionHandler
setExceptionHandlingMask:].
2. In your NSExceptionHandler delegate, crash the app when an exception
arrives (I use "*(int * 1) = 0;" to do this), _except_for_ a few exceptions
that seem to crop up every now and again in normal usage and must therefore be
silently ignored (these look like bugs in the Cocoa frameworks to me).
3. Don't log or handle uncaught system exceptions because you get a better
stack trace if you leave them alone. Also, if you log or handle these you loop
forever on Tiger when something like a SIGBUS comes along.
4. Embed UKCrashReporter in your app (and write a suitable back-end for it on
your webserver).
And now here's a reward for reading this far. Here's the list of exceptions I
silently ignore in my NSExceptionHandler delegate:
-----------------------------------------------------------------------
NSString *nss_exception_name = [exception name];
NSString *nss_reason = [exception reason];
if ([nss_exception_name isEqualToString: NSAccessibilityException])
return YES;
if ([nss_exception_name isEqualToString: NSInternalInconsistencyException])
{
if (gIsTiger) // Happens on startup, apparently a Tiger bug
{
// __T ("lockFocus sent to a view whose window is ")
// __T ("deferred and does not yet have a corresponding platform
window")
if ([nss_reason hasPrefix: @"lockFocus sent to a view whose window is
deferred"])
return YES;
}
// Happens occasionally during testing (on 10.5.8), plus one crash reported
in the field
if ([nss_reason hasPrefix: @"Invalid message sent to event \"NSEvent:
type=LMouseUp"])
return YES;
if ([nss_reason hasPrefix: @"Invalid message sent to event \"NSEvent:
type=RMouseUp"])
return YES;
// Seen this once in the field:
if ([nss_reason hasPrefix: @"Failed to get KeyCode from EventRef"])
return YES;
}
// Display bye-bye message to the user, maybe offer to save her work
* (int *) 1 = 0;
-----------------------------------------------------------------------
I'm sure there are one or two other special cases to add to this list. My
users will inform me what they are in due course, and if I learn anything
useful I will post it back to this thread.
Paul Sanders
http://www.alpinesoft.co.uk
PS: I just noticed you work for CodeWeavers, Ken. Salutations.
----- Original Message -----
From: "Ken Thomases" <[email protected]>
To: "Paul Sanders" <[email protected]>
Sent: Thursday, January 21, 2010 5:14 PM
Subject: Re: Uncaught exceptions not terminating my app
Your exception handler should be configured to only handle uncaught exceptions
or those that would make it to the top-level catch. In other words, I think
you should exclude NSHandleOtherExceptionMask from your handling mask, because
it requests handling of caught exceptions.
Regards,
Ken
_______________________________________________
Cocoa-dev mailing list ([email protected])
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com
This email sent to [email protected]