Bug Report
I have a few crashes on iOS. This is the crash from Crashalytics: [CDVSound audioFileForResource:withId:doValidation:forRecording:suppressValidationErrors:]
Problem
I have a few different stack traces that leads to the same crash. For example:
Fatal exception: com.apple.root.default-qos
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x316e72403b08646b
Crashed: com.apple.root.default-qos
0 libobjc.A.dylib 0x3008 objc_msgSend + 8
1 CoreFoundation 0x3918 -[__NSDictionaryM objectForKey:] + 168
2 Azmar 0x19d6c -[CDVSound audioFileForResource:withId:doValidation:forRecording:suppressValidationErrors:] + 224 (CDVSound.m:224)
3 Azmar 0x1a908 __30-[CDVSound startPlayingAudio:]_block_invoke + 416 (CDVSound.m:416)
4 libdispatch.dylib 0x1aac _dispatch_call_block_and_release + 32
5 libdispatch.dylib 0x1b584 _dispatch_client_callout + 16
6 libdispatch.dylib 0x3725c _dispatch_queue_override_invoke.cold.3 + 32
7 libdispatch.dylib 0x61f8 _dispatch_queue_override_invoke + 848
8 libdispatch.dylib 0x13db0 _dispatch_root_queue_drain + 364
9 libdispatch.dylib 0x1454c _dispatch_worker_thread2 + 156
10 libsystem_pthread.dylib 0x4624 _pthread_wqthread + 232
11 libsystem_pthread.dylib 0x19f8 start_wqthread + 8
and also:
Fatal exception: com.apple.root.default-qos
EXC_BREAKPOINT 0x0000000197cb6fa8
Crashed: com.apple.root.default-qos
0 CoreFoundation 0x3fa8 -[__NSCFString isEqual:] + 248
1 CoreFoundation 0x3918 -[__NSDictionaryM objectForKey:] + 168
2 Azmar 0x19d6c -[CDVSound audioFileForResource:withId:doValidation:forRecording:suppressValidationErrors:] + 224 (CDVSound.m:224)
3 Azmar 0x1a908 __30-[CDVSound startPlayingAudio:]_block_invoke + 416 (CDVSound.m:416)
4 libdispatch.dylib 0x1aac _dispatch_call_block_and_release + 32
5 libdispatch.dylib 0x1b584 _dispatch_client_callout + 16
6 libdispatch.dylib 0x3725c _dispatch_queue_override_invoke.cold.3 + 32
7 libdispatch.dylib 0x61f8 _dispatch_queue_override_invoke + 848
8 libdispatch.dylib 0x13db0 _dispatch_root_queue_drain + 364
9 libdispatch.dylib 0x1454c _dispatch_worker_thread2 + 156
10 libsystem_pthread.dylib 0x4624 _pthread_wqthread + 232
11 libsystem_pthread.dylib 0x19f8 start_wqthread + 8
What is expected to happen?
The app shouldn't crash.
What does actually happen?
The app crashes. Maybe it's a race condition because I may load multiple sounds or stop multiple musics? Who knows.
Information
I may call many times the stop function for different musics, and also load quite a few musics and sounds, since my app is a game.
I asked Gemini for a solution, and he mostly suggested me to add a @synchronized(self) { block in the audioFileForResource, which is the function that crashes, not sure how it's useful though:
- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId doValidation:(BOOL)bValidate forRecording:(BOOL)bRecord suppressValidationErrors:(BOOL)bSuppress
{
BOOL bError = NO;
CDVMediaError errcode = MEDIA_ERR_NONE_SUPPORTED;
NSString* errMsg = @"";
CDVAudioFile* audioFile = nil;
NSURL* resourceURL = nil;
// --- BEGIN SIMPLE WORKAROUND ---
// 1. Basic check: Ensure mediaId is actually a string before using it.
// This might not be the cause, but it's simple defensive coding.
if (![mediaId isKindOfClass:[NSString class]]) {
NSLog(@"CDVSound Error: Received non-string mediaId: %@", mediaId);
bError = YES;
errcode = MEDIA_ERR_ABORTED;
errMsg = @"Internal error: Invalid media ID type";
// Go straight to error reporting
goto HandleError;
}
// 2. Synchronize only the block of code that accesses the shared soundCache dictionary.
@synchronized(self) {
if ([self soundCache] == nil) {
// Initialize the cache if it doesn't exist (thread-safe initialization)
[self setSoundCache:[NSMutableDictionary dictionaryWithCapacity:1]];
} else {
// Safely attempt to retrieve the object from the cache
audioFile = [[self soundCache] objectForKey:mediaId]; // Line ~224 - Now wrapped
}
// If not found in cache, create and add it (still within the synchronized block)
if (audioFile == nil) {
// Basic validation of resourcePath (can stay inside for simplicity)
if ((resourcePath == nil) || ![resourcePath isKindOfClass:[NSString class]] || [resourcePath isEqualToString:@""]) {
bError = YES; // Mark error, handle after sync block
errcode = MEDIA_ERR_ABORTED;
errMsg = @"invalid media src argument";
} else {
// Create and add the new audioFile object while synchronized
audioFile = [[CDVAudioFile alloc] init];
audioFile.resourcePath = resourcePath;
audioFile.resourceURL = nil; // Will be validated later
[[self soundCache] setObject:audioFile forKey:mediaId];
}
}
} // --- END Synchronization Block ---
// --- END SIMPLE WORKAROUND ---
// If an error occurred during creation inside the sync block, handle it now
if (bError) {
goto HandleError;
}
// Proceed with URL validation if needed (outside the synchronized block)
// Note: audioFile might be nil here if resourcePath validation failed inside the sync block.
// However, the bError flag should catch that. We rely on audioFile being non-nil if bError is NO.
if (bValidate && (audioFile.resourceURL == nil)) {
if (bRecord) {
resourceURL = [self urlForRecording:resourcePath];
} else {
resourceURL = [self urlForPlaying:resourcePath];
}
if ((resourceURL == nil) && !bSuppress) {
bError = YES;
errcode = MEDIA_ERR_ABORTED;
errMsg = [NSString stringWithFormat:@"Cannot use audio file from resource '%@'", resourcePath];
} else {
// It's generally safe to set properties on the audioFile object itself here,
// as the object reference `audioFile` is local to this function execution.
// The risk would be if another thread `release`d this specific mediaId
// *between* the synchronized block above and this line.
// The simplest approach assumes this is less likely than the dictionary corruption.
audioFile.resourceURL = resourceURL;
}
}
HandleError: // Common error handling point
if (bError) {
// Use the validated mediaId (or a placeholder if it was invalid) for the error callback
NSString* errorMediaId = [mediaId isKindOfClass:[NSString class]] ? mediaId : @"<invalid_id>";
[self onStatus:MEDIA_ERROR mediaId:errorMediaId param:
[self createMediaErrorWithCode:errcode message:errMsg]];
// Return nil upon error
return nil;
}
// Return the found or created audioFile object
return audioFile;
}
Command or Code
Environment, Platform, Device
Most iPhones ranging from iPhone 11 to iPhone 16, 20% iOS 17 and 80% iOS 18.
Version information
"cordova": "^12.0.0",
"cordova-ios": "^7.1.1",
Checklist
Bug Report
I have a few crashes on iOS. This is the crash from Crashalytics:
[CDVSound audioFileForResource:withId:doValidation:forRecording:suppressValidationErrors:]Problem
I have a few different stack traces that leads to the same crash. For example:
Fatal exception: com.apple.root.default-qos
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x316e72403b08646b
and also:
Fatal exception: com.apple.root.default-qos
EXC_BREAKPOINT 0x0000000197cb6fa8
What is expected to happen?
The app shouldn't crash.
What does actually happen?
The app crashes. Maybe it's a race condition because I may load multiple sounds or stop multiple musics? Who knows.
Information
I may call many times the stop function for different musics, and also load quite a few musics and sounds, since my app is a game.
I asked Gemini for a solution, and he mostly suggested me to add a
@synchronized(self) {block in theaudioFileForResource, which is the function that crashes, not sure how it's useful though:Command or Code
Environment, Platform, Device
Most iPhones ranging from iPhone 11 to iPhone 16, 20% iOS 17 and 80% iOS 18.
Version information
Checklist