Skip to content

Commit 5449148

Browse files
p-sunfacebook-github-bot
authored andcommitted
4/n Display a RedBox with the JS stack (instead of native stack) when an unhandled JS exceptions occurs - Try 2
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} ---- build_on_commit[ios_fbios-arm64-dylibs-jackalope-iphoneos-production_build] Reviewed By: RSNara Differential Revision: D40613108 fbshipit-source-id: f36c3b39a2167402ee3730db8b40b53a0b80aa60
1 parent e69e6f4 commit 5449148

File tree

10 files changed

+97
-26
lines changed

10 files changed

+97
-26
lines changed

BUCK

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ rn_xplat_cxx_library2(
197197
visibility = ["PUBLIC"],
198198
deps = [
199199
"//xplat/folly:dynamic",
200+
react_native_xplat_target("jserrorhandler:jserrorhandler"),
200201
],
201202
)
202203

React/CxxBridge/RCTCxxBridge.mm

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,10 +1137,7 @@ - (void)handleError:(NSError *)error
11371137
// In state 3: do nothing.
11381138

11391139
if (self->_valid && !self->_loading) {
1140-
if ([error userInfo][RCTJSRawStackTraceKey]) {
1141-
[self.redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
1142-
}
1143-
1140+
[self showJsError:error onRedBox:self.redBox];
11441141
RCTFatal(error);
11451142

11461143
// RN will stop, but let the rest of the app keep going.
@@ -1171,15 +1168,20 @@ - (void)handleError:(NSError *)error
11711168
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
11721169
object:self->_parentBridge
11731170
userInfo:@{@"bridge" : self, @"error" : error}];
1174-
1175-
if ([error userInfo][RCTJSRawStackTraceKey]) {
1176-
[redBox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
1177-
}
1178-
1171+
[self showJsError:error onRedBox:redBox];
11791172
RCTFatal(error);
11801173
});
11811174
}
11821175

1176+
- (void)showJsError:(NSError *)error onRedBox:(RCTRedBox *)redbox
1177+
{
1178+
if ([error userInfo][RCTJSStackTraceKey]) {
1179+
[redbox showErrorMessage:[error localizedDescription] withStack:[error userInfo][RCTJSStackTraceKey]];
1180+
} else if ([error userInfo][RCTJSRawStackTraceKey]) {
1181+
[redbox showErrorMessage:[error localizedDescription] withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
1182+
}
1183+
}
1184+
11831185
RCT_NOT_IMPLEMENTED(-(instancetype)initWithDelegate
11841186
: (__unused id<RCTBridgeDelegate>)delegate bundleURL
11851187
: (__unused NSURL *)bundleURL moduleProvider

React/CxxModule/RCTCxxUtils.mm

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
#import "RCTCxxUtils.h"
99

10+
#include <jserrorhandler/JsErrorHandler.h>
11+
12+
#import <React/RCTConstants.h>
1013
#import <React/RCTFollyConvert.h>
1114
#import <React/RCTModuleData.h>
1215
#import <React/RCTUtils.h>
@@ -20,8 +23,6 @@
2023
namespace facebook {
2124
namespace react {
2225

23-
using facebook::jsi::JSError;
24-
2526
std::vector<std::unique_ptr<NativeModule>>
2627
createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const std::shared_ptr<Instance> &instance)
2728
{
@@ -42,13 +43,34 @@
4243

4344
static NSError *errorWithException(const std::exception &e)
4445
{
45-
NSString *msg = @(e.what());
46+
NSString *msg;
4647
NSMutableDictionary *errorInfo = [NSMutableDictionary dictionary];
4748

48-
const auto *jsError = dynamic_cast<const JSError *>(&e);
49-
if (jsError) {
50-
errorInfo[RCTJSRawStackTraceKey] = @(jsError->getStack().c_str());
51-
msg = [@"Unhandled JS Exception: " stringByAppendingString:msg];
49+
const auto *jsError = dynamic_cast<const jsi::JSError *>(&e);
50+
if (jsError && RCTGetParseUnhandledJSErrorStackNatively()) {
51+
MapBuffer errorMap = JsErrorHandler::parseErrorStack(*jsError, true, false);
52+
53+
NSString *message = [NSString stringWithCString:errorMap.getString(JSErrorHandlerKey::kErrorMessage).c_str()
54+
encoding:[NSString defaultCStringEncoding]];
55+
auto frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames);
56+
NSMutableArray *stack = [[NSMutableArray alloc] init];
57+
for (auto const &mapBuffer : frames) {
58+
NSDictionary *frame = @{
59+
@"file" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameFileName).c_str()
60+
encoding:[NSString defaultCStringEncoding]],
61+
@"methodName" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameMethodName).c_str()
62+
encoding:[NSString defaultCStringEncoding]],
63+
@"lineNumber" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameLineNumber)],
64+
@"column" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameColumnNumber)],
65+
};
66+
[stack addObject:frame];
67+
}
68+
69+
msg = [@"Unhandled JS Exception: " stringByAppendingString:message];
70+
errorInfo[RCTJSStackTraceKey] = stack;
71+
errorInfo[RCTJSRawStackTraceKey] = @(e.what());
72+
} else {
73+
msg = @(e.what());
5274
}
5375

5476
NSError *nestedError;

ReactCommon/React-Fabric.podspec

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,6 @@ Pod::Spec.new do |s|
254254
end
255255

256256
s.subspec "mapbuffer" do |ss|
257-
ss.dependency folly_dep_name, folly_version
258-
ss.compiler_flags = folly_compiler_flags
259257
ss.source_files = "react/renderer/mapbuffer/**/*.{m,mm,cpp,h}"
260258
ss.exclude_files = "react/renderer/mapbuffer/tests"
261259
ss.header_dir = "react/renderer/mapbuffer"

ReactCommon/jserrorhandler/BUCK

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "CXX", "react_nat
44
rn_xplat_cxx_library(
55
name = "jserrorhandler",
66
srcs = glob(["*.cpp"]),
7-
header_namespace = "",
8-
exported_headers = {"JsErrorHandler/JsErrorHandler.h": "JsErrorHandler.h"},
7+
header_namespace = "jserrorhandler",
8+
exported_headers = ["JsErrorHandler.h"],
99
compiler_flags = [
1010
"-fexceptions",
1111
"-frtti",
@@ -22,8 +22,6 @@ rn_xplat_cxx_library(
2222
"PUBLIC",
2323
],
2424
deps = [
25-
"//xplat/folly:dynamic",
26-
"//xplat/folly:json",
2725
"//xplat/jsi:jsi",
2826
react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
2927
],

ReactCommon/jserrorhandler/JsErrorHandler.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ namespace react {
1717

1818
using facebook::react::JSErrorHandlerKey;
1919

20-
static MapBuffer
21-
parseErrorStack(const jsi::JSError &error, bool isFatal, bool isHermes) {
20+
MapBuffer JsErrorHandler::parseErrorStack(
21+
const jsi::JSError &error,
22+
bool isFatal,
23+
bool isHermes) {
2224
/**
2325
* This parses the different stack traces and puts them into one format
2426
* This borrows heavily from TraceKit (https://github.com/occ/TraceKit)
@@ -99,7 +101,7 @@ JsErrorHandler::~JsErrorHandler() {}
99101
void JsErrorHandler::handleJsError(const jsi::JSError &error, bool isFatal) {
100102
// TODO: Current error parsing works and is stable. Can investigate using
101103
// REGEX_HERMES to get additional Hermes data, though it requires JS setup.
102-
MapBuffer errorMap = parseErrorStack(error, isFatal, false);
104+
MapBuffer errorMap = JsErrorHandler::parseErrorStack(error, isFatal, false);
103105
_jsErrorHandlingFunc(std::move(errorMap));
104106
}
105107

ReactCommon/jserrorhandler/JsErrorHandler.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class JsErrorHandler {
2626
public:
2727
using JsErrorHandlingFunc = std::function<void(MapBuffer errorMap)>;
2828

29+
static MapBuffer
30+
parseErrorStack(const jsi::JSError &error, bool isFatal, bool isHermes);
31+
2932
JsErrorHandler(JsErrorHandlingFunc jsErrorHandlingFunc);
3033
~JsErrorHandler();
3134

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
require "json"
7+
8+
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
9+
version = package['version']
10+
11+
source = { :git => 'https://github.com/facebook/react-native.git' }
12+
if version == '1000.0.0'
13+
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
14+
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
15+
else
16+
source[:tag] = "v#{version}"
17+
end
18+
19+
Pod::Spec.new do |s|
20+
s.name = "React-jserrorhandler"
21+
s.version = version
22+
s.summary = "-" # TODO
23+
s.homepage = "https://reactnative.dev/"
24+
s.license = package["license"]
25+
s.author = "Facebook, Inc. and its affiliates"
26+
s.platforms = { :ios => "12.4", :tvos => "12.4" }
27+
s.public_header_files = [ "JsErrorHandler.h" ]
28+
s.source = source
29+
s.source_files = "*.{cpp,h}"
30+
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "" }
31+
s.header_dir = "jserrorhandler"
32+
33+
s.dependency "React-jsi", version
34+
s.dependency "React-Fabric/mapbuffer", version
35+
36+
end

packages/rn-tester/Podfile.lock

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,9 @@ PODS:
620620
- React-jsiexecutor (= 1000.0.0)
621621
- React-jsinspector (= 1000.0.0)
622622
- React-perflogger (= 1000.0.0)
623+
- React-jserrorhandler (1000.0.0):
624+
- React-Fabric/mapbuffer (= 1000.0.0)
625+
- React-jsi (= 1000.0.0)
623626
- React-jsi (1000.0.0):
624627
- hermes-engine
625628
- React-jsidynamic (1000.0.0):
@@ -796,6 +799,7 @@ DEPENDENCIES:
796799
- React-Fabric (from `../../ReactCommon`)
797800
- React-graphics (from `../../ReactCommon/react/renderer/graphics`)
798801
- React-hermes (from `../../ReactCommon/hermes`)
802+
- React-jserrorhandler (from `../../ReactCommon/jserrorhandler`)
799803
- React-jsi (from `../../ReactCommon/jsi`)
800804
- React-jsidynamic (from `../../ReactCommon/jsi`)
801805
- React-jsiexecutor (from `../../ReactCommon/jsiexecutor`)
@@ -879,6 +883,8 @@ EXTERNAL SOURCES:
879883
:path: "../../ReactCommon/react/renderer/graphics"
880884
React-hermes:
881885
:path: "../../ReactCommon/hermes"
886+
React-jserrorhandler:
887+
:path: "../../ReactCommon/jserrorhandler"
882888
React-jsi:
883889
:path: "../../ReactCommon/jsi"
884890
React-jsidynamic:
@@ -958,9 +964,10 @@ SPEC CHECKSUMS:
958964
React-Core: 3965263aa4b4e1ebf7b4fdb50d2f49ce7bf28f63
959965
React-CoreModules: 675170bccf156da3a3348e04e2036ce401b2010d
960966
React-cxxreact: 7276467c246302fedf598cc40d7003896ddb20ba
961-
React-Fabric: 141459e61c825acf02d26ece099acbd9cbd87b99
967+
React-Fabric: e177589b59ae3ae3dd3340190adcde9cf01ebceb
962968
React-graphics: 5ccc9cc0d91794fd42bc1c693e9aea207554bbef
963969
React-hermes: 0a5145bae4207edf0def8e28fbcb6a8fd6e806c2
970+
React-jserrorhandler: f0e756378ad46f5f3448f097a736eb5249de262b
964971
React-jsi: c24dbcfdf7ea075138b73372387c7f17c0db56ef
965972
React-jsidynamic: 2b14ac1b6d3a1b7daa1e5a424b98de87da981698
966973
React-jsiexecutor: 14e899380e3fe9ca74c4e19727540a03e7574721

scripts/react_native_pods.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def use_react_native! (
101101
else
102102
setup_jsc!(:react_native_path => prefix, :fabric_enabled => fabric_enabled)
103103
end
104+
pod 'React-jserrorhandler', :path => "#{prefix}/ReactCommon/jserrorhandler"
105+
104106
pod 'React-jsidynamic', :path => "#{prefix}/ReactCommon/jsi"
105107
pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor"
106108
pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector"

0 commit comments

Comments
 (0)