Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: Mixed stack traces for Android and iOS Turbo Modules #36925

5 changes: 5 additions & 0 deletions packages/react-native/ReactCommon/jsc/JSCRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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: <unknown>");
*exception = makeError(rt, exceptionString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,48 @@ 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, facebook::jni::local_ref<facebook::jni::JThrowable> 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<facebook::jni::local_ref<facebook::jni::JString>()>("getSimpleName");
auto getMessage = throwable->getClass()
->getMethod<facebook::jni::local_ref<facebook::jni::JString>()>("getMessage");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know that I can reach the methods like this, so the change to the fbjni interface is not necessary, but would make it cleaner.

krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
auto message = getMessage(throwable)->toStdString();
cause.setProperty(
runtime,
"name",
getName(throwable->getClass())->toStdString());
cause.setProperty(runtime,"message",message);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: formatting

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(
Expand Down Expand Up @@ -468,7 +510,9 @@ jsi::Value JavaTurboModule::invokeJavaMethod(
} else {
TMPL::asyncMethodCallFail(moduleName, methodName);
}
throw;
auto exception = std::current_exception();
auto throwable = facebook::jni::getJavaExceptionForCppException(exception);
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
throw convertThrowableToJSError(runtime, throwable);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
namespace react {

Expand Down Expand Up @@ -377,9 +400,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);
Expand Down