4/n Display a RedBox with the JS stack (instead of native stack) when an unhandled JS exceptions occurs
Summary: Changelog: [iOS][Changed] Display a RedBox with the JS stack (instead of native stack) when an unhandled JS exceptions occurs ----- # A0) Bridge mode, Unhandled Exception, FBiOS Unhandled exception goes to FBReactModule, but the JS Stack is not parsed correctly. https://www.internalfb.com/code/fbsource/[312d5cbdd7278247a84619786b12a44c4400fcc0]/fbobjc/Apps/Wilde/FBReactModule2/FBReactModuleAPI/FBReactModuleAPI/Exported/FBReactModule.mm?lines=1488%2C1493 See `****** FBReactModule handleFatalError` in the logs P539306390, and compare with correct behavior in (A1) in the Test Plan. https://pxl.cl/2h6h3 {F782257996} ----- # A) Before diff, rn-tester Open to rn-tester -> APIs -> Crash Examples -> JS Crash. Set `RCTParseUnhandledJSErrorStackNatively` to YES. https://www.internalfb.com/code/fbsource/[98880e52ee78be3614e5d9a2ce3292f6a7b5e413]/xplat/js/react-native-github/React/Base/RCTConstants.m?lines=73 {F783395297} Reviewed By: sammy-SC Differential Revision: D40387938 fbshipit-source-id: 2abea657476d3bf61ad5b1c643f129e44c6f3f35
This commit is contained in:
Родитель
466ba91657
Коммит
ff398e4e26
1
BUCK
1
BUCK
|
@ -200,6 +200,7 @@ rn_xplat_cxx_library2(
|
||||||
visibility = ["PUBLIC"],
|
visibility = ["PUBLIC"],
|
||||||
deps = [
|
deps = [
|
||||||
"//xplat/folly:dynamic",
|
"//xplat/folly:dynamic",
|
||||||
|
react_native_xplat_target("jserrorhandler:jserrorhandler"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1137,10 +1137,7 @@ struct RCTInstanceCallback : public InstanceCallback {
|
||||||
// In state 3: do nothing.
|
// In state 3: do nothing.
|
||||||
|
|
||||||
if (self->_valid && !self->_loading) {
|
if (self->_valid && !self->_loading) {
|
||||||
if ([error userInfo][RCTJSRawStackTraceKey]) {
|
[self showJsError:error onRedBox:self.redBox];
|
||||||
[self.redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
|
|
||||||
}
|
|
||||||
|
|
||||||
RCTFatal(error);
|
RCTFatal(error);
|
||||||
|
|
||||||
// RN will stop, but let the rest of the app keep going.
|
// RN will stop, but let the rest of the app keep going.
|
||||||
|
@ -1171,15 +1168,20 @@ struct RCTInstanceCallback : public InstanceCallback {
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
|
||||||
object:self->_parentBridge
|
object:self->_parentBridge
|
||||||
userInfo:@{@"bridge" : self, @"error" : error}];
|
userInfo:@{@"bridge" : self, @"error" : error}];
|
||||||
|
[self showJsError:error onRedBox:redBox];
|
||||||
if ([error userInfo][RCTJSRawStackTraceKey]) {
|
|
||||||
[redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
|
|
||||||
}
|
|
||||||
|
|
||||||
RCTFatal(error);
|
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
|
RCT_NOT_IMPLEMENTED(-(instancetype)initWithDelegate
|
||||||
: (__unused id<RCTBridgeDelegate>)delegate bundleURL
|
: (__unused id<RCTBridgeDelegate>)delegate bundleURL
|
||||||
: (__unused NSURL *)bundleURL moduleProvider
|
: (__unused NSURL *)bundleURL moduleProvider
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
|
|
||||||
#import "RCTCxxUtils.h"
|
#import "RCTCxxUtils.h"
|
||||||
|
|
||||||
|
#include <JsErrorHandler/JsErrorHandler.h>
|
||||||
|
|
||||||
|
#import <React/RCTConstants.h>
|
||||||
#import <React/RCTFollyConvert.h>
|
#import <React/RCTFollyConvert.h>
|
||||||
#import <React/RCTModuleData.h>
|
#import <React/RCTModuleData.h>
|
||||||
#import <React/RCTUtils.h>
|
#import <React/RCTUtils.h>
|
||||||
|
@ -20,8 +23,6 @@
|
||||||
namespace facebook {
|
namespace facebook {
|
||||||
namespace react {
|
namespace react {
|
||||||
|
|
||||||
using facebook::jsi::JSError;
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<NativeModule>>
|
std::vector<std::unique_ptr<NativeModule>>
|
||||||
createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const std::shared_ptr<Instance> &instance)
|
createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const std::shared_ptr<Instance> &instance)
|
||||||
{
|
{
|
||||||
|
@ -42,13 +43,34 @@ createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const
|
||||||
|
|
||||||
static NSError *errorWithException(const std::exception &e)
|
static NSError *errorWithException(const std::exception &e)
|
||||||
{
|
{
|
||||||
NSString *msg = @(e.what());
|
NSString *msg;
|
||||||
NSMutableDictionary *errorInfo = [NSMutableDictionary dictionary];
|
NSMutableDictionary *errorInfo = [NSMutableDictionary dictionary];
|
||||||
|
|
||||||
const auto *jsError = dynamic_cast<const JSError *>(&e);
|
const auto *jsError = dynamic_cast<const jsi::JSError *>(&e);
|
||||||
if (jsError) {
|
if (jsError && RCTGetParseUnhandledJSErrorStackNatively()) {
|
||||||
errorInfo[RCTJSRawStackTraceKey] = @(jsError->getStack().c_str());
|
MapBuffer errorMap = JsErrorHandler::parseErrorStack(*jsError, true, false);
|
||||||
msg = [@"Unhandled JS Exception: " stringByAppendingString:msg];
|
|
||||||
|
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;
|
NSError *nestedError;
|
||||||
|
|
|
@ -254,8 +254,6 @@ Pod::Spec.new do |s|
|
||||||
end
|
end
|
||||||
|
|
||||||
s.subspec "mapbuffer" do |ss|
|
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.source_files = "react/renderer/mapbuffer/**/*.{m,mm,cpp,h}"
|
||||||
ss.exclude_files = "react/renderer/mapbuffer/tests"
|
ss.exclude_files = "react/renderer/mapbuffer/tests"
|
||||||
ss.header_dir = "react/renderer/mapbuffer"
|
ss.header_dir = "react/renderer/mapbuffer"
|
||||||
|
|
|
@ -23,8 +23,6 @@ rn_xplat_cxx_library(
|
||||||
"PUBLIC",
|
"PUBLIC",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//xplat/folly:dynamic",
|
|
||||||
"//xplat/folly:json",
|
|
||||||
"//xplat/jsi:jsi",
|
"//xplat/jsi:jsi",
|
||||||
react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
|
react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
|
||||||
],
|
],
|
||||||
|
|
|
@ -17,8 +17,10 @@ namespace react {
|
||||||
|
|
||||||
using facebook::react::JSErrorHandlerKey;
|
using facebook::react::JSErrorHandlerKey;
|
||||||
|
|
||||||
static MapBuffer
|
MapBuffer JsErrorHandler::parseErrorStack(
|
||||||
parseErrorStack(const jsi::JSError &error, bool isFatal, bool isHermes) {
|
const jsi::JSError &error,
|
||||||
|
bool isFatal,
|
||||||
|
bool isHermes) {
|
||||||
/**
|
/**
|
||||||
* This parses the different stack traces and puts them into one format
|
* This parses the different stack traces and puts them into one format
|
||||||
* This borrows heavily from TraceKit (https://github.com/occ/TraceKit)
|
* 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) {
|
void JsErrorHandler::handleJsError(const jsi::JSError &error, bool isFatal) {
|
||||||
// TODO: Current error parsing works and is stable. Can investigate using
|
// TODO: Current error parsing works and is stable. Can investigate using
|
||||||
// REGEX_HERMES to get additional Hermes data, though it requires JS setup.
|
// 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));
|
_jsErrorHandlingFunc(std::move(errorMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,9 @@ class JsErrorHandler {
|
||||||
public:
|
public:
|
||||||
using JsErrorHandlingFunc = std::function<void(MapBuffer errorMap)>;
|
using JsErrorHandlingFunc = std::function<void(MapBuffer errorMap)>;
|
||||||
|
|
||||||
|
static MapBuffer
|
||||||
|
parseErrorStack(const jsi::JSError &error, bool isFatal, bool isHermes);
|
||||||
|
|
||||||
JsErrorHandler(JsErrorHandlingFunc jsErrorHandlingFunc);
|
JsErrorHandler(JsErrorHandlingFunc jsErrorHandlingFunc);
|
||||||
~JsErrorHandler();
|
~JsErrorHandler();
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -620,6 +620,9 @@ PODS:
|
||||||
- React-jsiexecutor (= 1000.0.0)
|
- React-jsiexecutor (= 1000.0.0)
|
||||||
- React-jsinspector (= 1000.0.0)
|
- React-jsinspector (= 1000.0.0)
|
||||||
- React-perflogger (= 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):
|
- React-jsi (1000.0.0):
|
||||||
- hermes-engine
|
- hermes-engine
|
||||||
- React-jsidynamic (1000.0.0):
|
- React-jsidynamic (1000.0.0):
|
||||||
|
@ -796,6 +799,7 @@ DEPENDENCIES:
|
||||||
- React-Fabric (from `../../ReactCommon`)
|
- React-Fabric (from `../../ReactCommon`)
|
||||||
- React-graphics (from `../../ReactCommon/react/renderer/graphics`)
|
- React-graphics (from `../../ReactCommon/react/renderer/graphics`)
|
||||||
- React-hermes (from `../../ReactCommon/hermes`)
|
- React-hermes (from `../../ReactCommon/hermes`)
|
||||||
|
- React-jserrorhandler (from `../../ReactCommon/jserrorhandler`)
|
||||||
- React-jsi (from `../../ReactCommon/jsi`)
|
- React-jsi (from `../../ReactCommon/jsi`)
|
||||||
- React-jsidynamic (from `../../ReactCommon/jsi`)
|
- React-jsidynamic (from `../../ReactCommon/jsi`)
|
||||||
- React-jsiexecutor (from `../../ReactCommon/jsiexecutor`)
|
- React-jsiexecutor (from `../../ReactCommon/jsiexecutor`)
|
||||||
|
@ -879,6 +883,8 @@ EXTERNAL SOURCES:
|
||||||
:path: "../../ReactCommon/react/renderer/graphics"
|
:path: "../../ReactCommon/react/renderer/graphics"
|
||||||
React-hermes:
|
React-hermes:
|
||||||
:path: "../../ReactCommon/hermes"
|
:path: "../../ReactCommon/hermes"
|
||||||
|
React-jserrorhandler:
|
||||||
|
:path: "../../ReactCommon/jserrorhandler"
|
||||||
React-jsi:
|
React-jsi:
|
||||||
:path: "../../ReactCommon/jsi"
|
:path: "../../ReactCommon/jsi"
|
||||||
React-jsidynamic:
|
React-jsidynamic:
|
||||||
|
@ -958,9 +964,10 @@ SPEC CHECKSUMS:
|
||||||
React-Core: 3965263aa4b4e1ebf7b4fdb50d2f49ce7bf28f63
|
React-Core: 3965263aa4b4e1ebf7b4fdb50d2f49ce7bf28f63
|
||||||
React-CoreModules: 675170bccf156da3a3348e04e2036ce401b2010d
|
React-CoreModules: 675170bccf156da3a3348e04e2036ce401b2010d
|
||||||
React-cxxreact: 7276467c246302fedf598cc40d7003896ddb20ba
|
React-cxxreact: 7276467c246302fedf598cc40d7003896ddb20ba
|
||||||
React-Fabric: 141459e61c825acf02d26ece099acbd9cbd87b99
|
React-Fabric: e177589b59ae3ae3dd3340190adcde9cf01ebceb
|
||||||
React-graphics: 5ccc9cc0d91794fd42bc1c693e9aea207554bbef
|
React-graphics: 5ccc9cc0d91794fd42bc1c693e9aea207554bbef
|
||||||
React-hermes: 0a5145bae4207edf0def8e28fbcb6a8fd6e806c2
|
React-hermes: 0a5145bae4207edf0def8e28fbcb6a8fd6e806c2
|
||||||
|
React-jserrorhandler: f0e756378ad46f5f3448f097a736eb5249de262b
|
||||||
React-jsi: c24dbcfdf7ea075138b73372387c7f17c0db56ef
|
React-jsi: c24dbcfdf7ea075138b73372387c7f17c0db56ef
|
||||||
React-jsidynamic: 2b14ac1b6d3a1b7daa1e5a424b98de87da981698
|
React-jsidynamic: 2b14ac1b6d3a1b7daa1e5a424b98de87da981698
|
||||||
React-jsiexecutor: 14e899380e3fe9ca74c4e19727540a03e7574721
|
React-jsiexecutor: 14e899380e3fe9ca74c4e19727540a03e7574721
|
||||||
|
|
|
@ -101,10 +101,10 @@ def use_react_native! (
|
||||||
else
|
else
|
||||||
setup_jsc!(:react_native_path => prefix, :fabric_enabled => fabric_enabled)
|
setup_jsc!(:react_native_path => prefix, :fabric_enabled => fabric_enabled)
|
||||||
end
|
end
|
||||||
|
pod 'React-jserrorhandler', :path => "#{prefix}/ReactCommon/jserrorhandler"
|
||||||
pod 'React-jsidynamic', :path => "#{prefix}/ReactCommon/jsi"
|
pod 'React-jsidynamic', :path => "#{prefix}/ReactCommon/jsi"
|
||||||
pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor"
|
pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor"
|
||||||
pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector"
|
pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector"
|
||||||
|
|
||||||
pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker"
|
pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker"
|
||||||
pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor"
|
pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor"
|
||||||
pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger"
|
pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger"
|
||||||
|
|
Загрузка…
Ссылка в новой задаче