From 04ef49c0599be2cbf13647a56ca37352e1975b10 Mon Sep 17 00:00:00 2001 From: Autopilot Bot Date: Wed, 22 Jul 2026 08:43:12 -0700 Subject: [PATCH] Report a fatal JS error instead of aborting the process when reading its extra data fails (#57636) Summary: Changelog: [Android][Fixed] - Report a fatal JS error instead of aborting the process when reading the error's extra data fails Reviewed By: andrewdacenko, javache Differential Revision: D112476648 --- .../ReactJsExceptionHandler.kt | 6 ++-- .../runtime/jni/JReactExceptionManager.cpp | 34 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler.kt index 1c924fcdc26d..a382076abc33 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler.kt @@ -33,7 +33,9 @@ internal fun interface ReactJsExceptionHandler { val stack: List val id: Int val isFatal: Boolean - val extraData: ReadableMap + // Nullable: native marshalling may fail or the error may carry none (see + // JReactExceptionManager.cpp). + val extraData: ReadableMap? } @DoNotStripAny @@ -53,7 +55,7 @@ internal fun interface ReactJsExceptionHandler { override val stack: ArrayList, override val id: Int, override val isFatal: Boolean, - override val extraData: ReadableNativeMap, + override val extraData: ReadableNativeMap?, ) : ProcessedError fun reportJsException(errorMap: ProcessedError) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactExceptionManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactExceptionManager.cpp index 3d61ab537288..46f30fa38648 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactExceptionManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactExceptionManager.cpp @@ -53,11 +53,37 @@ class ProcessedErrorImpl stack->add(ProcessedErrorStackFrameImpl::create(frame)); } - auto extraDataDynamic = - jsi::dynamicFromValue(runtime, jsi::Value(runtime, error.extraData)); + // Marshalling `extraData` out of the runtime can throw (e.g. a + // jsi::JSIException when the runtime is in a bad state during a fatal + // error). This runs inside the `noexcept` onJsError callback, so an + // escaping exception aborts the whole process; contain it and report the + // error without its extra data instead. `extraData` may end up null (here, + // or when the JS value is null/undefined); the Java field is nullable. + jni::local_ref extraData; - auto extraData = - ReadableNativeMap::createWithContents(std::move(extraDataDynamic)); + auto reportWithoutExtraData = [&](const char* reason) { + LOG(ERROR) + << "JReactExceptionManager: failed to marshal JS error extraData; reporting the error without it: " + << reason; + try { + extraData = ReadableNativeMap::createWithContents( + folly::dynamic::object("extraDataMarshallingError", reason)); + } catch (...) { + // The fallback allocation can itself throw; leave extraData null. + extraData = nullptr; + } + }; + + try { + auto extraDataDynamic = + jsi::dynamicFromValue(runtime, jsi::Value(runtime, error.extraData)); + extraData = + ReadableNativeMap::createWithContents(std::move(extraDataDynamic)); + } catch (const std::exception& e) { + reportWithoutExtraData(e.what()); + } catch (...) { + reportWithoutExtraData("unknown exception"); + } return newInstance( error.message,