From 4a6f2ec44a0cee0fde7e31105a3da9ef91e27f69 Mon Sep 17 00:00:00 2001 From: Theo Yaung Date: Thu, 8 Dec 2016 13:31:52 -0800 Subject: [PATCH] Fail-Fast on Redundant Callback Invokes Reviewed By: javache Differential Revision: D4295268 fbshipit-source-id: 1258ffbc02bcf7d7199348c7df8fcd744bb9963f --- React/Base/RCTModuleMethod.m | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index 3b72f9f066565a..5f5c9e2dc2bb8a 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -102,6 +102,17 @@ static RCTNullability RCTParseNullabilityPostfix(const char **input) return RCTNullabilityUnspecified; } +// returns YES if execution is safe to proceed (enqueue callback invocation), NO if callback has already been invoked +static BOOL RCTCheckCallbackMultipleInvocations(BOOL *didInvoke) { + if (*didInvoke) { + RCTFatal(RCTErrorWithMessage(@"Illegal callback invocation from native module. This callback type only permits a single invocation from native code.")); + return NO; + } else { + *didInvoke = YES; + return YES; + } +} + SEL RCTParseMethodSignature(NSString *, NSArray **); SEL RCTParseMethodSignature(NSString *methodSignature, NSArray **arguments) { @@ -205,8 +216,11 @@ - (void)processMethodSignature return NO; } + __block BOOL didInvoke = NO; RCT_BLOCK_ARGUMENT(^(NSArray *args) { - [bridge enqueueCallback:json args:args]; + if (RCTCheckCallbackMultipleInvocations(&didInvoke)) { + [bridge enqueueCallback:json args:args]; + } }); ) }; @@ -302,8 +316,11 @@ - (void)processMethodSignature return NO; } + __block BOOL didInvoke = NO; RCT_BLOCK_ARGUMENT(^(NSError *error) { - [bridge enqueueCallback:json args:@[RCTJSErrorFromNSError(error)]]; + if (RCTCheckCallbackMultipleInvocations(&didInvoke)) { + [bridge enqueueCallback:json args:@[RCTJSErrorFromNSError(error)]]; + } }); ) } else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) { @@ -316,8 +333,11 @@ - (void)processMethodSignature return NO; } + __block BOOL didInvoke = NO; RCT_BLOCK_ARGUMENT(^(id result) { - [bridge enqueueCallback:json args:result ? @[result] : @[]]; + if (RCTCheckCallbackMultipleInvocations(&didInvoke)) { + [bridge enqueueCallback:json args:result ? @[result] : @[]]; + } }); ) } else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) { @@ -330,9 +350,12 @@ - (void)processMethodSignature return NO; } + __block BOOL didInvoke = NO; RCT_BLOCK_ARGUMENT(^(NSString *code, NSString *message, NSError *error) { - NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError(code, message, error); - [bridge enqueueCallback:json args:@[errorJSON]]; + if (RCTCheckCallbackMultipleInvocations(&didInvoke)) { + NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError(code, message, error); + [bridge enqueueCallback:json args:@[errorJSON]]; + } }); ) } else {