Handle bad JSON data without crashing
Summary: public NSJSONSerialization throws an exception when it encounters bad JSON data, including NaN values, which may not be a programming error. This diff adds code to catch those exceptions and convert to an error. Also, if no error handling is in place, RCTJSONStringify will now display a redbox, and attempt to recover by sanitizing the JSON data and retrying. Reviewed By: javache Differential Revision: D2854778 fb-gh-sync-id: 18e6990af0d91083496d6a0b75c31a94ed9454a5
This commit is contained in:
parent
1edcf4c6ac
commit
7419a82bd7
|
@ -78,4 +78,35 @@
|
|||
XCTAssertEqualObjects(obj, RCTJSONParse(json, NULL));
|
||||
}
|
||||
|
||||
- (void)testNotJSONSerializable
|
||||
{
|
||||
NSDictionary<NSString *, id> *obj = @{@"foo": [NSDate date]};
|
||||
NSString *json = @"{\"foo\":null}";
|
||||
XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL));
|
||||
}
|
||||
|
||||
- (void)testNaN
|
||||
{
|
||||
NSDictionary<NSString *, id> *obj = @{@"foo": @(NAN)};
|
||||
NSString *json = @"{\"foo\":0}";
|
||||
XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL));
|
||||
}
|
||||
|
||||
- (void)testNotUTF8Convertible
|
||||
{
|
||||
//see https://gist.github.com/0xced/56035d2f57254cf518b5
|
||||
NSString *string = [[NSString alloc] initWithBytes:"\xd8\x00" length:2 encoding:NSUTF16StringEncoding];
|
||||
NSDictionary<NSString *, id> *obj = @{@"foo": string};
|
||||
NSString *json = @"{\"foo\":null}";
|
||||
XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL));
|
||||
}
|
||||
|
||||
- (void)testErrorPointer
|
||||
{
|
||||
NSDictionary<NSString *, id> *obj = @{@"foo": [NSDate date]};
|
||||
NSError *error;
|
||||
XCTAssertNil(RCTJSONStringify(obj, &error));
|
||||
XCTAssertNotNil(error);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -23,7 +23,7 @@ RCT_EXTERN NSString *__nullable RCTJSONStringify(id __nullable jsonObject, NSErr
|
|||
RCT_EXTERN id __nullable RCTJSONParse(NSString *__nullable jsonString, NSError **error);
|
||||
RCT_EXTERN id __nullable RCTJSONParseMutable(NSString *__nullable jsonString, NSError **error);
|
||||
|
||||
// Strip non JSON-safe values from an object graph
|
||||
// Santize a JSON string by stripping invalid objects and/or NaN values
|
||||
RCT_EXTERN id RCTJSONClean(id object);
|
||||
|
||||
// Get MD5 hash of a string
|
||||
|
|
|
@ -23,8 +23,12 @@
|
|||
|
||||
NSString *const RCTErrorUnspecified = @"EUNSPECIFIED";
|
||||
|
||||
NSString *__nullable RCTJSONStringify(id __nullable jsonObject, NSError **error)
|
||||
static NSString *__nullable _RCTJSONStringifyNoRetry(id __nullable jsonObject, NSError **error)
|
||||
{
|
||||
if (!jsonObject) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
static SEL JSONKitSelector = NULL;
|
||||
static NSSet<Class> *collectionTypes;
|
||||
static dispatch_once_t onceToken;
|
||||
|
@ -38,17 +42,48 @@ NSString *__nullable RCTJSONStringify(id __nullable jsonObject, NSError **error)
|
|||
}
|
||||
});
|
||||
|
||||
// Use JSONKit if available and object is not a fragment
|
||||
if (JSONKitSelector && [collectionTypes containsObject:[jsonObject classForCoder]]) {
|
||||
return ((NSString *(*)(id, SEL, int, NSError **))objc_msgSend)(jsonObject, JSONKitSelector, 0, error);
|
||||
}
|
||||
@try {
|
||||
|
||||
// Use Foundation JSON method
|
||||
NSData *jsonData = jsonObject ? [NSJSONSerialization
|
||||
dataWithJSONObject:jsonObject
|
||||
options:(NSJSONWritingOptions)NSJSONReadingAllowFragments
|
||||
error:error] : nil;
|
||||
return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
|
||||
// Use JSONKit if available and object is not a fragment
|
||||
if (JSONKitSelector && [collectionTypes containsObject:[jsonObject classForCoder]]) {
|
||||
return ((NSString *(*)(id, SEL, int, NSError **))objc_msgSend)(jsonObject, JSONKitSelector, 0, error);
|
||||
}
|
||||
|
||||
// Use Foundation JSON method
|
||||
NSData *jsonData = [NSJSONSerialization
|
||||
dataWithJSONObject:jsonObject options:(NSJSONWritingOptions)NSJSONReadingAllowFragments
|
||||
error:error];
|
||||
|
||||
return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
|
||||
// Convert exception to error
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:RCTErrorDomain code:0 userInfo:@{
|
||||
NSLocalizedDescriptionKey: exception.description ?: @""
|
||||
}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *__nullable RCTJSONStringify(id __nullable jsonObject, NSError **error)
|
||||
{
|
||||
if (error) {
|
||||
return _RCTJSONStringifyNoRetry(jsonObject, error);
|
||||
} else {
|
||||
NSError *localError;
|
||||
NSString *json = _RCTJSONStringifyNoRetry(jsonObject, &localError);
|
||||
if (localError) {
|
||||
RCTLogError(@"RCTJSONStringify() encountered the following error: %@",
|
||||
localError.localizedDescription);
|
||||
// Sanitize the data, then retry. This is slow, but it prevents uncaught
|
||||
// data issues from crashing in production
|
||||
return _RCTJSONStringifyNoRetry(RCTJSONClean(jsonObject), NULL);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
static id __nullable _RCTJSONParse(NSString *__nullable jsonString, BOOL mutable, NSError **error)
|
||||
|
@ -134,6 +169,14 @@ id RCTJSONClean(id object)
|
|||
});
|
||||
|
||||
if ([validLeafTypes containsObject:[object classForCoder]]) {
|
||||
if ([object isKindOfClass:[NSNumber class]]) {
|
||||
return @(RCTZeroIfNaN([object doubleValue]));
|
||||
}
|
||||
if ([object isKindOfClass:[NSString class]]) {
|
||||
if ([object UTF8String] == NULL) {
|
||||
return (id)kCFNull;
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
|
||||
- (void)openStackFrameInEditor:(NSDictionary *)stackFrame
|
||||
{
|
||||
NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, nil) dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, NULL) dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest new];
|
||||
request.URL = [RCTConvert NSURL:@"http://localhost:8081/open-stack-frame"];
|
||||
|
|
|
@ -81,7 +81,7 @@ static systrace_arg_t *RCTProfileSystraceArgsFromNSDictionary(NSDictionary *args
|
|||
systrace_args[i].key = keyc;
|
||||
systrace_args[i].key_len = (int)strlen(keyc);
|
||||
|
||||
const char *valuec = RCTJSONStringify(value, nil).UTF8String;
|
||||
const char *valuec = RCTJSONStringify(value, NULL).UTF8String;
|
||||
systrace_args[i].value = valuec;
|
||||
systrace_args[i].value_len = (int)strlen(valuec);
|
||||
i++;
|
||||
|
|
Loading…
Reference in New Issue