XPC and Exception handling - objective-c

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.

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.

Related

Swift Catch Runtime Exceptions

I'm running fuzz testing on an application, and so am looking specifically for runtime errors that aren't handled. The application is written in both ObjC and Swift, but the unit tests are written in Swift.
I understand the basis of swift isn't to catch arbitrary runtime exceptions, but this is purely for unit tests. How do I catch runtime these exceptions (i.e. index out of bounds etc.)
To catch Obj-C exceptions in Swift, I am using a simple Obj-C class:
#import "ObjC2Swift.h"
#implementation ObjC
+ (id)catchException:(id(^)())tryBlock error:(__autoreleasing NSError **)error {
#try {
id result = tryBlock();
return result;
}
#catch (NSException *exception) {
if (error) {
*error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo];
}
return nil;
}
}
#end
In Swift called as
let result = try? ObjC.catchException { ... dangerous code here ... }
You might need a different variant for blocks that don't return anything.
Not to be abused. Obj-C exception are evil and I am using this only because I need a library that uses them.

How to debug communication between XPC service and client app in OSX

I'm trying to write a simple pair of "client app" & "XPC service". I was able to launch xpc service from client (i.e I can see service running in the Activity monitor processes list), but when I try to send any request, that has a response block, I get an error: "Couldn’t communicate with a helper application."
The worst thing here is that error doesn't give me any info about what went wrong. And I'm also unable to debug the service properly. As I understand, the correct way to do this is to attach a debugger to process (Debug->Attach to process, also see here). I have both client and service projects in a single workspace.
When I run client from xcode and try to attach debugger to launched service, that ends with a "Could not attach to pid : X" error.
If I archive the client app run it from app file and then try to attach debugger to service the result is the same.
The only way to record something from the service I could imagine is to write a logger class, that would write data to some file. Haven't tried this approach yet, however that looks insane to me.
So my question is:
a) How to find out what went wrong, when receiving such non-informative response like: "Couldn’t communicate with a helper application"?
b) And also, what's the correct way to debug the xpc service in the first place? The link above is 5 years old from now, however I can see that some people were saying that "attach to debugger" wasn't working.
The code itself is fairly simple:
XPC service, listener implementation:
#import "ProcessorListener.h"
#implementation ProcessorListener
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
[newConnection setExportedInterface: [NSXPCInterface interfaceWithProtocol:#protocol(TestServiceProtocol)]];
[newConnection setExportedObject: self];
self.xpcConnection = newConnection;
newConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol: #protocol(Progress)];
// connections start suspended by default, so resume and start receiving them
[newConnection resume];
return YES;
}
- (void) sendMessageWithResponse:(NSString *)receivedString reply:(void (^)(NSString *))reply
{
reply = #"This is a response";
}
- (void) sendMessageWithNoResponse:(NSString *)mString
{
// no response here, dummy method
NSLog(#"%#", mString);
}
And the main file for service:
#import <Foundation/Foundation.h>
#import "TestService.h"
#interface ServiceDelegate : NSObject <NSXPCListenerDelegate>
#end
#implementation ServiceDelegate
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
// This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection.
// Configure the connection.
// First, set the interface that the exported object implements.
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(TestServiceProtocol)];
// Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to the exported object to handle. The connection retains the exported object.
TestService *exportedObject = [TestService new];
newConnection.exportedObject = exportedObject;
// Resuming the connection allows the system to deliver more incoming messages.
[newConnection resume];
// Returning YES from this method tells the system that you have accepted this connection. If you want to reject the connection for some reason, call -invalidate on the connection and return NO.
return YES;
}
#end
int main(int argc, const char *argv[])
{
// [NSThread sleepForTimeInterval:10.0];
// Create the delegate for the service.
ServiceDelegate *delegate = [ServiceDelegate new];
// Set up the one NSXPCListener for this service. It will handle all incoming connections.
NSXPCListener *listener = [NSXPCListener serviceListener];
listener.delegate = delegate;
// Resuming the serviceListener starts this service. This method does not return.
[listener resume];
return 0;
}
For client app, the UI contains a bunch of buttons:
- (IBAction)buttonSendMessageTap:(id)sender {
if ([daemonController running])
{
[self executeRemoteProcessWithName:#"NoResponse"];
}
else
{
[[self.labelMessageResult cell] setTitle: #"Error"];
}
}
- (IBAction)buttonSendMessage2:(id)sender {
if ([daemonController running])
{
[self executeRemoteProcessWithName:#"WithResponse"];
}
else
{
[[self.labelMessageResult cell] setTitle: #"Error"];
}
}
- (void) executeRemoteProcessWithName: (NSString*) processName
{
// Create connection
NSXPCInterface * myCookieInterface = [NSXPCInterface interfaceWithProtocol: #protocol(Processor)];
NSXPCConnection * connection = [[NSXPCConnection alloc] initWithServiceName: #"bunldeID"]; // there's a correct bundle id there, really
[connection setRemoteObjectInterface: myCookieInterface];
connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(Progress)];
connection.exportedObject = self;
[connection resume];
// NOTE that this error handling code is not called, when debugging client, i.e connection seems to be established
id<Processor> theProcessor = [connection remoteObjectProxyWithErrorHandler:^(NSError *err)
{
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle: #"OK"];
[alert setMessageText: err.localizedDescription];
[alert setAlertStyle: NSAlertStyleWarning];
[alert performSelectorOnMainThread: #selector(runModal) withObject: nil waitUntilDone: YES];
}];
if ([processName containsString:#"NoResponse"])
{
[theProcessor sendMessageWithNoResponse:#"message"];
}
else if ([processName containsString:#"WithResponse"])
{
[theProcessor sendMessageWithResponse:#"message" reply:^(NSString* replyString)
{
[[self.labelMessageResult cell] setTitle: replyString];
}];
}
}
Jonathan Levin's XPoCe tool is helpful when you can't attach a debugger.
You can add logging NSLog() or fprintf(stderr,...) to your service and clients, specifically around the status codes. You just have to specify the path of the file to write stdout and stderr. <key>StandardErrorPath</key> <string>/tmp/mystderr.log</string>
There's a section on Debugging Daemons at this article on objc.io .

Xcode debugger can't catch exception if finally block returns

I discovered weird behavior of Xcode.
Xcode debugger doesn't break for uncaught exception in this code.
#try { #throw #"AA"; }
#catch (...) { #throw; }
#finally { return; }
But exception in this code caught and trigger Xcode break execution for debugging.
#try { #throw #"AA"; }
#catch (...) { #throw; }
#finally { }
If #finally block returns debugger can't catch the exception. Have you ever seen this problem? I'm not sure this is really an issue. By the perspective it looks like designed behavior. I don't know why. Shouldn't I return in #finally block? My problem is it swallows exception silently, so I can't detect it.
Shame on me, I don't know well try...catch...finally behaviors. I almost haven't used exception catching code. Is this designed behavior or buggy behavior? Is this any known issue?
Here's my environment.
Xcode Version 4.4 (4F250)
OS X 10.7.4
Edit
I attach full test source code.
#import <Foundation/Foundation.h>
int main (int a, char** b)
{
#try
{
NSLog(#"trying something...");
#try { #throw #"AA"; }
#catch (...) { #throw; }
#finally { return 0; }
}
#catch (...)
{
NSLog(#"something catched.");
}
#finally
{
NSLog(#"finally...");
}
}
Putting a return in a #finally block seems like a bad idea. The exception handling mechanism is going to try to unwind the call stack as it deals with the exception you're throwing. If a #finally block changes what's on the stack, you undermine the exception handler. It doesn't seem at all surprising that this crashes.
Also, as bbum pointed out, exceptions aren't used for flow control in Cocoa and Cocoa Touch. Throwing an exception through a Cocoa method usually fails. Even if what you're doing is supposed to work in generic Objective-C, it would probably still cause problems in real code.
Short answer: Don't do that.

Error propagatin inside try/catch in cocoa

I want to raise an exception inside a catch block of a method and handle it in catch of another method from where earlier method was called.
I tried this logic-
method B()
{
#try{
.......
// calling method where is probable chance of exception
method A();
}
catch(NSException e)
{
//catching the exception thrown in the method B()
NSString* theError=[e reason];
NSLog(#"the error is == %#",theError);
}
}
method A()
{
#try{
.............
//throw an exception incase of some condition
throw e;
}
catch(NSException e)
{
//rethrowing the exception, want to catch in the method from where this method is called.
throw e;
}
}
But the catch block of method B() is never accessible.
The control never returns to the catch block of method B().
Please suggest.
Thanks,
Sudhansu
Here is a bit of code. I am calling a method (populateData) of TableController from MyController.
The exception supposed to occur in another method of TableController(initializeTest) and i am throwing it
inside try block of FinderCompleted method. In the catch block of same method , rethrowing the exception, as
I don't want to handle it here.
The control is restricted only to the innermost catch block of method - (void)FinderCompleted:(id)args
the NSLog prints like this-
m here 1
Inside the error
You got nothing in ur bucket :D
Throwing exception for the 1st time
Gotcha--1st time exception , throwing it 2nd time
the error is == Something unexpected happened --EXCEPTION
After that i don't know where the control is going. I want the control to go catch block of the outer method which calls FinderCompleted method,
and print the other logs like-
Gotcha--2st time exception, throwing it 3rd time
Gotcha--3rd time exception
Throwing exception for the 4th time
Gotcha--4th time exception
the error is Something unexpected happened --EXCEPTION
in MyController.m
- (IBAction)fetchResults:(id)sender
{
NSArray *tableColumnArray = ...............;//some values initialized
NSArray *identifierArray = ................;//some values initialized
NSArray *bindVariableArray = ................;//some values initialized
TableController *pTC = [[TableController alloc] init];
#try
{
[pTC populateData :tableColumnArray :identifierArray :bindVariableArray];// calling populate DataForMD method defined in TableController class
[pTC release];
}
#catch (NSException * e)
{
NSLog(#"Gotcha--4th time exception");
//want to handle the exception here
NSString* theError=[e reason];
NSLog(#"the error is %#",theError);
}
}
in TableController.m
-(void)populateData:(NSArray *)tableColumnArray:(NSArray *)identifierArray:(NSArray *)bindVariableArray
{
[self setTableColumnArray:tableColumnArray];
[self setColumnIdentifierArray:identifierArray];
[self setBindVarArray:bindVariableArray];
#try
{
NSLog(#"m here 1");
[self initializeTest];// calling initializeTest method
}
#catch (NSException * e)
{
//Do not want to handle it here
NSLog(#"Gotcha--3rd time exception");
NSLog(#"Throwing exception for the 4th time");
#throw e;
}
}
-(void)initializeTest
{
#try
{
ISTQuery* theQuery = (ISTQuery*)[ISTQueryGenerator getQueryByName:[self queryClassName]];
..........
...........//some loc here
[theQuery run];
.................//some loc here
if(theQuery)
{
//Calling FinderCompleted method
//supposed to get error here
[[self modelFinder] startWithRecipient:self andNotificationSelector:#selector(FinderCompleted:)];
}
}
#catch(NSException *e)
{
NSLog(#"Gotcha--2st time exception, throwing it 3rd time");
//Do not want to handle it here
#throw e; // rethrows e implicitly
}
}
- (void)FinderCompleted:(id)args
{
#try
{ //getting some error while back-end transaction
NSString* theError = [ISTModelFinder errorMessageFromFinderArgs:args];
if (theError)
{
NSLog(#"Inside the error");
NSLog(#"You got nothing in ur bucket :D");
NSException *e = [NSException
exceptionWithName:#"InternalErrorException"
reason:#"Something unexpected happened --EXCEPTION"
userInfo:nil];
NSLog(#"Throwing exception for the 1st time");
#throw e;
}
else
{
//do sth else
}
}
#catch(NSException *e)
{
NSLog(#"Gotcha--1st time exception , throwing it 2nd time");
NSString* theError=[e reason];
//Do not want to handle it here
NSLog(#"the error is == %#",theError);
#throw e; // rethrows e implicitly
}
}
You cannot use exceptions for flow control in Cocoa or iOS programming. Exceptions are purely for identifying unrecoverable errors and, typically, the program crashes purposefully very shortly thereafter. (With a handful of exceptions to this rule, most of which have bugs filed against them to deprecate and eliminate the related API.)
Use the NSError pattern to manage user recoverable errors.
It isn't clear why your code isn't working. But that doesn't look like real code. What have you tried?
I had added the code snippet, please suggest how to achieve that
functionality.
The first thing you do is do not use exceptions.
Use the NSError pattern. You will need to refactor your code to do so. It is worth it.
See the documentation on error handling in cocoa.

Fail to catch exception from proxy object under Xcode 3.2.3

I use HessianKit to communicate with server. In the situation of network or server down Hessian will throw exception, so I put every Hessian call in a #try ... #catch block. Everything worked fine until I upgraded Xcode from 3.2.2 to 3.2.3. I wrote the some testing code and found under Xcode 3.2.3, catch exception would be failed if the exception was thrown from a proxy object.
MyProxy.h:
#interface MyProxy : NSProxy {
}
#end
MyProxy.m:
#implementation MyProxy
- (id)init {
return self;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
NSLog(#"Call method %#", NSStringFromSelector([invocation selector]));
[NSException raise:#"MyException" format:#"this is an exception"];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
.....
}
#end
Code using MyProxy:
#try {
MyProxy *p = [[MyProxy alloc] init];
[p doSomething];
[p release];
}
#catch (NSException * e) {
NSLog(#"%#", e);
}
When these code build under xcode 3.2.2, the exception can be catched correctly. But under xcode 3.2.3, the program terminated after output following on the console:
2010-09-08 21:09:29.877 BriefCase[34651:40b] Call method doSomgthing
2010-09-08 21:09:29.879 BriefCase[34651:40b] *** Terminating app due to uncaught exception 'MyException', reason: 'this is an exception'
2010-09-08 21:09:29.880 BriefCase[34651:40b] Stack: (
45955152,
47113004,
45692683,
45692522,
151932,
45426420,
45423090,
9352,
4417860,
4421967,
4447550,
4429047,
4461016,
53399932,
45234332,
45230248,
4420129,
4453234,
8812,
8666
)
terminate called after throwing an instance of 'NSException'
Program received signal: “SIGABRT”.
What can I do?
I filed a bug with Apple, and the reply is:
It has been determined that this is a known issue, which is currently being investigated by engineering. This issue has been filed in our bug database under the original Bug ID# 7995323.
Maybe your project/target/executable settings have been messed up?
Is the "Enable Objective-C Exceptions" box ticked for your configuration/target/etc?
If it is, maybe you should file a bug with Apple here.