mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
native modules: Guard against concurrent resolve/reject calls (#53151)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/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
This commit is contained in:
committed by
Facebook GitHub Bot
parent
e0ea781908
commit
dc879950d1
+47
-18
@@ -26,6 +26,8 @@
|
||||
#import <objc/runtime.h>
|
||||
#import <atomic>
|
||||
#import <iostream>
|
||||
#import <mutex>
|
||||
|
||||
#import <sstream>
|
||||
#import <vector>
|
||||
|
||||
@@ -284,44 +286,71 @@ ObjCTurboModule::createPromise(jsi::Runtime &runtime, const std::string &methodN
|
||||
{rt, args[0].getObject(rt).getFunction(rt), std::move(jsInvoker)});
|
||||
__block std::optional<AsyncCallback<>> reject(
|
||||
{rt, args[1].getObject(rt).getFunction(rt), std::move(jsInvoker)});
|
||||
__block std::shared_ptr<std::mutex> mutex = std::make_shared<std::mutex>();
|
||||
|
||||
RCTPromiseResolveBlock resolveBlock = ^(id result) {
|
||||
if (!resolve || !reject) {
|
||||
if (resolveWasCalled) {
|
||||
RCTLogError(@"%s: Tried to resolve a promise more than once.", moduleMethod.c_str());
|
||||
std::optional<AsyncCallback<>> localResolve;
|
||||
bool alreadyResolved = false;
|
||||
bool alreadyRejected = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
if (!resolve || !reject) {
|
||||
alreadyResolved = resolveWasCalled;
|
||||
alreadyRejected = !resolveWasCalled;
|
||||
} else {
|
||||
RCTLogError(
|
||||
@"%s: Tried to resolve a promise after it's already been rejected.", moduleMethod.c_str());
|
||||
resolveWasCalled = YES;
|
||||
localResolve = std::move(resolve);
|
||||
resolve = std::nullopt;
|
||||
reject = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (alreadyResolved) {
|
||||
RCTLogError(@"%s: Tried to resolve a promise more than once.", moduleMethod.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
resolve->call([result](jsi::Runtime &rt, jsi::Function &jsFunction) {
|
||||
if (alreadyRejected) {
|
||||
RCTLogError(@"%s: Tried to resolve a promise after it's already been rejected.", moduleMethod.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
localResolve->call([result](jsi::Runtime &rt, jsi::Function &jsFunction) {
|
||||
jsFunction.call(rt, convertObjCObjectToJSIValue(rt, result));
|
||||
});
|
||||
|
||||
resolveWasCalled = YES;
|
||||
resolve = std::nullopt;
|
||||
reject = std::nullopt;
|
||||
};
|
||||
|
||||
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
|
||||
if (!resolve || !reject) {
|
||||
if (resolveWasCalled) {
|
||||
RCTLogError(@"%s: Tried to reject a promise after it's already been resolved.", moduleMethod.c_str());
|
||||
std::optional<AsyncCallback<>> localReject;
|
||||
bool alreadyResolved = false;
|
||||
bool alreadyRejected = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
if (!resolve || !reject) {
|
||||
alreadyResolved = resolveWasCalled;
|
||||
alreadyRejected = !resolveWasCalled;
|
||||
} else {
|
||||
RCTLogError(@"%s: Tried to reject a promise more than once.", moduleMethod.c_str());
|
||||
resolveWasCalled = NO;
|
||||
localReject = std::move(reject);
|
||||
reject = std::nullopt;
|
||||
resolve = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (alreadyResolved) {
|
||||
RCTLogError(@"%s: Tried to reject a promise after it's already been resolved.", moduleMethod.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (alreadyRejected) {
|
||||
RCTLogError(@"%s: Tried to reject a promise more than once.", moduleMethod.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *jsErrorDetails = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
|
||||
reject->call([jsErrorDetails](jsi::Runtime &rt, jsi::Function &jsFunction) {
|
||||
localReject->call([jsErrorDetails](jsi::Runtime &rt, jsi::Function &jsFunction) {
|
||||
jsFunction.call(rt, convertJSErrorDetailsToJSRuntimeError(rt, jsErrorDetails));
|
||||
});
|
||||
resolveWasCalled = NO;
|
||||
resolve = std::nullopt;
|
||||
reject = std::nullopt;
|
||||
};
|
||||
|
||||
invokeCopy(resolveBlock, rejectBlock);
|
||||
|
||||
Reference in New Issue
Block a user