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?