diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 52f2e1fc8457ff..12ef9ecd5f8a1c 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -2830,6 +2830,20 @@ public abstract interface class com/facebook/react/interfaces/TaskInterface { public abstract fun waitForCompletion (JLjava/util/concurrent/TimeUnit;)Z } +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 getMessage ()Ljava/lang/String; + 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 getMethodName ()Ljava/lang/String; +} + public abstract interface class com/facebook/react/interfaces/fabric/ReactSurface { public abstract fun clear ()V public abstract fun detach ()V 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 613016b640a72a..c12a1ebeef96f4 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 @@ -9,10 +9,42 @@ package com.facebook.react.interfaces.exceptionmanager import com.facebook.proguard.annotations.DoNotStripAny import com.facebook.react.common.annotations.UnstableReactNativeAPI -import com.facebook.react.common.mapbuffer.ReadableMapBuffer +import java.util.ArrayList @DoNotStripAny @UnstableReactNativeAPI public fun interface ReactJsExceptionHandler { - public fun reportJsException(errorMap: ReadableMapBuffer?) + @DoNotStripAny + public interface ParsedError { + @DoNotStripAny + public interface StackFrame { + public val fileName: String + public val methodName: String + public val lineNumber: Int + public val columnNumber: Int + } + + public val frames: List + public val message: String + public val exceptionId: Int + public val isFatal: Boolean + } + + @DoNotStripAny + private data class ParsedStackFrameImpl( + override val fileName: String, + override val methodName: String, + override val lineNumber: Int, + override val columnNumber: Int, + ) : ParsedError.StackFrame + + @DoNotStripAny + private data class ParsedErrorImpl( + override val frames: ArrayList, + override val message: String, + override val exceptionId: Int, + override val isFatal: Boolean, + ) : 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 21cc5d52d3bfe8..157c3eb454a876 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,17 +9,57 @@ #include #include #include -#include namespace facebook::react { +namespace { +class ParsedError : public facebook::jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedError;"; +}; + +class ParsedStackFrameImpl + : public facebook::jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedStackFrameImpl;"; + + static facebook::jni::local_ref create( + const JsErrorHandler::ParsedError::StackFrame& frame) { + return newInstance( + frame.fileName, frame.methodName, frame.lineNumber, frame.columnNumber); + } +}; + +class ParsedErrorImpl + : public facebook::jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedErrorImpl;"; + + static facebook::jni::local_ref create( + const JsErrorHandler::ParsedError& error) { + auto stackFrames = + facebook::jni::JArrayList::create(); + for (const auto& frame : error.frames) { + stackFrames->add(ParsedStackFrameImpl::create(frame)); + } + + return newInstance( + stackFrames, error.message, error.exceptionId, error.isFatal); + } +}; + +} // namespace + void JReactExceptionManager::reportJsException( - const JReadableMapBuffer::javaobject errorMapBuffer) { + const JsErrorHandler::ParsedError& error) { static const auto method = - javaClassStatic()->getMethod( + javaClassStatic()->getMethod)>( "reportJsException"); if (self() != nullptr) { - method(self(), errorMapBuffer); + method(self(), ParsedErrorImpl::create(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 80b898c39ea9dc..3261545582f434 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 @@ -9,7 +9,7 @@ #include #include -#include +#include namespace facebook::react { @@ -19,7 +19,7 @@ class JReactExceptionManager static auto constexpr kJavaDescriptor = "Lcom/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler;"; - void reportJsException(const JReadableMapBuffer::javaobject errorMapBuffer); + void reportJsException(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 a74cc54fc45668..5c0f1bc678f165 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 @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include "JavaTimerRegistry.h" @@ -52,13 +51,14 @@ JReactInstance::JReactInstance( jsTimerExecutor->cthis()->setTimerManager(timerManager); jReactExceptionManager_ = jni::make_global(jReactExceptionManager); - auto jsErrorHandlingFunc = [this](MapBuffer errorMap) noexcept { - if (jReactExceptionManager_ != nullptr) { - auto jErrorMap = - JReadableMapBuffer::createWithContents(std::move(errorMap)); - jReactExceptionManager_->reportJsException(jErrorMap.get()); - } - }; + auto jsErrorHandlingFunc = + [weakJReactExceptionManager = jni::make_weak(jReactExceptionManager)]( + const JsErrorHandler::ParsedError& error) mutable noexcept { + if (auto jReactExceptionManager = + weakJReactExceptionManager.lockLocal()) { + jReactExceptionManager->reportJsException(error); + } + }; jBindingsInstaller_ = jni::make_global(jBindingsInstaller); diff --git a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp index dbd7551b6fa797..9b474c1e13914c 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp +++ b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp @@ -6,7 +6,6 @@ */ #include "JsErrorHandler.h" -#include #include #include #include @@ -14,9 +13,7 @@ namespace facebook::react { -using facebook::react::JSErrorHandlerKey; - -static MapBuffer +static JsErrorHandler::ParsedError parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) { /** * This parses the different stack traces and puts them into one format @@ -43,63 +40,67 @@ parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) { std::string line; std::stringstream strStream(error.getStack()); - auto errorObj = MapBufferBuilder(); - std::vector frames; + std::vector frames; while (std::getline(strStream, line, '\n')) { - auto frame = MapBufferBuilder(); auto searchResults = std::smatch{}; if (isHermes) { if (std::regex_search(line, searchResults, REGEX_HERMES)) { std::string str2 = std::string(searchResults[2]); if (str2.compare("native")) { - frame.putString(kFrameFileName, std::string(searchResults[4])); - frame.putString(kFrameMethodName, std::string(searchResults[1])); - frame.putInt(kFrameLineNumber, std::stoi(searchResults[5])); - frame.putInt(kFrameColumnNumber, std::stoi(searchResults[6])); - frames.push_back(frame.build()); + frames.push_back({ + .fileName = std::string(searchResults[4]), + .methodName = std::string(searchResults[1]), + .lineNumber = std::stoi(searchResults[5]), + .columnNumber = std::stoi(searchResults[6]), + }); } } } else { if (std::regex_search(line, searchResults, REGEX_GECKO)) { - frame.putString(kFrameFileName, std::string(searchResults[3])); - frame.putString(kFrameMethodName, std::string(searchResults[1])); - frame.putInt(kFrameLineNumber, std::stoi(searchResults[4])); - frame.putInt(kFrameColumnNumber, std::stoi(searchResults[5])); + frames.push_back({ + .fileName = std::string(searchResults[3]), + .methodName = std::string(searchResults[1]), + .lineNumber = std::stoi(searchResults[4]), + .columnNumber = std::stoi(searchResults[5]), + }); } else if ( std::regex_search(line, searchResults, REGEX_CHROME) || std::regex_search(line, searchResults, REGEX_NODE)) { - frame.putString(kFrameFileName, std::string(searchResults[2])); - frame.putString(kFrameMethodName, std::string(searchResults[1])); - frame.putInt(kFrameLineNumber, std::stoi(searchResults[3])); - frame.putInt(kFrameColumnNumber, std::stoi(searchResults[4])); + frames.push_back({ + .fileName = std::string(searchResults[2]), + .methodName = std::string(searchResults[1]), + .lineNumber = std::stoi(searchResults[3]), + .columnNumber = std::stoi(searchResults[4]), + }); } else { continue; } - frames.push_back(frame.build()); } } - errorObj.putMapBufferList(kAllStackFrames, std::move(frames)); - errorObj.putString(kErrorMessage, "EarlyJsError: " + error.getMessage()); - // TODO: If needed, can increment exceptionId by 1 each time - errorObj.putInt(kExceptionId, 0); - errorObj.putBool(kIsFatal, isFatal); - return errorObj.build(); + + return { + .frames = std::move(frames), + .message = "EarlyJsError: " + error.getMessage(), + .exceptionId = 0, + .isFatal = isFatal, + }; } JsErrorHandler::JsErrorHandler( - JsErrorHandler::JsErrorHandlingFunc jsErrorHandlingFunc) { - this->_jsErrorHandlingFunc = jsErrorHandlingFunc; -}; + JsErrorHandler::JsErrorHandlingFunc jsErrorHandlingFunc) + : _jsErrorHandlingFunc(std::move(jsErrorHandlingFunc)){ + + }; JsErrorHandler::~JsErrorHandler() {} void JsErrorHandler::handleJsError(const jsi::JSError& error, bool isFatal) { // TODO: Current error parsing works and is stable. Can investigate using // REGEX_HERMES to get additional Hermes data, though it requires JS setup. - MapBuffer errorMap = parseErrorStack(error, isFatal, false); - _jsErrorHandlingFunc(std::move(errorMap)); + ParsedError parsedError = parseErrorStack(error, isFatal, false); + _jsErrorHandlingFunc(parsedError); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h index 558bda29e5d145..69f714d48536ac 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h +++ b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h @@ -8,26 +8,28 @@ #pragma once #include -#include namespace facebook::react { -enum JSErrorHandlerKey : uint16_t { - kFrameFileName = 0, - kFrameMethodName = 1, - kFrameLineNumber = 2, - kFrameColumnNumber = 3, - kAllStackFrames = 4, - kErrorMessage = 5, - kExceptionId = 6, - kIsFatal = 7 -}; - class JsErrorHandler { public: - using JsErrorHandlingFunc = std::function; - - JsErrorHandler(JsErrorHandlingFunc jsErrorHandlingFunc); + struct ParsedError { + struct StackFrame { + std::string fileName; + std::string methodName; + int lineNumber; + int columnNumber; + }; + + std::vector frames; + std::string message; + int exceptionId; + bool isFatal; + }; + + using JsErrorHandlingFunc = std::function; + + explicit JsErrorHandler(JsErrorHandlingFunc jsErrorHandlingFunc); ~JsErrorHandler(); void handleJsError(const jsi::JSError& error, bool isFatal); diff --git a/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec b/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec index 0202b89061497e..17e9d3417f9b5c 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec +++ b/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec @@ -48,6 +48,5 @@ Pod::Spec.new do |s| s.dependency folly_dep_name, folly_version s.dependency "React-jsi" add_dependency(s, "React-debug") - add_dependency(s, "React-Mapbuffer") end diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.cpp index 0ddec227d1c790..17ed7a7cce5284 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactInstanceIntegrationTest.cpp @@ -34,12 +34,22 @@ void ReactInstanceIntegrationTest::SetUp() { auto timerManager = std::make_shared(std::move(mockRegistry)); - auto jsErrorHandlingFunc = [](react::MapBuffer error) noexcept { - LOG(INFO) << "Error: \nFile: " << error.getString(react::kFrameFileName) - << "\nLine: " << error.getInt(react::kFrameLineNumber) - << "\nColumn: " << error.getInt(react::kFrameColumnNumber) - << "\nMethod: " << error.getString(react::kFrameMethodName); - }; + auto jsErrorHandlingFunc = + [](const JsErrorHandler::ParsedError& errorMap) 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); + } + }; auto jsRuntimeFactory = std::make_unique(); std::unique_ptr runtime_ = diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactNativeMocks.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactNativeMocks.cpp index aa74f01cbc2f33..8d1a01c79f356e 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactNativeMocks.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/ReactNativeMocks.cpp @@ -6,6 +6,7 @@ */ #include "ReactNativeMocks.h" +#include namespace facebook::react::jsinspector_modern { diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp index fdc47330e53fcc..3ed3b134215151 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include namespace facebook::react { @@ -37,7 +36,7 @@ ReactInstance::ReactInstance( : runtime_(std::move(runtime)), jsMessageQueueThread_(jsMessageQueueThread), timerManager_(std::move(timerManager)), - jsErrorHandler_(jsErrorHandlingFunc), + jsErrorHandler_(std::move(jsErrorHandlingFunc)), hasFatalJsError_(std::make_shared(false)), parentInspectorTarget_(parentInspectorTarget) { RuntimeExecutor runtimeExecutor = [weakRuntime = std::weak_ptr(runtime_), diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.h b/packages/react-native/ReactCommon/react/runtime/ReactInstance.h index 7c5eb4dc4cb1bc..c785147f72bc1e 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.h +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.h @@ -34,7 +34,7 @@ class ReactInstance final : private jsinspector_modern::InstanceTargetDelegate { std::unique_ptr runtime, std::shared_ptr jsMessageQueueThread, std::shared_ptr timerManager, - JsErrorHandler::JsErrorHandlingFunc JsErrorHandlingFunc, + JsErrorHandler::JsErrorHandlingFunc jsErrorHandlingFunc, jsinspector_modern::HostTarget* parentInspectorTarget = nullptr); RuntimeExecutor getUnbufferedRuntimeExecutor() noexcept; 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 7b30e83c17f290..489ac30f524751 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 @@ -245,7 +245,7 @@ - (void)_start objCTimerRegistryRawPtr->setTimerManager(timerManager); __weak __typeof(self) weakSelf = self; - auto jsErrorHandlingFunc = [=](MapBuffer errorMap) { [weakSelf _handleJSErrorMap:std::move(errorMap)]; }; + auto jsErrorHandlingFunc = [=](const JsErrorHandler::ParsedError &error) { [weakSelf _handleJSError:error]; }; // Create the React Instance _reactInstance = std::make_unique( @@ -496,28 +496,23 @@ - (void)_notifyEventDispatcherObserversOfEvent_DEPRECATED:(NSNotification *)noti } } -- (void)_handleJSErrorMap:(facebook::react::MapBuffer)errorMap +- (void)_handleJSError:(const JsErrorHandler::ParsedError &)error { - NSString *message = [NSString stringWithCString:errorMap.getString(JSErrorHandlerKey::kErrorMessage).c_str() - encoding:[NSString defaultCStringEncoding]]; - std::vector frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); + NSString *message = [NSString stringWithCString:error.message.c_str() encoding:[NSString defaultCStringEncoding]]; NSMutableArray *> *stack = [NSMutableArray new]; - for (const facebook::react::MapBuffer &mapBuffer : frames) { - NSDictionary *frame = @{ - @"file" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameFileName).c_str() - encoding:[NSString defaultCStringEncoding]], - @"methodName" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameMethodName).c_str() - encoding:[NSString defaultCStringEncoding]], - @"lineNumber" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameLineNumber)], - @"column" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameColumnNumber)], - }; - [stack addObject:frame]; + for (const JsErrorHandler::ParsedError::StackFrame &frame : error.frames) { + [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], + }]; } [_delegate instance:self didReceiveJSErrorStack:stack message:message - exceptionId:errorMap.getInt(JSErrorHandlerKey::kExceptionId) - isFatal:errorMap.getBool(JSErrorHandlerKey::kIsFatal)]; + exceptionId:error.exceptionId + isFatal:error.isFatal]; } - (void)didReceiveReloadCommand 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 3f8a36b39daa83..42c3d32f407687 100644 --- a/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp +++ b/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp @@ -13,8 +13,8 @@ #include #include +#include #include -#include #include using ::testing::_; @@ -123,9 +123,10 @@ class ReactInstanceTest : public ::testing::Test { auto mockRegistry = std::make_unique(); mockRegistry_ = mockRegistry.get(); timerManager_ = std::make_shared(std::move(mockRegistry)); - auto jsErrorHandlingFunc = [](MapBuffer errorMap) noexcept { - // Do nothing - }; + auto jsErrorHandlingFunc = + [](const JsErrorHandler::ParsedError& errorMap) noexcept { + // Do nothing + }; instance_ = std::make_unique( std::move(runtime), messageQueueThread_,