Skip to content

Commit 29245e9

Browse files
fkgozalifacebook-github-bot
authored andcommitted
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
1 parent e6b305b commit 29245e9

File tree

2 files changed

+36
-20
lines changed

2 files changed

+36
-20
lines changed

Libraries/RCTTest/RCTTestRunner.m

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -115,20 +115,20 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName
115115
{
116116
__weak RCTBridge *batchedBridge;
117117
NSNumber *rootTag;
118-
119-
@autoreleasepool {
120-
__block NSMutableArray<NSString *> *errors = nil;
121-
RCTLogFunction defaultLogFunction = RCTGetLogFunction();
122-
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
123-
defaultLogFunction(level, source, fileName, lineNumber, message);
124-
if (level >= RCTLogLevelError) {
125-
if (errors == nil) {
126-
errors = [NSMutableArray new];
127-
}
128-
[errors addObject:message];
118+
RCTLogFunction defaultLogFunction = RCTGetLogFunction();
119+
// Catch all error logs, that are equivalent to redboxes in dev mode.
120+
__block NSMutableArray<NSString *> *errors = nil;
121+
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
122+
defaultLogFunction(level, source, fileName, lineNumber, message);
123+
if (level >= RCTLogLevelError) {
124+
if (errors == nil) {
125+
errors = [NSMutableArray new];
129126
}
130-
});
127+
[errors addObject:message];
128+
}
129+
});
131130

131+
@autoreleasepool {
132132
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
133133
moduleProvider:_moduleProvider
134134
launchOptions:nil];
@@ -172,7 +172,16 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName
172172
testModule.view = nil;
173173
}
174174

175-
RCTSetLogFunction(defaultLogFunction);
175+
// From this point on catch only fatal errors.
176+
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
177+
defaultLogFunction(level, source, fileName, lineNumber, message);
178+
if (level >= RCTLogLevelFatal) {
179+
if (errors == nil) {
180+
errors = [NSMutableArray new];
181+
}
182+
[errors addObject:message];
183+
}
184+
});
176185

177186
#if RCT_DEV
178187
NSArray<UIView *> *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
@@ -208,8 +217,10 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName
208217
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
209218
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
210219
}
211-
// Note: this deallocation isn't consistently working in test setup, so disable the assertion.
212-
// RCTAssert(batchedBridge == nil, @"Bridge should be deallocated after the test");
220+
RCTAssert(errors == nil, @"RedBox errors during bridge invalidation: %@", errors);
221+
RCTAssert(batchedBridge == nil, @"Bridge should be deallocated after the test");
222+
223+
RCTSetLogFunction(defaultLogFunction);
213224
}
214225

215226
@end

React/CxxModule/RCTNativeModule.mm

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,16 @@
7171
invokeInner(weakBridge, weakModuleData, methodId, std::move(params));
7272
};
7373

74-
dispatch_queue_t queue = m_moduleData.methodQueue;
75-
if (queue == RCTJSThread) {
76-
block();
77-
} else if (queue) {
78-
dispatch_async(queue, block);
74+
if (m_bridge.valid) {
75+
dispatch_queue_t queue = m_moduleData.methodQueue;
76+
if (queue == RCTJSThread) {
77+
block();
78+
} else if (queue) {
79+
dispatch_async(queue, block);
80+
}
81+
} else {
82+
RCTLogError(@"Attempted to invoke `%u` (method ID) on `%@` (NativeModule name) with an invalid bridge.",
83+
methodId, m_moduleData.name);
7984
}
8085
}
8186

0 commit comments

Comments
 (0)