react-native/React/CxxModule/RCTNativeModule.mm
Kevin Gozali 29245e96cb iOS: prevent nativemodule access from JS if bridge is no longer valid
Summary: This helps prevent race condition where JS calls to NativeModules got queued and executed while the bridge is invalidating itself, causing assertion failures in test setup (for example). It won't prevent it 100% of the time, due to threading (and adding lock is expensive for each nativemodule call).

Reviewed By: yungsters

Differential Revision: D9231636

fbshipit-source-id: 298eaf52ffa4b84108184124e75b206b9ca7a41d
2018-08-09 12:17:07 -07:00

124 lines
3.9 KiB
Plaintext

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTNativeModule.h"
#import <React/RCTBridge.h>
#import <React/RCTBridgeMethod.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTCxxUtils.h>
#import <React/RCTFollyConvert.h>
#import <React/RCTLog.h>
#import <React/RCTProfile.h>
#import <React/RCTUtils.h>
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
#endif
namespace facebook {
namespace react {
static MethodCallResult invokeInner(RCTBridge *bridge, RCTModuleData *moduleData, unsigned int methodId, const folly::dynamic &params);
RCTNativeModule::RCTNativeModule(RCTBridge *bridge, RCTModuleData *moduleData)
: m_bridge(bridge)
, m_moduleData(moduleData) {}
std::string RCTNativeModule::getName() {
return [m_moduleData.name UTF8String];
}
std::vector<MethodDescriptor> RCTNativeModule::getMethods() {
std::vector<MethodDescriptor> descs;
for (id<RCTBridgeMethod> method in m_moduleData.methods) {
descs.emplace_back(
method.JSMethodName,
RCTFunctionDescriptorFromType(method.functionType)
);
}
return descs;
}
folly::dynamic RCTNativeModule::getConstants() {
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways,
@"[RCTNativeModule getConstants] moduleData.exportedConstants", nil);
NSDictionary *constants = m_moduleData.exportedConstants;
folly::dynamic ret = convertIdToFollyDynamic(constants);
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
return ret;
}
void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &&params, int callId) {
// capture by weak pointer so that we can safely use these variables in a callback
__weak RCTBridge *weakBridge = m_bridge;
__weak RCTModuleData *weakModuleData = m_moduleData;
// The BatchedBridge version of this buckets all the callbacks by thread, and
// queues one block on each. This is much simpler; we'll see how it goes and
// iterate.
dispatch_block_t block = [weakBridge, weakModuleData, methodId, params=std::move(params), callId] {
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
}
#endif
invokeInner(weakBridge, weakModuleData, methodId, std::move(params));
};
if (m_bridge.valid) {
dispatch_queue_t queue = m_moduleData.methodQueue;
if (queue == RCTJSThread) {
block();
} else if (queue) {
dispatch_async(queue, block);
}
} else {
RCTLogError(@"Attempted to invoke `%u` (method ID) on `%@` (NativeModule name) with an invalid bridge.",
methodId, m_moduleData.name);
}
}
MethodCallResult RCTNativeModule::callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &&params) {
return invokeInner(m_bridge, m_moduleData, reactMethodId, params);
}
static MethodCallResult invokeInner(RCTBridge *bridge, RCTModuleData *moduleData, unsigned int methodId, const folly::dynamic &params) {
if (!bridge || !bridge.valid || !moduleData) {
return folly::none;
}
id<RCTBridgeMethod> method = moduleData.methods[methodId];
if (RCT_DEBUG && !method) {
RCTLogError(@"Unknown methodID: %ud for module: %@",
methodId, moduleData.name);
}
NSArray *objcParams = convertFollyDynamicToId(params);
@try {
id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams];
return convertIdToFollyDynamic(result);
}
@catch (NSException *exception) {
// Pass on JS exceptions
if ([exception.name hasPrefix:RCTFatalExceptionName]) {
@throw exception;
}
NSString *message = [NSString stringWithFormat:
@"Exception '%@' was thrown while invoking %s on target %@ with params %@\ncallstack: %@",
exception, method.JSMethodName, moduleData.name, objcParams, exception.callStackSymbols];
RCTFatal(RCTErrorWithMessage(message));
}
return folly::none;
}
}
}