4

I have an LaunchAgent using HockeyApp for crash reporting. Now I noticed that uncaught exception where not reported by HockeyApp, like they would have been in a normal macOS app.

For example:

- (void)upperCaseString:(NSString *)aString withReply:(void (^)(NSString *))reply {
    NSArray *array = [NSArray array];
    reply([array objectAtIndex:23]);
}

Never reaches NSUncaughtExceptionHandler, but the console logs:

<NSXPCConnection: 0x7fe97dc0f110> connection from pid 44573: Warning: Exception caught during invocation of received message, dropping incoming message and invalidating the connection.

The question is how to get unhandled exception reported with HockeyApp.

catlan
  • 25,100
  • 8
  • 67
  • 78

1 Answers1

2

Problem:

XPC seems to have its own @try @catch block, which catches unhandled exceptions inside a method, logs the exception and then the calls -[NSXPCConnection interruptionHandler].

This issue is reported to Apple under rdar://48543049.

NOTE: These are not copy & past solutions, carefully evaluate your crash reporting framework. I link to implementation details of PLCrashReporter.

Solution A:

@try @catch block:

- (void)upperCaseString:(NSString *)aString withReply:(void (^)(NSString *))reply {
    @try {
        NSArray *array = [NSArray array];
        reply([array objectAtIndex:23]);
    } @catch (NSException *exception) {
        NSUncaughtExceptionHandler *handler = NSGetUncaughtExceptionHandler();
        if (handler) {
            handler(exception);
        }
    }
}

Discussion

HockeyApp uses PLCrashReporter for crash reporting. PLCrashReporter registers an NSUncaughtExceptionHandler (code). So the above code will forward the exception to the PLCrashReporter exception handler and terminates (code) the XPC.

Mattie suggest to @throw the exception again, to trigger the internal XPC @catch block and possible internal clean-up and logging. This is something to consider. Especially if you have a custom interruptionHandler on NSXPCConnection in the LaunchAgent/Server side of the connection!

For now I side with not throwing it again, because my XPC is complete stateless and should be fine just crashing.

The downside to Solution A is that every method exposed via XPC requires this @try @catch block.

Solution B:

Use a NSProxy that catches all unhandled exceptions as NSXPCConnection exportObject:

@interface LOUncaughtExceptionHandlerProxy : NSProxy {
    NSObject *_object;
}
@end

@implementation LOUncaughtExceptionHandlerProxy

- (instancetype)initWithObject:(NSObject *)object
{
    NSParameterAssert(object);
    _object = object;
    return self;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    return [_object methodSignatureForSelector:selector];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    @try {
        [invocation invokeWithTarget:_object];
    } @catch (NSException *exception) {
        NSUncaughtExceptionHandler *handler = NSGetUncaughtExceptionHandler();
        if (handler) {
            handler(exception);
        }
    }
}

@end

Setup within the NSXPCListener listener:

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
    XPC *exportedObject = [XPC new];
    LOUncaughtExceptionHandlerProxy *proxy = [[LOUncaughtExceptionHandlerProxy alloc] initWithObject:exportedObject];
    newConnection.exportedObject = proxy;
    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
    [newConnection resume];
    return YES;
}

All details of Solution A apply to Solution B.

Solution Z:

On macOS is possible to use the ExceptionHandling.framework, the problems with it are very well outlined in BITCrashExceptionApplication.h.

Discussion

It is never a good sign when a framework is not ported to iOS. Also note Matties comment:

I've had interactions with Apple that directly indicate that ExceptionHandling.framework is no longer supported. And, in my experience while working on Crashlytics, it had some fundamental interoperability issues beyond what is indicated in that quoted header.

catlan
  • 25,100
  • 8
  • 67
  • 78
  • Solution 1 is great, and 2 is very clever! I would, however, consider adding an `@throw` statement at the end of your `@catch` blocks. Right now, you are changing the runtime behavior - though that could be your intention. Also: I've had interactions with Apple that directly indicate that ExceptionHandling.framework is no longer supported. And, in my experience while working on Crashlytics, it had some fundamental interoperability issues beyond what is indicated in that quoted header. Though, I'm afraid I do not have those details recorded. – Mattie Mar 03 '19 at 14:32