-[NSKeyedUnarchiver initForReadingWithData:] returns __NSInvalidKeyedUnarchiver instead of nil when reading invalid archive file

Originator:mail.junjie
Number:rdar://33788799 Date Originated:August 9 2017, 8:09 AM
Status: Resolved:
Product:iOS + SDK / Foundation Product Version:11.0 (15A5341f)
Classification:Serious Bug Reproducible:Always
 
Area:
Foundation

Summary:
According to the documentation for this method, -[NSKeyedUnarchiver initForReadingWithData:] throws an exception when data is an invalid archive file (https://developer.apple.com/documentation/foundation/nskeyedunarchiver/1410862-initforreadingwithdata). To find out whether a keyed archive file is valid, we would use @try/@catch to catch an exception, and handle it accordingly.

This seems to be the case in early version of iOS. But since iOS 9 (or even earlier?), it no longer throws an exception when data is an invalid archive. However, the method returns nil, which makes it easy to identify an invalid archive file.

In iOS 11 Beta 5, and macOS High Sierra Beta 5, this method (1) does not throw any exception, and (2) returns a private subclass __NSInvalidKeyedUnarchiver instead of nil when reading invalid archive file.

As you can imagine, there is no proper way to determine if a data file is a valid archive file anymore. Worse, __NSInvalidKeyedUnarchiver continues to respond to methods such as decodeObjectForKey:, making the app thinks that is has loaded a proper file when in fact it hasn't.

This results in app depending on -initForReadingWithData: returning nil to identify invalid databases to fail in iOS Beta 5 and macOS High Sierra Beta 5.

Steps to Reproduce:
1. See sample code, or…
2. Load an invalid keyed archive file, perhaps a JSON file?
NSData *data = [[NSData alloc] initWithContentsOfFile:pathToInvalidKeyedArchiveFile];
3. Attempt to unarchive the file using NSKeyedUnarchiver, with @try/@catch blocks:

	NSKeyedUnarchiver *unarchiver = nil;
	
	@try {
		NSLog(@"Attempting to unarchive data…");
		unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];		
		NSLog(@"… success!");
	}
	
	@catch (NSException *exception) {
		NSLog(@"… failed to unarchive data: %@%@%@", unarchiver, [exception name], [exception reason]);
		return nil;
	}

Expected Results:
1. initForReadingWithData: should throw an exception, as per documentation
2. Failing which, it should at least return nil so we can check for an invalid archive

Observed Results:
1. No exception is thrown
2. A private subclass __NSInvalidKeyedUnarchiver is returned, which responds to methods like a valid NSKeyedUnarchiver does, misleading the app into thinking that it has loaded a proper keyed archive that just has no data.

Version:
11.0 (15A5341f)

Notes:


Configuration:
This bug also affects macOS High Sierra Beta 5

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!