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 @@ -468,7 +468,39 @@ 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
auto stackTrace = throwable->getStackTrace();

jsi::Array stackArray(runtime, stackTrace->size());
std::string stackTraceStr;
for (int i = 0; i < stackTrace->size(); ++i) {
auto frame = stackTrace->getElement(i);
stackTraceStr += frame->toString() + '\n';

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());
stackArray.setValueAtIndex(runtime, i, std::move(frameObject));
}

// TODO Better would be use getMessage() but its missing in fbjni interface
auto throwableString = throwable->toString();
Copy link
Member

Choose a reason for hiding this comment

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

Feel free to send this as a PR to fbjni. I'm not super comfortable with the substring assumptions done below, especially as this is already on an error path.

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've used the dynamic calls for now, same as fbjni would do, also opened the PR there, but have some issues with testing, please see the desc in the PR.

https://github.com/facebook/react-native/pull/36925/files/847043eac5132d10f05b7e58fb3e3fbaa199bede#r1169624312

std::string name = throwableString.substr(0, throwableString.find(':'));
std::string message = throwableString.substr(throwableString.find(':') + 2, -1);
jsi::Object cause(runtime);
cause.setProperty(runtime, "name", name);
cause.setProperty(runtime, "message", message);
cause.setProperty(runtime, "stack", stackTraceStr);
cause.setProperty(runtime, "stackArray", std::move(stackArray));

jsi::Value error =
runtime.global().getPropertyAsFunction(runtime, "Error")
.call(runtime, "Exception in HostFunction: " + message);
error.asObject(runtime).setProperty(runtime, "cause", cause);
throw jsi::JSError(runtime, std::move(error));
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@ static int32_t getUniqueId()
return [callback copy];
}

static NSString *convertNSExceptionStackToString(NSException *exception)
{
NSMutableString *stack = [[NSMutableString alloc] init];
for (NSString* symbol in exception.callStackSymbols) {
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
[stack appendFormat:@"%@\n", symbol];
}

return stack;
}

namespace facebook {
namespace react {

Expand Down Expand Up @@ -378,8 +388,23 @@ static int32_t getUniqueId()
}

// TODO(T66699874) Should we guard this with a try/catch?
[inv invokeWithTarget:strongModule];
[retainedObjectsForInvocation removeAllObjects];
@try {
[inv invokeWithTarget:strongModule];
} @catch (NSException *exception) {
std::string reason = std::string([exception.reason UTF8String]);
jsi::Object cause(runtime);
cause.setProperty(runtime, "name", std::string([exception.name UTF8String]));
cause.setProperty(runtime, "message", reason);
cause.setProperty(runtime, "stack", std::string([convertNSExceptionStackToString(exception) UTF8String]));
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
//TODO For release builds better would be use include callStackReturnAddresses
jsi::Value error =
runtime.global().getPropertyAsFunction(runtime, "Error")
.call(runtime, "Exception in HostFunction: " + reason);
error.asObject(runtime).setProperty(runtime, "cause", cause);
throw jsi::JSError(runtime, std::move(error));
} @finally {
[retainedObjectsForInvocation removeAllObjects];
}

if (!wasMethodSync) {
TurboModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodNameStr.c_str(), asyncCallCounter);
Expand Down