diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index 28e339d0e00c51..2be6b0d8f73838 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -181,8 +181,12 @@ - (void)hostDidStart:(RCTHost *)host - (void)host:(RCTHost *)host didReceiveJSErrorStack:(NSArray *> *)stack message:(NSString *)message + originalMessage:(NSString *_Nullable)originalMessage + name:(NSString *_Nullable)name + componentStack:(NSString *_Nullable)componentStack exceptionId:(NSUInteger)exceptionId isFatal:(BOOL)isFatal + extraData:(NSDictionary *)extraData { } diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 631ecd84111cc7..312ee90d17ebb2 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -2396,12 +2396,16 @@ public class com/facebook/react/devsupport/ReleaseDevSupportManager : com/facebo public class com/facebook/react/devsupport/StackTraceHelper { public static final field COLUMN_KEY Ljava/lang/String; + public static final field COMPONENT_STACK_KEY Ljava/lang/String; + public static final field EXTRA_DATA_KEY Ljava/lang/String; public static final field FILE_KEY Ljava/lang/String; public static final field ID_KEY Ljava/lang/String; public static final field IS_FATAL_KEY Ljava/lang/String; public static final field LINE_NUMBER_KEY Ljava/lang/String; public static final field MESSAGE_KEY Ljava/lang/String; public static final field METHOD_NAME_KEY Ljava/lang/String; + public static final field NAME_KEY Ljava/lang/String; + public static final field ORIGINAL_MESSAGE_KEY Ljava/lang/String; public static final field STACK_KEY Ljava/lang/String; public fun ()V public static fun convertJavaStackTrace (Ljava/lang/Throwable;)[Lcom/facebook/react/devsupport/interfaces/StackFrame; @@ -2893,16 +2897,20 @@ public abstract interface class com/facebook/react/interfaces/TaskInterface { } public abstract interface class com/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedError { - public abstract fun getExceptionId ()I - public abstract fun getFrames ()Ljava/util/List; + public abstract fun getComponentStack ()Ljava/lang/String; + public abstract fun getExtraData ()Lcom/facebook/react/bridge/ReadableMap; + public abstract fun getId ()I public abstract fun getMessage ()Ljava/lang/String; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getOriginalMessage ()Ljava/lang/String; + public abstract fun getStack ()Ljava/util/List; public abstract fun isFatal ()Z } public abstract interface class com/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedError$StackFrame { - public abstract fun getColumnNumber ()I - public abstract fun getFileName ()Ljava/lang/String; - public abstract fun getLineNumber ()I + public abstract fun getColumn ()Ljava/lang/Integer; + public abstract fun getFile ()Ljava/lang/String; + public abstract fun getLineNumber ()Ljava/lang/Integer; public abstract fun getMethodName ()Ljava/lang/String; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java index 70b779a8dc8e10..d901f8b2f73c79 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java @@ -34,9 +34,13 @@ public class StackTraceHelper { public static final String METHOD_NAME_KEY = "methodName"; public static final String MESSAGE_KEY = "message"; + public static final String ORIGINAL_MESSAGE_KEY = "originalMessage"; + public static final String NAME_KEY = "name"; + public static final String COMPONENT_STACK_KEY = "componentStack"; public static final String STACK_KEY = "stack"; public static final String ID_KEY = "id"; public static final String IS_FATAL_KEY = "isFatal"; + public static final String EXTRA_DATA_KEY = "extraData"; private static final Pattern STACK_FRAME_PATTERN1 = Pattern.compile("^(?:(.*?)@)?(.*?)\\:([0-9]+)\\:([0-9]+)$"); @@ -260,22 +264,32 @@ public static String formatStackTrace(String title, StackFrame[] stack) { } public static JavaOnlyMap convertParsedError(ParsedError error) { - List frames = error.getFrames(); + List frames = error.getStack(); List readableMapList = new ArrayList<>(); for (ParsedError.StackFrame frame : frames) { JavaOnlyMap map = new JavaOnlyMap(); - map.putDouble(COLUMN_KEY, frame.getColumnNumber()); + map.putDouble(COLUMN_KEY, frame.getColumn()); map.putDouble(LINE_NUMBER_KEY, frame.getLineNumber()); - map.putString(FILE_KEY, (String) frame.getFileName()); + map.putString(FILE_KEY, (String) frame.getFile()); map.putString(METHOD_NAME_KEY, (String) frame.getMethodName()); readableMapList.add(map); } JavaOnlyMap data = new JavaOnlyMap(); data.putString(MESSAGE_KEY, error.getMessage()); + if (error.getOriginalMessage() != null) { + data.putString(ORIGINAL_MESSAGE_KEY, error.getOriginalMessage()); + } + if (error.getName() != null) { + data.putString(NAME_KEY, error.getName()); + } + if (error.getComponentStack() != null) { + data.putString(COMPONENT_STACK_KEY, error.getComponentStack()); + } data.putArray(STACK_KEY, JavaOnlyArray.from(readableMapList)); - data.putInt(ID_KEY, error.getExceptionId()); + data.putInt(ID_KEY, error.getId()); data.putBoolean(IS_FATAL_KEY, error.isFatal()); + data.putMap(EXTRA_DATA_KEY, error.getExtraData()); return data; } 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 c12a1ebeef96f4..60113e7e6bdb35 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 @@ -8,6 +8,8 @@ package com.facebook.react.interfaces.exceptionmanager import com.facebook.proguard.annotations.DoNotStripAny +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableNativeMap import com.facebook.react.common.annotations.UnstableReactNativeAPI import java.util.ArrayList @@ -18,32 +20,40 @@ public fun interface ReactJsExceptionHandler { public interface ParsedError { @DoNotStripAny public interface StackFrame { - public val fileName: String + public val file: String? public val methodName: String - public val lineNumber: Int - public val columnNumber: Int + public val lineNumber: Int? + public val column: Int? } - public val frames: List public val message: String - public val exceptionId: Int + public val originalMessage: String? + public val name: String? + public val componentStack: String? + public val stack: List + public val id: Int public val isFatal: Boolean + public val extraData: ReadableMap } @DoNotStripAny private data class ParsedStackFrameImpl( - override val fileName: String, + override val file: String?, override val methodName: String, - override val lineNumber: Int, - override val columnNumber: Int, + override val lineNumber: Int?, + override val column: Int?, ) : ParsedError.StackFrame @DoNotStripAny private data class ParsedErrorImpl( - override val frames: ArrayList, override val message: String, - override val exceptionId: Int, + override val originalMessage: String?, + override val name: String?, + override val componentStack: String?, + override val stack: ArrayList, + override val id: Int, override val isFatal: Boolean, + override val extraData: ReadableNativeMap, ) : ParsedError public fun reportJsException(errorMap: ParsedError) 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 157c3eb454a876..e0ac376122bd9d 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 @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include namespace facebook::react { @@ -28,7 +31,10 @@ class ParsedStackFrameImpl static facebook::jni::local_ref create( const JsErrorHandler::ParsedError::StackFrame& frame) { return newInstance( - frame.fileName, frame.methodName, frame.lineNumber, frame.columnNumber); + frame.file ? jni::make_jstring(*frame.file) : nullptr, + frame.methodName, + frame.lineNumber ? jni::JInteger::valueOf(*frame.lineNumber) : nullptr, + frame.column ? jni::JInteger::valueOf(*frame.column) : nullptr); } }; @@ -39,27 +45,42 @@ class ParsedErrorImpl "Lcom/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedErrorImpl;"; static facebook::jni::local_ref create( + jsi::Runtime& runtime, const JsErrorHandler::ParsedError& error) { - auto stackFrames = - facebook::jni::JArrayList::create(); - for (const auto& frame : error.frames) { - stackFrames->add(ParsedStackFrameImpl::create(frame)); + auto stack = facebook::jni::JArrayList::create(); + for (const auto& frame : error.stack) { + stack->add(ParsedStackFrameImpl::create(frame)); } + auto extraDataDynamic = + jsi::dynamicFromValue(runtime, jsi::Value(runtime, error.extraData)); + + auto extraData = + ReadableNativeMap::createWithContents(std::move(extraDataDynamic)); + return newInstance( - stackFrames, error.message, error.exceptionId, error.isFatal); + error.message, + error.originalMessage ? jni::make_jstring(*error.originalMessage) + : nullptr, + error.name ? jni::make_jstring(*error.name) : nullptr, + error.componentStack ? jni::make_jstring(*error.componentStack) + : nullptr, + stack, + error.id, + error.isFatal, + extraData); } }; - } // namespace void JReactExceptionManager::reportJsException( + jsi::Runtime& runtime, const JsErrorHandler::ParsedError& error) { static const auto method = javaClassStatic()->getMethod)>( "reportJsException"); if (self() != nullptr) { - method(self(), ParsedErrorImpl::create(error)); + method(self(), ParsedErrorImpl::create(runtime, error)); } } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactExceptionManager.h b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactExceptionManager.h index 3261545582f434..078977a0e1e2ce 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactExceptionManager.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactExceptionManager.h @@ -19,7 +19,9 @@ class JReactExceptionManager static auto constexpr kJavaDescriptor = "Lcom/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler;"; - void reportJsException(const JsErrorHandler::ParsedError& error); + void reportJsException( + jsi::Runtime& runtime, + const JsErrorHandler::ParsedError& error); }; } // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp index 3bb569a9d0ecb5..e6cd6614e3e3e1 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp @@ -52,10 +52,11 @@ JReactInstance::JReactInstance( jReactExceptionManager_ = jni::make_global(jReactExceptionManager); auto onJsError = [weakJReactExceptionManager = jni::make_weak(jReactExceptionManager)]( + jsi::Runtime& runtime, const JsErrorHandler::ParsedError& error) mutable noexcept { if (auto jReactExceptionManager = weakJReactExceptionManager.lockLocal()) { - jReactExceptionManager->reportJsException(error); + jReactExceptionManager->reportJsException(runtime, error); } }; diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt index 5e19da6b688908..2f642b1f1b76b2 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt @@ -7,6 +7,7 @@ package com.facebook.react.devsupport +import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.ReadableMap import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.interfaces.exceptionmanager.ReactJsExceptionHandler.* @@ -98,27 +99,31 @@ class StackTraceHelperTest { private fun getParsedErrorTestData(): ParsedError { val frame1 = object : ParsedError.StackFrame { - override val fileName = "file1" + override val file = "file1" override val methodName = "method1" override val lineNumber = 1 - override val columnNumber = 10 + override val column = 10 } val frame2 = object : ParsedError.StackFrame { - override val fileName = "file2" + override val file = "file2" override val methodName = "method2" override val lineNumber = 2 - override val columnNumber = 20 + override val column = 20 } val frames = listOf(frame1, frame2) return object : ParsedError { - override val frames = frames override val message = "error message" - override val exceptionId = 123 + override val originalMessage = null + override val name = null + override val componentStack = null + override val stack = frames + override val id = 123 override val isFatal = true + override val extraData = JavaOnlyMap() } } } diff --git a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp index 08dac54ccd10db..6fb7d8705514ca 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp +++ b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp @@ -13,11 +13,64 @@ #include #include +namespace { +std::string quote(const std::string& view) { + return "\"" + view + "\""; +} +} // namespace + namespace facebook::react { +std::ostream& operator<<( + std::ostream& os, + const JsErrorHandler::ParsedError::StackFrame& frame) { + auto file = frame.file ? quote(*frame.file) : "nil"; + auto methodName = quote(frame.methodName); + auto lineNumber = + frame.lineNumber ? std::to_string(*frame.lineNumber) : "nil"; + auto column = frame.column ? std::to_string(*frame.column) : "nil"; + + os << "StackFrame { .file = " << file << ", .methodName = " << methodName + << ", .lineNumber = " << lineNumber << ", .column = " << column << " }"; + return os; +} +std::ostream& operator<<( + std::ostream& os, + const JsErrorHandler::ParsedError& error) { + auto message = quote(error.message); + auto originalMessage = + error.originalMessage ? quote(*error.originalMessage) : "nil"; + auto name = error.name ? quote(*error.name) : "nil"; + auto componentStack = + error.componentStack ? quote(*error.componentStack) : "nil"; + auto id = std::to_string(error.id); + auto isFatal = std::to_string(static_cast(error.isFatal)); + auto extraData = "jsi::Object{ } "; + + os << "ParsedError {\n" + << " .message = " << message << "\n" + << " .originalMessage = " << originalMessage << "\n" + << " .name = " << name << "\n" + << " .componentStack = " << componentStack << "\n" + << " .stack = [\n"; + + for (const auto& frame : error.stack) { + os << " " << frame << ", \n"; + } + os << " ]\n" + << " .id = " << id << "\n" + << " .isFatal " << isFatal << "\n" + << " .extraData = " << extraData << "\n" + << "}"; + return os; +} + // TODO(T198763073): Migrate away from std::regex in this function -static JsErrorHandler::ParsedError -parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) { +static JsErrorHandler::ParsedError parseErrorStack( + jsi::Runtime& runtime, + const jsi::JSError& error, + bool isFatal, + bool isHermes) { /** * This parses the different stack traces and puts them into one format * This borrows heavily from TraceKit (https://github.com/occ/TraceKit) @@ -58,10 +111,10 @@ parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) { std::string str2 = std::string(searchResults[2]); if (str2.compare("native")) { frames.push_back({ - .fileName = std::string(searchResults[4]), + .file = std::string(searchResults[4]), .methodName = std::string(searchResults[1]), .lineNumber = std::stoi(searchResults[5]), - .columnNumber = std::stoi(searchResults[6]), + .column = std::stoi(searchResults[6]), }); } } @@ -69,10 +122,10 @@ parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) { // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful if (std::regex_search(line, searchResults, REGEX_GECKO)) { frames.push_back({ - .fileName = std::string(searchResults[3]), + .file = std::string(searchResults[3]), .methodName = std::string(searchResults[1]), .lineNumber = std::stoi(searchResults[4]), - .columnNumber = std::stoi(searchResults[5]), + .column = std::stoi(searchResults[5]), }); } else if ( // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful @@ -80,10 +133,10 @@ parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) { // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful std::regex_search(line, searchResults, REGEX_NODE)) { frames.push_back({ - .fileName = std::string(searchResults[2]), + .file = std::string(searchResults[2]), .methodName = std::string(searchResults[1]), .lineNumber = std::stoi(searchResults[3]), - .columnNumber = std::stoi(searchResults[4]), + .column = std::stoi(searchResults[4]), }); } else { continue; @@ -92,10 +145,14 @@ parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) { } return { - .frames = std::move(frames), .message = "EarlyJsError: " + error.getMessage(), - .exceptionId = 0, + .originalMessage = std::nullopt, + .name = std::nullopt, + .componentStack = std::nullopt, + .stack = std::move(frames), + .id = 0, .isFatal = isFatal, + .extraData = jsi::Object(runtime), }; } @@ -127,8 +184,8 @@ void JsErrorHandler::handleFatalError( } } // This is a hacky way to get Hermes stack trace. - ParsedError parsedError = parseErrorStack(error, true, false); - _onJsError(parsedError); + ParsedError parsedError = parseErrorStack(runtime, error, true, false); + _onJsError(runtime, parsedError); } bool JsErrorHandler::hasHandledFatalError() { diff --git a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h index a9b94f2d5c09b7..372a81f358c3c5 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h +++ b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h @@ -8,6 +8,8 @@ #pragma once #include +#include +#include namespace facebook::react { @@ -15,19 +17,28 @@ class JsErrorHandler { public: struct ParsedError { struct StackFrame { - std::string fileName; + std::optional file; std::string methodName; - int lineNumber; - int columnNumber; + std::optional lineNumber; + std::optional column; + friend std::ostream& operator<<( + std::ostream& os, + const StackFrame& frame); }; - std::vector frames; std::string message; - int exceptionId; + std::optional originalMessage; + std::optional name; + std::optional componentStack; + std::vector stack; + int id; bool isFatal; + jsi::Object extraData; + friend std::ostream& operator<<(std::ostream& os, const ParsedError& error); }; - using OnJsError = std::function; + using OnJsError = + std::function; explicit JsErrorHandler(OnJsError onJsError); ~JsErrorHandler(); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.cpp index 3c3f81edd1f470..42549ee8c8a1e2 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.cpp @@ -34,19 +34,10 @@ void ReactInstanceIntegrationTest::SetUp() { auto timerManager = std::make_shared(std::move(mockRegistry)); - auto onJsError = [](const JsErrorHandler::ParsedError& errorMap) noexcept { + auto onJsError = [](jsi::Runtime& /*runtime*/, + const JsErrorHandler::ParsedError& error) noexcept { LOG(INFO) << "[jsErrorHandlingFunc called]"; - LOG(INFO) << "message: " << errorMap.message; - LOG(INFO) << "exceptionId: " << std::to_string(errorMap.exceptionId); - LOG(INFO) << "isFatal: " - << std::to_string(static_cast(errorMap.isFatal)); - auto frames = errorMap.frames; - for (const auto& mapBuffer : frames) { - LOG(INFO) << "[Frame]" << std::endl << "\tfile: " << mapBuffer.fileName; - LOG(INFO) << "\tmethodName: " << mapBuffer.methodName; - LOG(INFO) << "\tlineNumber: " << std::to_string(mapBuffer.lineNumber); - LOG(INFO) << "\tcolumn: " << std::to_string(mapBuffer.columnNumber); - } + LOG(INFO) << error << std::endl; }; auto jsRuntimeFactory = std::make_unique(); diff --git a/packages/react-native/ReactCommon/react/runtime/iostests/RCTHostTests.mm b/packages/react-native/ReactCommon/react/runtime/iostests/RCTHostTests.mm index cadf6d61cdca8e..2b007f02fdd807 100644 --- a/packages/react-native/ReactCommon/react/runtime/iostests/RCTHostTests.mm +++ b/packages/react-native/ReactCommon/react/runtime/iostests/RCTHostTests.mm @@ -135,11 +135,29 @@ - (void)testDidReceiveErrorStack stackFrame0[@"file"] = @"file2.js"; [stack addObject:stackFrame1]; - [instanceDelegate instance:[OCMArg any] didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES]; + id extraData = [NSDictionary dictionary]; + + [instanceDelegate instance:[OCMArg any] + didReceiveJSErrorStack:stack + message:@"message" + originalMessage:nil + name:nil + componentStack:nil + exceptionId:5 + isFatal:YES + extraData:extraData]; OCMVerify( OCMTimes(1), - [_mockHostDelegate host:_subject didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES]); + [_mockHostDelegate host:_subject + didReceiveJSErrorStack:stack + message:@"message" + originalMessage:nil + name:nil + componentStack:nil + exceptionId:5 + isFatal:YES + extraData:extraData]); } - (void)testDidInitializeRuntime diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h index fa16f997266346..25e213a11c27b5 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h @@ -30,8 +30,12 @@ typedef NSURL *_Nullable (^RCTHostBundleURLProvider)(void); - (void)host:(RCTHost *)host didReceiveJSErrorStack:(NSArray *> *)stack message:(NSString *)message + originalMessage:(NSString *_Nullable)originalMessage + name:(NSString *_Nullable)name + componentStack:(NSString *_Nullable)componentStack exceptionId:(NSUInteger)exceptionId - isFatal:(BOOL)isFatal; + isFatal:(BOOL)isFatal + extraData:(NSDictionary *)extraData; - (void)hostDidStart:(RCTHost *)host; diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm index 5fb599a8595ca0..75fe99c2353cbd 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm @@ -318,10 +318,22 @@ - (void)dealloc - (void)instance:(RCTInstance *)instance didReceiveJSErrorStack:(NSArray *> *)stack message:(NSString *)message + originalMessage:(NSString *_Nullable)originalMessage + name:(NSString *_Nullable)name + componentStack:(NSString *_Nullable)componentStack exceptionId:(NSUInteger)exceptionId isFatal:(BOOL)isFatal + extraData:(NSDictionary *)extraData { - [_hostDelegate host:self didReceiveJSErrorStack:stack message:message exceptionId:exceptionId isFatal:isFatal]; + [_hostDelegate host:self + didReceiveJSErrorStack:stack + message:message + originalMessage:originalMessage + name:name + componentStack:componentStack + exceptionId:exceptionId + isFatal:isFatal + extraData:extraData]; } - (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h index 8d26c642de3e57..b77c8994e35a2f 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h @@ -40,8 +40,12 @@ RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags); - (void)instance:(RCTInstance *)instance didReceiveJSErrorStack:(NSArray *> *)stack message:(NSString *)message + originalMessage:(NSString *_Nullable)originalMessage + name:(NSString *_Nullable)name + componentStack:(NSString *_Nullable)componentStack exceptionId:(NSUInteger)exceptionId - isFatal:(BOOL)isFatal; + isFatal:(BOOL)isFatal + extraData:(NSDictionary *)extraData; - (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime; diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index 89aeb2e19ca764..09ee426ea0af80 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -31,6 +31,7 @@ #import #import #import +#import #import #import #import @@ -220,7 +221,9 @@ - (void)_start objCTimerRegistryRawPtr->setTimerManager(timerManager); __weak __typeof(self) weakSelf = self; - auto onJsError = [=](const JsErrorHandler::ParsedError &error) { [weakSelf _handleJSError:error]; }; + auto onJsError = [=](jsi::Runtime &runtime, const JsErrorHandler::ParsedError &error) { + [weakSelf _handleJSError:error withRuntime:runtime]; + }; // Create the React Instance _reactInstance = std::make_unique( @@ -462,23 +465,34 @@ - (void)_loadScriptFromSource:(RCTSource *)source [[NSNotificationCenter defaultCenter] postNotificationName:@"RCTInstanceDidLoadBundle" object:nil]; } -- (void)_handleJSError:(const JsErrorHandler::ParsedError &)error +- (void)_handleJSError:(const JsErrorHandler::ParsedError &)error withRuntime:(jsi::Runtime &)runtime { - NSString *message = [NSString stringWithCString:error.message.c_str() encoding:[NSString defaultCStringEncoding]]; + NSString *message = @(error.message.c_str()); NSMutableArray *> *stack = [NSMutableArray new]; - for (const JsErrorHandler::ParsedError::StackFrame &frame : error.frames) { + for (const JsErrorHandler::ParsedError::StackFrame &frame : error.stack) { [stack addObject:@{ - @"file" : [NSString stringWithCString:frame.fileName.c_str() encoding:[NSString defaultCStringEncoding]], - @"methodName" : [NSString stringWithCString:frame.methodName.c_str() encoding:[NSString defaultCStringEncoding]], - @"lineNumber" : [NSNumber numberWithInt:frame.lineNumber], - @"column" : [NSNumber numberWithInt:frame.columnNumber], + @"file" : frame.file ? @((*frame.file).c_str()) : [NSNull null], + @"methodName" : @(frame.methodName.c_str()), + @"lineNumber" : frame.lineNumber ? @(*frame.lineNumber) : [NSNull null], + @"column" : frame.column ? @(*frame.column) : [NSNull null], }]; } + + NSString *originalMessage = error.originalMessage ? @(error.originalMessage->c_str()) : nil; + NSString *name = error.name ? @(error.name->c_str()) : nil; + NSString *componentStack = error.componentStack ? @(error.componentStack->c_str()) : nil; + id extraData = + TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsi::Value(runtime, error.extraData), nullptr); + [_delegate instance:self didReceiveJSErrorStack:stack message:message - exceptionId:error.exceptionId - isFatal:error.isFatal]; + originalMessage:originalMessage + name:name + componentStack:componentStack + exceptionId:error.id + isFatal:error.isFatal + extraData:extraData]; } @end diff --git a/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp b/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp index cea0100cb2a606..cfee71e3c3eeb2 100644 --- a/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp +++ b/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp @@ -124,7 +124,8 @@ class ReactInstanceTest : public ::testing::Test { auto mockRegistry = std::make_unique(); mockRegistry_ = mockRegistry.get(); timerManager_ = std::make_shared(std::move(mockRegistry)); - auto onJsError = [](const JsErrorHandler::ParsedError& errorMap) noexcept { + auto onJsError = [](jsi::Runtime& /*runtime*/, + const JsErrorHandler::ParsedError& /*error*/) noexcept { // Do nothing };