diff --git a/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp b/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp index 7958519e683f6d..fea1d0bc2d33c7 100644 --- a/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp +++ b/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp @@ -1240,6 +1240,11 @@ jsi::Function JSCRuntime::createFunctionFromHostFunction( exceptionString += ex.what(); *exception = makeError(rt, exceptionString); res = JSValueMakeUndefined(ctx); + } catch (const std::string &ex) { + std::string exceptionString("Exception in HostFunction: "); + exceptionString += ex; + *exception = makeError(rt, exceptionString); + res = JSValueMakeUndefined(ctx); } catch (...) { std::string exceptionString("Exception in HostFunction: "); *exception = makeError(rt, exceptionString); diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp index 35fbba1af840be..39f5b30c27ebb3 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp @@ -400,6 +400,45 @@ jsi::Value convertFromJMapToValue(JNIEnv *env, jsi::Runtime &rt, jobject arg) { return jsi::valueFromDynamic(rt, result->cthis()->consume()); } +jsi::Value createJSRuntimeError(jsi::Runtime &runtime, const std::string& message) +{ + return runtime.global().getPropertyAsFunction(runtime, "Error").call(runtime, message); +} + +/** + * Creates JSError with current JS runtime stack and Throwable stack trace. + */ +jsi::JSError convertThrowableToJSError(jsi::Runtime &runtime, jni::local_ref throwable) +{ + auto stackTrace = throwable->getStackTrace(); + + jsi::Array stackElements(runtime, stackTrace->size()); + for (int i = 0; i < stackTrace->size(); ++i) { + auto frame = stackTrace->getElement(i); + + jsi::Object frameObject(runtime); + frameObject.setProperty(runtime, "className", frame->getClassName()); + frameObject.setProperty(runtime, "fileName", frame->getFileName()); + frameObject.setProperty(runtime, "lineNumber", frame->getLineNumber()); + frameObject.setProperty(runtime, "methodName", frame->getMethodName()); + stackElements.setValueAtIndex(runtime, i, std::move(frameObject)); + } + + jsi::Object cause(runtime); + auto getName = throwable->getClass()->getClass() + ->getMethod()>("getName"); + auto getMessage = throwable->getClass() + ->getMethod()>("getMessage"); + auto message = getMessage(throwable)->toStdString(); + cause.setProperty(runtime, "name", getName(throwable->getClass())->toStdString()); + cause.setProperty(runtime, "message", message); + cause.setProperty(runtime, "stackElements", std::move(stackElements)); + + jsi::Value error = createJSRuntimeError(runtime, "Exception in HostFunction: " + message); + error.asObject(runtime).setProperty(runtime, "cause", std::move(cause)); + return {runtime, std::move(error)}; +} + } // namespace jsi::Value JavaTurboModule::invokeJavaMethod( @@ -467,7 +506,9 @@ jsi::Value JavaTurboModule::invokeJavaMethod( } else { TMPL::asyncMethodCallFail(moduleName, methodName); } - throw; + auto exception = std::current_exception(); + auto throwable = jni::getJavaExceptionForCppException(exception); + throw convertThrowableToJSError(runtime, throwable); } }; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm index 7a1ca33ccc2213..5abf81b57fade0 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm @@ -211,6 +211,29 @@ static int32_t getUniqueId() return [callback copy]; } +static jsi::Value createJSRuntimeError(jsi::Runtime &runtime, const std::string& message) +{ + return runtime.global().getPropertyAsFunction(runtime, "Error").call(runtime, message); +} + +/** + * Creates JSError with current JS runtime and NSException stack trace. + */ +static jsi::JSError convertNSExceptionToJSError(jsi::Runtime &runtime, NSException *exception) +{ + std::string reason = [exception.reason UTF8String]; + + jsi::Object cause(runtime); + cause.setProperty(runtime, "name", [exception.name UTF8String]); + cause.setProperty(runtime, "message", reason); + cause.setProperty(runtime, "stackSymbols", convertNSArrayToJSIArray(runtime, exception.callStackSymbols)); + cause.setProperty(runtime, "stackReturnAddresses", convertNSArrayToJSIArray(runtime, exception.callStackReturnAddresses)); + + jsi::Value error = createJSRuntimeError(runtime, "Exception in HostFunction: " + reason); + error.asObject(runtime).setProperty(runtime, "cause", std::move(cause)); + return {runtime, std::move(error)}; +} + namespace facebook::react { jsi::Value ObjCTurboModule::createPromise(jsi::Runtime &runtime, std::string methodName, PromiseInvocationBlock invoke) @@ -376,9 +399,13 @@ static int32_t getUniqueId() TurboModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodNameStr.c_str(), asyncCallCounter); } - // TODO(T66699874) Should we guard this with a try/catch? - [inv invokeWithTarget:strongModule]; - [retainedObjectsForInvocation removeAllObjects]; + @try { + [inv invokeWithTarget:strongModule]; + } @catch (NSException *exception) { + throw convertNSExceptionToJSError(runtime, exception); + } @finally { + [retainedObjectsForInvocation removeAllObjects]; + } if (!wasMethodSync) { TurboModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodNameStr.c_str(), asyncCallCounter);