From a86e6b76fbbef6585052f1ca6eea58d9ab8fb3ad Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 11 Aug 2015 19:18:08 -0100 Subject: [PATCH] Fixed fuzzer app and ensured that React does not crash when fuzzed --- Libraries/Utilities/MessageQueue.js | 7 +++++-- React/Base/RCTModuleMethod.m | 26 ++++++++++++++------------ React/Executors/RCTContextExecutor.m | 2 +- React/Modules/RCTUIManager.m | 12 +++++++++--- React/Views/RCTWebViewManager.m | 11 +++++++---- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 98b0a9fe0..ffca2bca5 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -218,9 +218,10 @@ class MessageQueue { return null; } + let fn = null; let self = this; if (type === MethodTypes.remoteAsync) { - return function(...args) { + fn = function(...args) { return new Promise((resolve, reject) => { self.__nativeCall(module, method, args, resolve, (errorData) => { var error = createErrorFromErrorData(errorData); @@ -229,7 +230,7 @@ class MessageQueue { }); }; } else { - return function(...args) { + fn = function(...args) { let lastArg = args.length > 0 ? args[args.length - 1] : null; let secondLastArg = args.length > 1 ? args[args.length - 2] : null; let hasSuccCB = typeof lastArg === 'function'; @@ -245,6 +246,8 @@ class MessageQueue { return self.__nativeCall(module, method, args, onFail, onSucc); }; } + fn.type = type; + return fn; } } diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index df751097a..a64bc46ab 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -17,7 +17,7 @@ #import "RCTLog.h" #import "RCTUtils.h" -typedef void (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id); +typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id); @implementation RCTMethodArgument @@ -166,6 +166,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) [argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) { \ _logic \ [invocation setArgument:&value atIndex:(index) + 2]; \ + return YES; \ }]; __weak RCTModuleMethod *weakSelf = self; @@ -174,7 +175,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a function"); - return; + return NO; } // Marked as autoreleasing, because NSInvocation doesn't retain arguments @@ -268,13 +269,13 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a function"); - return; + return NO; } // Marked as autoreleasing, because NSInvocation doesn't retain arguments __autoreleasing id value = (json ? ^(NSError *error) { [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" + method:@"invokeCallbackAndReturnFlushedQueue" arguments:@[json, @[RCTJSErrorFromNSError(error)]]]; } : ^(__unused NSError *error) {}); ) @@ -285,7 +286,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) RCT_ARG_BLOCK( if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function"); - return; + return NO; } // Marked as autoreleasing, because NSInvocation doesn't retain arguments @@ -302,7 +303,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) RCT_ARG_BLOCK( if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function"); - return; + return NO; } // Marked as autoreleasing, because NSInvocation doesn't retain arguments @@ -350,12 +351,11 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) if (nullability == RCTNonnullable) { RCTArgumentBlock oldBlock = argumentBlocks[i - 2]; argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) { - if (json == nil || json == (id)kCFNull) { + if (json == nil) { RCTLogArgumentError(weakSelf, index, typeName, "must not be null"); - id null = nil; - [invocation setArgument:&null atIndex:index + 2]; + return NO; } else { - oldBlock(bridge, index, json); + return oldBlock(bridge, index, json); } }; } @@ -408,9 +408,11 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) // Set arguments NSUInteger index = 0; for (id json in arguments) { - id arg = RCTNilIfNull(json); RCTArgumentBlock block = _argumentBlocks[index]; - block(bridge, index, arg); + if (!block(bridge, index, RCTNilIfNull(json))) { + // Invalid argument, abort + return; + } index++; } diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index b31c7e98c..8c8174773 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -558,7 +558,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) }), @"js_call,json_call", (@{@"objectName": objectName}))]; } -RCT_EXPORT_METHOD(setContextName:(NSString *)name) +RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)name) { if (JSGlobalContextSetName != NULL) { JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)name); diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 40c0f7eb8..0a8ac410c 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -820,6 +820,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po - (void)batchDidComplete { RCTProfileBeginEvent(); + // Gather blocks to be executed now that all view hierarchy manipulations have // been completed (note that these may still take place before layout has finished) for (RCTComponentData *componentData in _componentDataByName.allValues) { @@ -871,8 +872,13 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po dispatch_async(dispatch_get_main_queue(), ^{ RCTProfileEndFlowEvent(); RCTProfileBeginEvent(); - for (dispatch_block_t block in previousPendingUIBlocks) { - block(); + @try { + for (dispatch_block_t block in previousPendingUIBlocks) { + block(); + } + } + @catch (NSException *exception) { + RCTLogError(@"Exception thrown while executing UI block: %@", exception); } RCTProfileEndEvent(@"UIManager flushUIBlocks", @"objc_call", @{ @"count": @(previousPendingUIBlocks.count), @@ -1043,7 +1049,7 @@ RCT_EXPORT_METHOD(setMainScrollViewTag:(nonnull NSNumber *)reactTag) uiManager.mainScrollView = (id)view; uiManager.mainScrollView.nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate; } else { - RCTAssert(NO, @"Tag #%@ does not conform to RCTScrollableProtocol", reactTag); + RCTLogError(@"Tag #%@ does not conform to RCTScrollableProtocol", reactTag); } } else { uiManager.mainScrollView = nil; diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index 6cd6e6502..1fd2cfe3f 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -62,8 +62,9 @@ RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag) RCTWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + } else { + [view goBack]; } - [view goBack]; }]; } @@ -73,8 +74,9 @@ RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag) id view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + } else { + [view goForward]; } - [view goForward]; }]; } @@ -84,9 +86,10 @@ RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag) [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { RCTWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogMustFix(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + } else { + [view reload]; } - [view reload]; }]; }