diff --git a/BUCK b/BUCK index 24538162ef03f9..299c6347abda7a 100644 --- a/BUCK +++ b/BUCK @@ -200,6 +200,7 @@ rn_xplat_cxx_library2( visibility = ["PUBLIC"], deps = [ "//xplat/folly:dynamic", + react_native_xplat_target("jserrorhandler:jserrorhandler"), ], ) diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index 047a7c2c4eb4c1..b8fd6f44608722 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -1137,10 +1137,7 @@ - (void)handleError:(NSError *)error // In state 3: do nothing. if (self->_valid && !self->_loading) { - if ([error userInfo][RCTJSRawStackTraceKey]) { - [self.redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]]; - } - + [self showJsError:error onRedBox:self.redBox]; RCTFatal(error); // RN will stop, but let the rest of the app keep going. @@ -1171,15 +1168,20 @@ - (void)handleError:(NSError *)error [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification object:self->_parentBridge userInfo:@{@"bridge" : self, @"error" : error}]; - - if ([error userInfo][RCTJSRawStackTraceKey]) { - [redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]]; - } - + [self showJsError:error onRedBox:redBox]; RCTFatal(error); }); } +- (void)showJsError:(NSError *)error onRedBox:(RCTRedBox *)redbox +{ + if ([error userInfo][RCTJSStackTraceKey]) { + [redbox showErrorMessage:[error localizedDescription] withStack:[error userInfo][RCTJSStackTraceKey]]; + } else if ([error userInfo][RCTJSRawStackTraceKey]) { + [redbox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]]; + } +} + RCT_NOT_IMPLEMENTED(-(instancetype)initWithDelegate : (__unused id)delegate bundleURL : (__unused NSURL *)bundleURL moduleProvider diff --git a/React/CxxModule/RCTCxxUtils.mm b/React/CxxModule/RCTCxxUtils.mm index 03f0ad0c83d8df..6be964fabdf793 100644 --- a/React/CxxModule/RCTCxxUtils.mm +++ b/React/CxxModule/RCTCxxUtils.mm @@ -7,6 +7,9 @@ #import "RCTCxxUtils.h" +#include + +#import #import #import #import @@ -20,8 +23,6 @@ namespace facebook { namespace react { -using facebook::jsi::JSError; - std::vector> createNativeModules(NSArray *modules, RCTBridge *bridge, const std::shared_ptr &instance) { @@ -42,13 +43,34 @@ static NSError *errorWithException(const std::exception &e) { - NSString *msg = @(e.what()); + NSString *msg; NSMutableDictionary *errorInfo = [NSMutableDictionary dictionary]; - const auto *jsError = dynamic_cast(&e); - if (jsError) { - errorInfo[RCTJSRawStackTraceKey] = @(jsError->getStack().c_str()); - msg = [@"Unhandled JS Exception: " stringByAppendingString:msg]; + const auto *jsError = dynamic_cast(&e); + if (jsError && RCTGetParseUnhandledJSErrorStackNatively()) { + MapBuffer errorMap = JsErrorHandler::parseErrorStack(*jsError, true, false); + + NSString *message = [NSString stringWithCString:errorMap.getString(JSErrorHandlerKey::kErrorMessage).c_str() + encoding:[NSString defaultCStringEncoding]]; + auto frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); + NSMutableArray *stack = [[NSMutableArray alloc] init]; + for (auto const &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]; + } + + msg = [@"Unhandled JS Exception: " stringByAppendingString:message]; + errorInfo[RCTJSStackTraceKey] = stack; + errorInfo[RCTJSRawStackTraceKey] = @(e.what()); + } else { + msg = @(e.what()); } NSError *nestedError; diff --git a/ReactCommon/React-Fabric.podspec b/ReactCommon/React-Fabric.podspec index c28eee60d06fbe..b9a13624a51b3e 100644 --- a/ReactCommon/React-Fabric.podspec +++ b/ReactCommon/React-Fabric.podspec @@ -254,8 +254,6 @@ Pod::Spec.new do |s| end s.subspec "mapbuffer" do |ss| - ss.dependency folly_dep_name, folly_version - ss.compiler_flags = folly_compiler_flags ss.source_files = "react/renderer/mapbuffer/**/*.{m,mm,cpp,h}" ss.exclude_files = "react/renderer/mapbuffer/tests" ss.header_dir = "react/renderer/mapbuffer" diff --git a/ReactCommon/jserrorhandler/BUCK b/ReactCommon/jserrorhandler/BUCK index e7ef279bb10674..e0515033095955 100644 --- a/ReactCommon/jserrorhandler/BUCK +++ b/ReactCommon/jserrorhandler/BUCK @@ -23,8 +23,6 @@ rn_xplat_cxx_library( "PUBLIC", ], deps = [ - "//xplat/folly:dynamic", - "//xplat/folly:json", "//xplat/jsi:jsi", react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"), ], diff --git a/ReactCommon/jserrorhandler/JsErrorHandler.cpp b/ReactCommon/jserrorhandler/JsErrorHandler.cpp index 0902e68b2b1644..be74dc5da2b8fe 100644 --- a/ReactCommon/jserrorhandler/JsErrorHandler.cpp +++ b/ReactCommon/jserrorhandler/JsErrorHandler.cpp @@ -17,8 +17,10 @@ namespace react { using facebook::react::JSErrorHandlerKey; -static MapBuffer -parseErrorStack(const jsi::JSError &error, bool isFatal, bool isHermes) { +MapBuffer JsErrorHandler::parseErrorStack( + 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) @@ -99,7 +101,7 @@ 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); + MapBuffer errorMap = JsErrorHandler::parseErrorStack(error, isFatal, false); _jsErrorHandlingFunc(std::move(errorMap)); } diff --git a/ReactCommon/jserrorhandler/JsErrorHandler.h b/ReactCommon/jserrorhandler/JsErrorHandler.h index 90383e17ca7dd8..ca5af45425b17e 100644 --- a/ReactCommon/jserrorhandler/JsErrorHandler.h +++ b/ReactCommon/jserrorhandler/JsErrorHandler.h @@ -26,6 +26,9 @@ class JsErrorHandler { public: using JsErrorHandlingFunc = std::function; + static MapBuffer + parseErrorStack(const jsi::JSError &error, bool isFatal, bool isHermes); + JsErrorHandler(JsErrorHandlingFunc jsErrorHandlingFunc); ~JsErrorHandler(); diff --git a/ReactCommon/jserrorhandler/React-jserrorhandler.podspec b/ReactCommon/jserrorhandler/React-jserrorhandler.podspec new file mode 100644 index 00000000000000..cfb7594ee2a3f0 --- /dev/null +++ b/ReactCommon/jserrorhandler/React-jserrorhandler.podspec @@ -0,0 +1,36 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +Pod::Spec.new do |s| + s.name = "React-jserrorhandler" + s.version = version + s.summary = "-" # TODO + s.homepage = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Facebook, Inc. and its affiliates" + s.platforms = { :ios => "12.4", :tvos => "12.4" } + s.public_header_files = [ "JsErrorHandler.h" ] + s.source = source + s.source_files = "*.{cpp,h}" + s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "" } + s.header_dir = "jserrorhandler" + + s.dependency "React-jsi", version + s.dependency "React-Fabric/mapbuffer", version + +end diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index fedd81b021de81..336f7d517072b1 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -620,6 +620,9 @@ PODS: - React-jsiexecutor (= 1000.0.0) - React-jsinspector (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-jserrorhandler (1000.0.0): + - React-Fabric/mapbuffer (= 1000.0.0) + - React-jsi (= 1000.0.0) - React-jsi (1000.0.0): - hermes-engine - React-jsidynamic (1000.0.0): @@ -796,6 +799,7 @@ DEPENDENCIES: - React-Fabric (from `../../ReactCommon`) - React-graphics (from `../../ReactCommon/react/renderer/graphics`) - React-hermes (from `../../ReactCommon/hermes`) + - React-jserrorhandler (from `../../ReactCommon/jserrorhandler`) - React-jsi (from `../../ReactCommon/jsi`) - React-jsidynamic (from `../../ReactCommon/jsi`) - React-jsiexecutor (from `../../ReactCommon/jsiexecutor`) @@ -879,6 +883,8 @@ EXTERNAL SOURCES: :path: "../../ReactCommon/react/renderer/graphics" React-hermes: :path: "../../ReactCommon/hermes" + React-jserrorhandler: + :path: "../../ReactCommon/jserrorhandler" React-jsi: :path: "../../ReactCommon/jsi" React-jsidynamic: @@ -958,9 +964,10 @@ SPEC CHECKSUMS: React-Core: 3965263aa4b4e1ebf7b4fdb50d2f49ce7bf28f63 React-CoreModules: 675170bccf156da3a3348e04e2036ce401b2010d React-cxxreact: 7276467c246302fedf598cc40d7003896ddb20ba - React-Fabric: 141459e61c825acf02d26ece099acbd9cbd87b99 + React-Fabric: e177589b59ae3ae3dd3340190adcde9cf01ebceb React-graphics: 5ccc9cc0d91794fd42bc1c693e9aea207554bbef React-hermes: 0a5145bae4207edf0def8e28fbcb6a8fd6e806c2 + React-jserrorhandler: f0e756378ad46f5f3448f097a736eb5249de262b React-jsi: c24dbcfdf7ea075138b73372387c7f17c0db56ef React-jsidynamic: 2b14ac1b6d3a1b7daa1e5a424b98de87da981698 React-jsiexecutor: 14e899380e3fe9ca74c4e19727540a03e7574721 diff --git a/scripts/react_native_pods.rb b/scripts/react_native_pods.rb index 73b44c12bab708..326cb50ee4ff71 100644 --- a/scripts/react_native_pods.rb +++ b/scripts/react_native_pods.rb @@ -101,10 +101,10 @@ def use_react_native! ( else setup_jsc!(:react_native_path => prefix, :fabric_enabled => fabric_enabled) end + pod 'React-jserrorhandler', :path => "#{prefix}/ReactCommon/jserrorhandler" pod 'React-jsidynamic', :path => "#{prefix}/ReactCommon/jsi" pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor" pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector" - pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker" pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor" pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger"