xmlhack
xmlhack

Reputation: 61

Obj-C exception raised from libxml error handler callback function (Obj-C++) is not caught in Obj-C

I've created a libxml error handler in an Obj-C MyStreamHandler.mm class file and assigned it to the stream parser using xmlTextReaderSetErrorHandler. In the error handler I am raising an Obj-C exception using [NSException raise:format:]. However the exception is not being caught within my Obj-C class in it's @try/@catch block.

The callback is triggered when invalid xml is encountered (such ass </foo</bar>) is encountered within libxml. The error handler is being called that much is certain, and the NSException is also known to be raised. However the exception is not being caught in my Obj-C class that is the client of libxml.

MyStreamHandler.mm

#import <libxml/xmlreader.h>

#import "MyStreamHandler.h"

void MyErrorCallback(void *arg, const char *msg, xmlParserSeverities severity, xmlTextReaderLocatorPtr locator);

@implementation MyStreamHandler

-(void)readStream:(xmlTextReaderPtr)streamAPI
{
    xmlTextReaderSetErrorHandler(streamAPI, (xmlTextReaderErrorFunc)MyErrorCallback, NULL);

    @try
    {
        while (xmlTextReaderRead(streamAPI) == 1)
        {
            // Loop through the XML until the malformed XML is hit.
        }
    }
    @catch (id e)
    {
        NSLog(e);
    }
}

@end

void MyErrorCallback(void *arg, const char *msg, xmlParserSeverities severity, xmlTextReaderLocatorPtr locator)
{
   [NSException raise:@"MyException" format:@"Something bad happened: %s",msg];
}

Here's a sample XML file that would trigger this callback and exception:

<foo><bar</foo>

Here's the associated call stack below caused by the uncaught exception:

*** Terminating app due to uncaught exception 'MyException', reason: 'Something bad happened: Opening and ending tag mismatch: content line 0 and container
'

*** Call stack at first throw:
(_NSCallStackArray*)0x10301a40; count=33 {
0   CoreFoundation                      0x04324be9 __exceptionPreprocess + 185
1   libobjc.A.dylib                     0x044795c2 objc_exception_throw + 47
2   CoreFoundation                      0x042dd628 +[NSException raise:format:arguments:] + 136
3   CoreFoundation                      0x042dd59a +[NSException raise:format:] + 58
4   MyApp                               0x00ee1b85 _Z25MyErrorCallbackPvPKc19xmlParserSeveritiesS_ + 319
5   libxml2.2.dylib                     0x0401766c xmlTextReaderStandalone + 105
6   libxml2.2.dylib                     0x04018793 xmlTextReaderSetErrorHandler + 838
7   libxml2.2.dylib                     0x03f69c78 __xmlRaiseError + 1222
8   libxml2.2.dylib                     0x03f6d564 namePush + 1283
9   libxml2.2.dylib                     0x03f75e2c xmlParseXMLDecl + 1291
10  libxml2.2.dylib                     0x03f80b6d xmlParseChunk + 3984
11  libxml2.2.dylib                     0x0401a255 xmlTextReaderGetAttribute + 816
12  libxml2.2.dylib                     0x0401bb34 xmlTextReaderRead + 441
13  MyApp                               0x00ec2919 +[MyStreamHandler readStream:]
...
Application call stack
...
24  CoreFoundation                      0x0429567d __invoking___ + 29
25  CoreFoundation                      0x04295551 -[NSInvocation invoke] + 145
26  Foundation                          0x027bd555 -[NSInvocationOperation main] + 51
27  Foundation                          0x0272bbd2 -[__NSOperationInternal start] + 747
28  Foundation                          0x0272b826 ____startOperations_block_invoke_2 + 106
29  libSystem.B.dylib                   0x925a0024 _dispatch_call_block_and_release + 16
30  libSystem.B.dylib                   0x925922f2 _dispatch_worker_thread2 + 228
31  libSystem.B.dylib                   0x92591d81 _pthread_wqthread + 390
32  libSystem.B.dylib                   0x92591bc6 start_wqthread + 30
}
terminate called after throwing an instance of 'NSException'

Is this totally bogus? Am I able to throw a Obj-C exception from within an Obj-C++ function and handle that via a normal @try/@catch block in my Obj-C code? Or do I need to raise an actual Obj-C++ exception with it's own Obj-C++ try/catch block?

Any help would be much appreciated.

Upvotes: 0

Views: 808

Answers (2)

JeremyP
JeremyP

Reputation: 86651

I'm not a great fan of Apple's advice on exceptions since I think the exception paradigm leads to cleaner code generally since you don't have to litter it with extra NSError** parameters and you don't have to set aside special return values to indicate an error.

However you absolutely cannot allow Objective-C exceptions to propagate across code that is not expecting them. This is because quite a lot of code is not written to deal with Objective-C exceptions. libxml2 for instance will almost certainly not unwind its stack properly leading to leaks and other nastiness.

I have no idea why your exception is not caught by your handler (maybe you need to @throw it) but that is irrelevant. You can't use them in this situation.

What you should do in your call back is merely note that you have had an error, maybe log or record it and then return normally. If the error is fatal, libxml2 will know how to terminate without you helping it.

Upvotes: 0

Chris Hanson
Chris Hanson

Reputation: 55096

Exceptions in Cocoa and Cocoa Touch are reserved for programmer errors only; you should not expect to use them in production code as shipped to end users.

You also can't generally expect to catch an exception thrown across an API boundary, only in situations where you control all of the intermediate stack frames. Thus in the example you give, you're throwing an exception out of code that doesn't have any knowledge of Objective-C exceptions, and thus may not be left in a valid state.

You'll have to have your error handler pass the error information out to your higher-level code in some other fashion. That's typically why callback and callback-registration APIs have a void * argument (such as the final argument of xmlTextReaderSetErrorHandler, which becomes the first argument of the handler function) - it allows you to associate some context with your callback so you can do something like determine which of multiple possible documents you may be reading have triggered the error.

Upvotes: 1

Related Questions