0

We are getting a KERN_INVALID_ADDRESS exception being thrown in our iOS application. The application is actually a Tamarin application but the exception is occurring within our PhoneGap library which is based on the code for PhoneGap version 2.1. The exception is only being reported on iPhone 6 and iPad Air 2 devices but not on other Apple devices.

Here is the symbolicated crash report:

Exception Type:  EXC_BAD_ACCESS (SIGABRT)
Exception Subtype: KERN_INVALID_ADDRESS at 0x004c004900000072
Triggered by Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib               0x000000019853b270 0x198520000 + 111216
1   libsystem_pthread.dylib              0x00000001985d916c 0x1985d4000 + 20844
2   libsystem_c.dylib                    0x00000001984b2b14 0x198450000 + 404244
3   Mobileforms                          0x0000000100799384 mono_handle_native_sigsegv (mini-exceptions.c:2386)
4   Mobileforms                          0x00000001007a61b0 mono_sigsegv_signal_handler (mini.c:6818)
5   libsystem_platform.dylib             0x00000001985d0958 0x1985cc000 + 18776
6   Mobileforms                          0x0000000100155104 -[CDVConnection getConnectionInfo:] (CDVConnection.m:36)
7   Mobileforms                          0x000000010014e948 -[NativeGapViewController execute:] (NativeGap.mm:205)
8   Mobileforms                          0x000000010014e6d4 -[NativeGapViewController executeCommandsFromJson:] (NativeGap.mm:158)
9   Mobileforms                          0x000000010014e58c -[NativeGapViewController flushCommandQueue] (NativeGap.mm:136)
10  Mobileforms                          0x000000010014e2c4 -[NativeGapViewController handleRequest:] (NativeGap.mm:78)

And here I am tracing through the code starting with line 136 in the flushCommandQueue method.

NativeGap.mm: 136

/**
 * Repeatedly fetches and executes the command queue until it is empty.
 */
- (void) flushCommandQueue
{
    UIWebView* webView = (UIWebView*)self.view ;
    [webView stringByEvaluatingJavaScriptFromString:
     @"cordova.commandQueueFlushing = true"];

    // Keep executing the command queue until no commands get executed.
    // This ensures that commands that are queued while executing other
    // commands are executed as well.
    int numExecutedCommands = 0;
    do {
        // Grab all the queued commands from the JS side.
        NSString* queuedCommandsJSON = [webView stringByEvaluatingJavaScriptFromString:
                                        @"cordova.require('cordova/plugin/ios/nativecomm')()"];
        numExecutedCommands = [self executeCommandsFromJson:queuedCommandsJSON]; // LINE 136
    } while (numExecutedCommands != 0);

    [webView stringByEvaluatingJavaScriptFromString:
     @"cordova.commandQueueFlushing = false"];
}

NativeGap.mm: 158

/**
 * Fetches the command queue and executes each command. It is possible that the
 * queue will not be empty after this function has completed since the executed
 * commands may have run callbacks which queued more commands.
 *
 * Returns the number of executed commands.
 */
- (int) executeCommandsFromJson:(NSString*)queuedCommandsJSON
{
    // Parse the returned JSON array.
    NSArray* queuedCommands = [queuedCommandsJSON cdvjk_mutableObjectFromJSONString];

    // Iterate over and execute all of the commands.
    for (NSArray* jsonEntry in queuedCommands) {
        CDVInvokedUrlCommand* command = [[CDVInvokedUrlCommand commandFromJson:jsonEntry] retain];
        if(![self.commandDelegate execute:command]) { // LINE 158
            DLog(@"FAILED pluginJSON = %@", commandString);
        }
        [command release];
    }
    return [queuedCommands count];
}

NativeGap.mm: 205

- (BOOL) execute:(CDVInvokedUrlCommand*)command
{
    ...

    // Find the proper selector to call.
    NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName];
    NSString* methodNameWithDict = [NSString stringWithFormat:@"%@:withDict:", command.methodName];
    SEL normalSelector = NSSelectorFromString(methodName);
    SEL legacySelector = NSSelectorFromString(methodNameWithDict);
    // Test for the legacy selector first in case they both exist.
    if ([obj respondsToSelector:legacySelector]) {
        NSMutableArray* arguments = nil;
        NSMutableDictionary* dict = nil;
        [command legacyArguments:&arguments andDict:&dict];
        //[obj performSelector:legacySelector withObject:arguments withObject:dict];
        objc_msgSend(obj,legacySelector,arguments,dict);
    } else if ([obj respondsToSelector:normalSelector]) {
        //[obj performSelector:normalSelector withObject:command];
        objc_msgSend(obj,normalSelector,command); // LINE 205

    } else {
        // There's no method to call, so throw an error.
        NSLog(@"ERROR: Method '%@' not defined in Plugin '%@'", methodName, command.className);
        retVal = NO;
    }

    return retVal;
}

CSVConnection.m: 36

- (void) getConnectionInfo:(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* result = nil;
    NSString* jsString = nil;
    NSString* callbackId = command.callbackId; // LINE 36 << Exception 

    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:self.connectionType];
    jsString = [result toSuccessCallbackString:callbackId];
    [self writeJavascript:jsString];
}

The root of the problem seems to me to be in the method executeCommandsFromJson (NativeGap.mm: 158) and specifically this code:

CDVInvokedUrlCommand* command = [[CDVInvokedUrlCommand commandFromJson:jsonEntry] retain];
if(![self.commandDelegate execute:command]) { // LINE 158
    DLog(@"FAILED pluginJSON = %@", commandString);
}
[command release];

It seems to me that this CDVInvokedUrlCommand instance that is created is somehow getting released prematurely and is there already disposed when it reaches this line in CSVConnection:

NSString* callbackId = command.callbackId; // LINE 36

Here is the relevant section where the CDVInvokedUrlCommand instance is created:

+ (CDVInvokedUrlCommand*) commandFromJson:(NSArray*)jsonEntry
{
    return [[[CDVInvokedUrlCommand alloc] initFromJson:jsonEntry] autorelease];
}

- (id) initFromJson:(NSArray*)jsonEntry
{
    id tmp = [jsonEntry objectAtIndex:0];
    NSString* callbackId = tmp == [NSNull null] ? nil : tmp;
    NSString* className = [jsonEntry objectAtIndex:1];
    NSString* methodName = [jsonEntry objectAtIndex:2];
    NSMutableArray* arguments = [jsonEntry objectAtIndex:3];

    return [self initWithArguments:arguments
                        callbackId:callbackId
                         className:className
                        methodName:methodName];
}

Does anyone have any idea why this exception is occurring on iPhone 6 and how we could go about resolving it?

BruceHill
  • 6,954
  • 8
  • 62
  • 114

1 Answers1

0

In case it helps someone else, here is the solution to this problem that I took days to hunt down. The crash occurred only on devices with 64 bit architectures. This line in NativeGap.mm:

objc_msgSend(obj,normalSelector,command); // LINE 205

needed to change to the following:

((void(*)(id, SEL, id))objc_msgSend)(obj,normalSelector,command); // LINE 205

More information on this problem can be found over here.

Community
  • 1
  • 1
BruceHill
  • 6,954
  • 8
  • 62
  • 114