Skip to content

File tree

7 files changed

+1643
-102
lines changed

7 files changed

+1643
-102
lines changed

packages/react-native/ReactCommon/jserrorhandler/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ set(CMAKE_VERBOSE_MAKEFILE on)
88

99
add_compile_options(-std=c++20)
1010

11-
file(GLOB_RECURSE js_error_handler_SRC CONFIGURE_DEPENDS *.cpp)
11+
file(GLOB js_error_handler_SRC CONFIGURE_DEPENDS *.cpp)
1212
add_library(
1313
jserrorhandler
1414
OBJECT

packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

Lines changed: 90 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,33 @@
88
#include "JsErrorHandler.h"
99
#include <cxxreact/ErrorUtils.h>
1010
#include <glog/logging.h>
11-
#include <regex>
12-
#include <sstream>
1311
#include <string>
14-
#include <vector>
12+
#include "StackTraceParser.h"
13+
14+
using namespace facebook;
1515

1616
namespace {
1717
std::string quote(const std::string& view) {
1818
return "\"" + view + "\"";
1919
}
20+
21+
int nextExceptionId() {
22+
static int exceptionId = 0;
23+
return exceptionId++;
24+
}
25+
26+
bool isLooselyNull(const jsi::Value& value) {
27+
return value.isNull() || value.isUndefined();
28+
}
29+
30+
bool isEmptyString(jsi::Runtime& runtime, const jsi::Value& value) {
31+
return jsi::Value::strictEquals(
32+
runtime, value, jsi::String::createFromUtf8(runtime, ""));
33+
}
34+
35+
std::string stringifyToCpp(jsi::Runtime& runtime, const jsi::Value& value) {
36+
return value.toString(runtime).utf8(runtime);
37+
}
2038
} // namespace
2139

2240
namespace facebook::react {
@@ -65,97 +83,6 @@ std::ostream& operator<<(
6583
return os;
6684
}
6785

68-
// TODO(T198763073): Migrate away from std::regex in this function
69-
static JsErrorHandler::ParsedError parseErrorStack(
70-
jsi::Runtime& runtime,
71-
const jsi::JSError& error,
72-
bool isFatal,
73-
bool isHermes) {
74-
/**
75-
* This parses the different stack traces and puts them into one format
76-
* This borrows heavily from TraceKit (https://github.com/occ/TraceKit)
77-
* This is the same regex from stacktrace-parser.js.
78-
*/
79-
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
80-
const std::regex REGEX_CHROME(
81-
R"(^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$)");
82-
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
83-
const std::regex REGEX_GECKO(
84-
R"(^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)");
85-
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
86-
const std::regex REGEX_NODE(
87-
R"(^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$)");
88-
89-
// Capture groups for Hermes (from parseHermesStack.js):
90-
// 1. function name
91-
// 2. is this a native stack frame?
92-
// 3. is this a bytecode address or a source location?
93-
// 4. source URL (filename)
94-
// 5. line number (1 based)
95-
// 6. column number (1 based) or virtual offset (0 based)
96-
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
97-
const std::regex REGEX_HERMES(
98-
R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)");
99-
100-
std::string line;
101-
std::stringstream strStream(error.getStack());
102-
103-
std::vector<JsErrorHandler::ParsedError::StackFrame> frames;
104-
105-
while (std::getline(strStream, line, '\n')) {
106-
auto searchResults = std::smatch{};
107-
108-
if (isHermes) {
109-
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
110-
if (std::regex_search(line, searchResults, REGEX_HERMES)) {
111-
std::string str2 = std::string(searchResults[2]);
112-
if (str2.compare("native")) {
113-
frames.push_back({
114-
.file = std::string(searchResults[4]),
115-
.methodName = std::string(searchResults[1]),
116-
.lineNumber = std::stoi(searchResults[5]),
117-
.column = std::stoi(searchResults[6]),
118-
});
119-
}
120-
}
121-
} else {
122-
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
123-
if (std::regex_search(line, searchResults, REGEX_GECKO)) {
124-
frames.push_back({
125-
.file = std::string(searchResults[3]),
126-
.methodName = std::string(searchResults[1]),
127-
.lineNumber = std::stoi(searchResults[4]),
128-
.column = std::stoi(searchResults[5]),
129-
});
130-
} else if (
131-
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
132-
std::regex_search(line, searchResults, REGEX_CHROME) ||
133-
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
134-
std::regex_search(line, searchResults, REGEX_NODE)) {
135-
frames.push_back({
136-
.file = std::string(searchResults[2]),
137-
.methodName = std::string(searchResults[1]),
138-
.lineNumber = std::stoi(searchResults[3]),
139-
.column = std::stoi(searchResults[4]),
140-
});
141-
} else {
142-
continue;
143-
}
144-
}
145-
}
146-
147-
return {
148-
.message = "EarlyJsError: " + error.getMessage(),
149-
.originalMessage = std::nullopt,
150-
.name = std::nullopt,
151-
.componentStack = std::nullopt,
152-
.stack = std::move(frames),
153-
.id = 0,
154-
.isFatal = isFatal,
155-
.extraData = jsi::Object(runtime),
156-
};
157-
}
158-
15986
JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError)
16087
: _onJsError(std::move(onJsError)),
16188
_hasHandledFatalError(false){
@@ -183,8 +110,75 @@ void JsErrorHandler::handleFatalError(
183110
<< "Original js error: " << error.getMessage() << std::endl;
184111
}
185112
}
186-
// This is a hacky way to get Hermes stack trace.
187-
ParsedError parsedError = parseErrorStack(runtime, error, true, false);
113+
114+
auto message = error.getMessage();
115+
auto errorObj = error.value().getObject(runtime);
116+
auto componentStackValue = errorObj.getProperty(runtime, "componentStack");
117+
if (!isLooselyNull(componentStackValue)) {
118+
message += "\n" + stringifyToCpp(runtime, componentStackValue);
119+
}
120+
121+
auto nameValue = errorObj.getProperty(runtime, "name");
122+
auto name = (isLooselyNull(nameValue) || isEmptyString(runtime, nameValue))
123+
? std::nullopt
124+
: std::optional(stringifyToCpp(runtime, nameValue));
125+
126+
if (name && !message.starts_with(*name + ": ")) {
127+
message = *name + ": " + message;
128+
}
129+
130+
auto jsEngineValue = errorObj.getProperty(runtime, "jsEngine");
131+
132+
if (!isLooselyNull(jsEngineValue)) {
133+
message += ", js engine: " + stringifyToCpp(runtime, jsEngineValue);
134+
}
135+
136+
// TODO: What about spreading in decoratedExtraDataKey?
137+
auto extraData = jsi::Object(runtime);
138+
extraData.setProperty(runtime, "jsEngine", jsEngineValue);
139+
extraData.setProperty(runtime, "rawStack", error.getStack());
140+
141+
auto cause = errorObj.getProperty(runtime, "cause");
142+
if (cause.isObject()) {
143+
auto causeObj = cause.asObject(runtime);
144+
// TODO: Consider just forwarding all properties. For now, just forward the
145+
// stack properties to maintain symmetry with js pipeline
146+
auto stackSymbols = causeObj.getProperty(runtime, "stackSymbols");
147+
extraData.setProperty(runtime, "stackSymbols", stackSymbols);
148+
149+
auto stackReturnAddresses =
150+
causeObj.getProperty(runtime, "stackReturnAddresses");
151+
extraData.setProperty(
152+
runtime, "stackReturnAddresses", stackReturnAddresses);
153+
154+
auto stackElements = causeObj.getProperty(runtime, "stackElements");
155+
extraData.setProperty(runtime, "stackElements", stackElements);
156+
}
157+
158+
auto originalMessage = message == error.getMessage()
159+
? std::nullopt
160+
: std::optional(error.getMessage());
161+
162+
auto componentStack = !componentStackValue.isString()
163+
? std::nullopt
164+
: std::optional(componentStackValue.asString(runtime).utf8(runtime));
165+
166+
auto isHermes = runtime.global().hasProperty(runtime, "HermesInternal");
167+
auto stackFrames = StackTraceParser::parse(isHermes, error.getStack());
168+
169+
auto id = nextExceptionId();
170+
171+
ParsedError parsedError = {
172+
.message = "EarlyJsError: " + message,
173+
.originalMessage = originalMessage,
174+
.name = name,
175+
.componentStack = componentStack,
176+
.stack = stackFrames,
177+
.id = id,
178+
.isFatal = true,
179+
.extraData = std::move(extraData),
180+
};
181+
188182
_onJsError(runtime, parsedError);
189183
}
190184

packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ folly_version = folly_config[:version]
2222
folly_dep_name = folly_config[:dep_name]
2323

2424
boost_config = get_boost_config()
25-
boost_compiler_flags = boost_config[:compiler_flags]
25+
boost_compiler_flags = boost_config[:compiler_flags]
2626
react_native_path = ".."
2727

2828
Pod::Spec.new do |s|
@@ -35,7 +35,7 @@ Pod::Spec.new do |s|
3535
s.platforms = min_supported_versions
3636
s.source = source
3737
s.header_dir = "jserrorhandler"
38-
s.source_files = "JsErrorHandler.{cpp,h}"
38+
s.source_files = "JsErrorHandler.{cpp,h}", "StackTraceParser.{cpp,h}"
3939
s.pod_target_xcconfig = {
4040
"USE_HEADERMAP" => "YES",
4141
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard()
@@ -52,7 +52,7 @@ Pod::Spec.new do |s|
5252
s.dependency "React-cxxreact"
5353
s.dependency "glog"
5454
add_dependency(s, "React-debug")
55-
55+
5656
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
5757
s.dependency 'hermes-engine'
5858
end

0 commit comments

Comments
 (0)