Skip to content

Commit 9ad279c

Browse files
committed
Fixes LogBox displaying "Unknown" when an exception is thrown from an extra Hermes Runtime
1 parent 41a1941 commit 9ad279c

1 file changed

Lines changed: 58 additions & 2 deletions

File tree

packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,42 @@ void RuntimeScheduler_Modern::executeTask(
380380
task.callback = result.getObject(runtime).getFunction(runtime);
381381
}
382382
} catch (jsi::JSError& error) {
383-
onTaskError_(runtime, error);
383+
// The JSError may originate from a different Hermes runtime (e.g. an extra
384+
// runtime created by a NativeModule). Forwarding error.value() directly to
385+
// ErrorUtils.reportFatalError would pass a jsi::Value that belongs to the
386+
// source runtime's heap into the main runtime, which is undefined behaviour
387+
// and causes LogBox to display "Unknown" instead of the real message.
388+
//
389+
// Fix: reconstruct a proper `new Error(message)` in the correct (main)
390+
// runtime and copy the already-extracted stack string onto it, then wrap it
391+
// with the JSError(Value&&, string, string) constructor that does NOT
392+
// re-read any properties from the runtime - so only safe std::strings cross
393+
// the runtime boundary.
394+
try {
395+
auto errorMessage = error.getMessage();
396+
auto errorStack = error.getStack();
397+
// Build `new Error(message)` in the main runtime.
398+
jsi::Value newErrorObj =
399+
runtime.global()
400+
.getPropertyAsFunction(runtime, "Error")
401+
.callAsConstructor(
402+
runtime,
403+
jsi::String::createFromUtf8(runtime, errorMessage));
404+
// Override .stack with the original cross-runtime stack string.
405+
newErrorObj.getObject(runtime).setProperty(
406+
runtime,
407+
"stack",
408+
jsi::String::createFromUtf8(runtime, errorStack));
409+
// Use the no-runtime-read constructor to avoid any cross-runtime access.
410+
jsi::JSError newError(
411+
std::move(newErrorObj), errorMessage, errorStack);
412+
onTaskError_(runtime, newError);
413+
} catch (...) {
414+
// Reconstruction failed (e.g. Error constructor not available yet).
415+
// Fall back to a plain string error so we at least surface the message.
416+
jsi::JSError fallback(runtime, error.getMessage());
417+
onTaskError_(runtime, fallback);
418+
}
384419
} catch (std::exception& ex) {
385420
jsi::JSError error(runtime, std::string("Non-js exception: ") + ex.what());
386421
onTaskError_(runtime, error);
@@ -419,7 +454,28 @@ void RuntimeScheduler_Modern::performMicrotaskCheckpoint(
419454
break;
420455
}
421456
} catch (jsi::JSError& error) {
422-
onTaskError_(runtime, error);
457+
// Same cross-runtime fix as in executeTask: reconstruct a proper
458+
// new Error in the main runtime before forwarding to ErrorUtils.
459+
try {
460+
auto errorMessage = error.getMessage();
461+
auto errorStack = error.getStack();
462+
jsi::Value newErrorObj =
463+
runtime.global()
464+
.getPropertyAsFunction(runtime, "Error")
465+
.callAsConstructor(
466+
runtime,
467+
jsi::String::createFromUtf8(runtime, errorMessage));
468+
newErrorObj.getObject(runtime).setProperty(
469+
runtime,
470+
"stack",
471+
jsi::String::createFromUtf8(runtime, errorStack));
472+
jsi::JSError newError(
473+
std::move(newErrorObj), errorMessage, errorStack);
474+
onTaskError_(runtime, newError);
475+
} catch (...) {
476+
jsi::JSError fallback(runtime, error.getMessage());
477+
onTaskError_(runtime, fallback);
478+
}
423479
} catch (std::exception& ex) {
424480
jsi::JSError error(
425481
runtime, std::string("Non-js exception: ") + ex.what());

0 commit comments

Comments
 (0)