Reputation: 2762
I have an app for Mac and iOS that uses .plist files to sync data between devices using iCloud. Every once in a while I get an email from someone that's getting repeated crashes on a specific device. (Other devices are fine.) In every case the app crashed while trying to read in a file from iCloud, using NSPropertyListSerialization to convert the data to a dictionary. In every case the problem is solved by having the user delete the iCloud data for the app. The same files get synced back in, and everything works fine again.
The specific example I'll use here is from the Mac version, but I've had almost identical crashes on iOS. From the crash report:
Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Codes: 0x000000000000000a, 0x0000000101ee2000
VM Regions Near 0x101ee2000:
VM_ALLOCATE 0000000101ee1000-0000000101ee2000 [ 4K] rw-/rwx SM=PRV
--> mapped file 0000000101ee2000-0000000101ee3000 [ 4K] r--/rwx SM=COW /Users/USER/Library/Mobile Documents/PB4R74AA4J~com~junecloud~Notefile/*/*.notefile
shared memory 0000000101ee3000-0000000101ee4000 [ 4K] rw-/rw- SM=SHM
And then:
Thread 7 Crashed:
0 libsystem_c.dylib 0x0000000105157013 bcmp + 19
1 com.apple.CoreFoundation 0x0000000101bbf4b0 __CFBinaryPlistGetTopLevelInfo + 80
2 com.apple.CoreFoundation 0x0000000101bbf36d __CFTryParseBinaryPlist + 93
3 com.apple.CoreFoundation 0x0000000101bbede2 _CFPropertyListCreateWithData + 146
4 com.apple.CoreFoundation 0x0000000101bcbdb0 CFPropertyListCreateWithData + 112
5 com.apple.Foundation 0x0000000101568b89 +[NSPropertyListSerialization propertyListWithData:options:format:error:] + 94
6 com.junecloud.Notefile-Helper 0x000000010107ad06 -[JUNNoteDocument readFromData:ofType:error:] + 64
7 com.apple.AppKit 0x000000010268d507 -[NSDocument readFromURL:ofType:error:] + 546
8 com.apple.AppKit 0x000000010228e3c8 -[NSDocument _initWithContentsOfURL:ofType:error:] + 135
9 com.apple.AppKit 0x000000010228e074 -[NSDocument initWithContentsOfURL:ofType:error:] + 262
10 com.junecloud.Notefile-Helper 0x000000010107cad8 -[JUNSyncDocument initWithFileURL:] + 213
11 com.junecloud.Notefile-Helper 0x0000000101079ec7 -[NotefileAppDelegate documentForURL:] + 68
12 com.junecloud.Notefile-Helper 0x00000001010825cb -[JUNSyncManager documentForURL:] + 76
13 com.junecloud.Notefile-Helper 0x000000010107d43c -[JUNSyncInOperation main] + 1340
14 com.apple.Foundation 0x0000000101563cd2 __NSThread__main__ + 1345
15 libsystem_c.dylib 0x00000001051697a2 _pthread_start + 327
16 libsystem_c.dylib 0x00000001051561e1 thread_start + 13
Thread 7 crashed with X86 Thread State (64-bit):
rax: 0x0000000000000062 rbx: 0x000000000000007b rcx: 0x000000010c2be868 rdx: 0x0000000000000007
rdi: 0x0000000101d1e151 rsi: 0x0000000101ee2000 rbp: 0x000000010c2be810 rsp: 0x000000010c2be7b8
r8: 0x000000010c2be870 r9: 0x0000000000000000 r10: 0x00007ff841c28900 r11: 0x00007ff841c186d8
r12: 0x000000010c2be870 r13: 0x0000000101ee2000 r14: 0x000000010c2beb00 r15: 0x000000010c2be980
rip: 0x0000000105157013 rfl: 0x0000000000010202 cr2: 0x0000000101ee2000
Logical CPU: 2
Here's the relevant bit of code where it's crashing:
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
BOOL success = NO;
NSDictionary *dictionary = nil;
NSError *error = nil;
id plist = [NSPropertyListSerialization propertyListWithData:data
options:NSPropertyListImmutable format:NULL error:&error];
if (plist && [plist isKindOfClass:[NSDictionary class]]) {
dictionary = plist;
} else {
NSLog(@"Error opening document: %@ %@",error,[error userInfo]);
if (outError != NULL) *outError = error;
}
// Then it does some things with the dictionary if it's not nil
return success;
}
Am I correct in thinking that NSPropertyListSerialization is just choking on some corrupt data, or is the problem more likely in my own code? If the problem is with NSPropertyListSerialization, is there anything I can do to prevent the app from crashing, and deal with the problem more appropriately?
If the problem is likely to be in my own code, what could I do to track down the cause? This would be much easier if I could duplicate the problem myself, but I've never seen this crash on my own devices, and obviously I can't expect a user to give me access to their iCloud account.
Update As requested, here's a bit of JUNSyncDocument
. On iOS this is a UIDocument subclass, on Mac it's an NSDocument subclass.
- (id)initWithFileURL:(NSURL *)url {
#if TARGET_OS_IPHONE
self = [super initWithFileURL:url];
return self;
#else
NSError *error = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
if ((self = [super initWithContentsOfURL:url ofType:[self documentType] error:&error])) {
self.fileURL = url;
self.hasUndoManager = NO;
} else NSLog(@"Error initializing existing document: %@ %@",url,error);
} else {
if ((self = [super initWithType:[self documentType] error:&error])) {
self.fileURL = url;
self.hasUndoManager = NO;
} else NSLog(@"Error initializing new document: %@",error);
}
return self;
#endif
}
This seems messy, and I wouldn't be surprised if I'm doing something stupid here. It works fine in most cases though. That gets called from NotefileAppDelegate
:
- (JUNSyncDocument *)documentForURL:(NSURL *)url {
JUNNoteDocument *document = [[JUNNoteDocument alloc] initWithFileURL:url];
return document;
}
Which in turn is called by JUNSyncManager
:
- (JUNSyncDocument *)documentForURL:(NSURL *)url {
return [self.delegate documentForURL:url];
}
Which is called by JUNSyncInOperation
:
JUNSyncDocument *document = [self.delegate documentForURL:self.url];
I just realized I can get rid of that ridiculous delegate chain by using NSClassFromString
, but I don't know if that will affect the problem or not.
Upvotes: 3
Views: 1092
Reputation: 334
I have the exact same problem, (UIDocument
& NSDocument
subclasses, crash when accessing the plist data), although I'm using NSFileWrapper
instead.
The mapped file path looks suspicious in your report (unless it's been anonymized by the OS), doesn't appear to be an actual file, but the query string. Isn't it that the NSMetadataQuery
is returning incorrect results?
I have no solution so far, still searching.
I could debug this problem with a customer, generating custom builds with logging information. Here are my observations:
NSPropertyListSerialization
, CGImageSourceCreateWithURL
, and possibly many other.It has the following status:
fileExists = YES;
isUbiquitous = YES;
hasNonZeroSize = YES;
isDownloaded = NO;
So it appears as a regular file to most APIs, yet it is not available. When accessed, it makes the app crash.
- the accessed file can be within a file package that has the isDownloaded = YES
, although the accessed file itself (within the file package) has isDownloaded = NO
.
Work around: check the isDownloaded
property for the file prior to accessing its contents. This property is checked using:
-[NSURL getResourceValue:&val forKey:NSURLUbiquitousItemIsDownloadedKey error:&error]
If like me you're using NSFileWrapper
to read the UIDocument
contents, then you need to have this check in:
-[UIDocument readFromURL:(NSURL *)url error:(NSError *__autoreleasing *)outError]
because the NSFileWrapper
won't give you access to the file package NSURL
that's needed.
I suspect a problem with the atomicity of iCloud when the document is created. It looks like the file package and its contents are not in sync for their downloaded status, just like if the document directory is made first, and its contents copied to it as 2 distinct operations.
Upvotes: 1
Reputation: 11594
Looking at the source of __CFTryParseBinaryPlist
and __CFBinaryPlistGetTopLevelInfo
, which are open source.
It looks like the memcmp (bcmp) that is crashing is at the very beginning where it checks first few bytes of the data for the binary plist header. It wouldn't get that far if CFDataGetLength
was <= 8
bytes, so it's not a buffer underflow. It's possible CFDataGetBytePtr
returned nil, but I don't see how that could happen if the length was > 8. Most likely, the data pointer has gone invalid.
I could tell more you posted the register contents from the crash report. Also, post the code of how it's creating the data (-[JUNSyncDocument initWithFileURL:]
and -[NotefileAppDelegate documentForURL:]
.)
Upvotes: 3