Skip to content

Commit dc87995

Browse files
RSNarafacebook-github-bot
authored andcommitted
native modules: Guard against concurrent resolve/reject calls (#53151)
Summary: Pull Request resolved: #53151 If the native module calls the resolve/reject, resolve/resolve, reject/resolve, reject/reject concurrently, the turbomodule infra could run into a null pointer exception. This diff mitigates that problem. Changelog: [iOS][Fixed] - Fix concurrent calls into resolve/reject inside native modules Reviewed By: sanjay-io Differential Revision: D79824319 fbshipit-source-id: 675264781f303d12fc1eb9649ecdc78601b7720b
1 parent e0ea781 commit dc87995

1 file changed

Lines changed: 47 additions & 18 deletions

File tree

  • packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon

packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#import <objc/runtime.h>
2727
#import <atomic>
2828
#import <iostream>
29+
#import <mutex>
30+
2931
#import <sstream>
3032
#import <vector>
3133

@@ -284,44 +286,71 @@ id convertJSIValueToObjCObject(
284286
{rt, args[0].getObject(rt).getFunction(rt), std::move(jsInvoker)});
285287
__block std::optional<AsyncCallback<>> reject(
286288
{rt, args[1].getObject(rt).getFunction(rt), std::move(jsInvoker)});
289+
__block std::shared_ptr<std::mutex> mutex = std::make_shared<std::mutex>();
287290

288291
RCTPromiseResolveBlock resolveBlock = ^(id result) {
289-
if (!resolve || !reject) {
290-
if (resolveWasCalled) {
291-
RCTLogError(@"%s: Tried to resolve a promise more than once.", moduleMethod.c_str());
292+
std::optional<AsyncCallback<>> localResolve;
293+
bool alreadyResolved = false;
294+
bool alreadyRejected = false;
295+
{
296+
std::lock_guard<std::mutex> lock(*mutex);
297+
if (!resolve || !reject) {
298+
alreadyResolved = resolveWasCalled;
299+
alreadyRejected = !resolveWasCalled;
292300
} else {
293-
RCTLogError(
294-
@"%s: Tried to resolve a promise after it's already been rejected.", moduleMethod.c_str());
301+
resolveWasCalled = YES;
302+
localResolve = std::move(resolve);
303+
resolve = std::nullopt;
304+
reject = std::nullopt;
295305
}
306+
}
307+
308+
if (alreadyResolved) {
309+
RCTLogError(@"%s: Tried to resolve a promise more than once.", moduleMethod.c_str());
310+
return;
311+
}
312+
313+
if (alreadyRejected) {
314+
RCTLogError(@"%s: Tried to resolve a promise after it's already been rejected.", moduleMethod.c_str());
296315
return;
297316
}
298317

299-
resolve->call([result](jsi::Runtime &rt, jsi::Function &jsFunction) {
318+
localResolve->call([result](jsi::Runtime &rt, jsi::Function &jsFunction) {
300319
jsFunction.call(rt, convertObjCObjectToJSIValue(rt, result));
301320
});
302-
303-
resolveWasCalled = YES;
304-
resolve = std::nullopt;
305-
reject = std::nullopt;
306321
};
307322

308323
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
309-
if (!resolve || !reject) {
310-
if (resolveWasCalled) {
311-
RCTLogError(@"%s: Tried to reject a promise after it's already been resolved.", moduleMethod.c_str());
324+
std::optional<AsyncCallback<>> localReject;
325+
bool alreadyResolved = false;
326+
bool alreadyRejected = false;
327+
{
328+
std::lock_guard<std::mutex> lock(*mutex);
329+
if (!resolve || !reject) {
330+
alreadyResolved = resolveWasCalled;
331+
alreadyRejected = !resolveWasCalled;
312332
} else {
313-
RCTLogError(@"%s: Tried to reject a promise more than once.", moduleMethod.c_str());
333+
resolveWasCalled = NO;
334+
localReject = std::move(reject);
335+
reject = std::nullopt;
336+
resolve = std::nullopt;
314337
}
338+
}
339+
340+
if (alreadyResolved) {
341+
RCTLogError(@"%s: Tried to reject a promise after it's already been resolved.", moduleMethod.c_str());
342+
return;
343+
}
344+
345+
if (alreadyRejected) {
346+
RCTLogError(@"%s: Tried to reject a promise more than once.", moduleMethod.c_str());
315347
return;
316348
}
317349

318350
NSDictionary *jsErrorDetails = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
319-
reject->call([jsErrorDetails](jsi::Runtime &rt, jsi::Function &jsFunction) {
351+
localReject->call([jsErrorDetails](jsi::Runtime &rt, jsi::Function &jsFunction) {
320352
jsFunction.call(rt, convertJSErrorDetailsToJSRuntimeError(rt, jsErrorDetails));
321353
});
322-
resolveWasCalled = NO;
323-
resolve = std::nullopt;
324-
reject = std::nullopt;
325354
};
326355

327356
invokeCopy(resolveBlock, rejectBlock);

0 commit comments

Comments
 (0)