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" <k...@codeweavers.com>
To: "Paul Sanders" <p.sand...@alpinesoft.co.uk>
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 (Cocoa-dev@lists.apple.com)

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 arch...@mail-archive.com

Reply via email to