mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Align earlyjs c++ stack trace parsing with js (#46894)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46894 This diff re-implements [js error stack trace parsing](https://github.com/facebook/react-native/blob/86cac6836502aaeb5c894bff6427e837c52c09e0/packages/react-native/Libraries/Core/Devtools/parseErrorStack.js#L41-L57) in c++. Details: - I migrated [stacktrace-parser](https://github.com/errwischt/stacktrace-parser/blob/ad379de5e5ac056012bbeb12923cf502aefe4710/src/stack-trace-parser.js#L7) - I migrated [parseHermesStack.js](https://github.com/facebook/react-native/blob/86cac6836502aaeb5c894bff6427e837c52c09e0/packages/react-native/Libraries/Core/Devtools/parseHermesStack.js#L82) I also migrated all their tests to c++: - [stacktrace-parser tests](https://github.com/errwischt/stacktrace-parser/blob/ad379de5e5ac056012bbeb12923cf502aefe4710/test/stack-trace-parser.spec.js#L5) - [parseHermesStack tests](https://github.com/facebook/react-native/blob/86cac6836502aaeb5c894bff6427e837c52c09e0/packages/react-native/Libraries/Core/Devtools/__tests__/parseHermesStack-test.js#L16) Changelog: [Internal] Reviewed By: javache, NickGerleman Differential Revision: D63659013 fbshipit-source-id: 146acc9db7d4e8907b9fa1d42e4979133ef020f6
This commit is contained in:
committed by
Facebook GitHub Bot
parent
e4645d033a
commit
934af0c59e
@@ -8,7 +8,7 @@ set(CMAKE_VERBOSE_MAKEFILE on)
|
||||
|
||||
add_compile_options(-std=c++20)
|
||||
|
||||
file(GLOB_RECURSE js_error_handler_SRC CONFIGURE_DEPENDS *.cpp)
|
||||
file(GLOB js_error_handler_SRC CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(
|
||||
jserrorhandler
|
||||
OBJECT
|
||||
|
||||
@@ -8,15 +8,33 @@
|
||||
#include "JsErrorHandler.h"
|
||||
#include <cxxreact/ErrorUtils.h>
|
||||
#include <glog/logging.h>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "StackTraceParser.h"
|
||||
|
||||
using namespace facebook;
|
||||
|
||||
namespace {
|
||||
std::string quote(const std::string& view) {
|
||||
return "\"" + view + "\"";
|
||||
}
|
||||
|
||||
int nextExceptionId() {
|
||||
static int exceptionId = 0;
|
||||
return exceptionId++;
|
||||
}
|
||||
|
||||
bool isLooselyNull(const jsi::Value& value) {
|
||||
return value.isNull() || value.isUndefined();
|
||||
}
|
||||
|
||||
bool isEmptyString(jsi::Runtime& runtime, const jsi::Value& value) {
|
||||
return jsi::Value::strictEquals(
|
||||
runtime, value, jsi::String::createFromUtf8(runtime, ""));
|
||||
}
|
||||
|
||||
std::string stringifyToCpp(jsi::Runtime& runtime, const jsi::Value& value) {
|
||||
return value.toString(runtime).utf8(runtime);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace facebook::react {
|
||||
@@ -65,97 +83,6 @@ std::ostream& operator<<(
|
||||
return os;
|
||||
}
|
||||
|
||||
// TODO(T198763073): Migrate away from std::regex in this function
|
||||
static JsErrorHandler::ParsedError parseErrorStack(
|
||||
jsi::Runtime& runtime,
|
||||
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)
|
||||
* This is the same regex from stacktrace-parser.js.
|
||||
*/
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
const std::regex REGEX_CHROME(
|
||||
R"(^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$)");
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
const std::regex REGEX_GECKO(
|
||||
R"(^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)");
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
const std::regex REGEX_NODE(
|
||||
R"(^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$)");
|
||||
|
||||
// Capture groups for Hermes (from parseHermesStack.js):
|
||||
// 1. function name
|
||||
// 2. is this a native stack frame?
|
||||
// 3. is this a bytecode address or a source location?
|
||||
// 4. source URL (filename)
|
||||
// 5. line number (1 based)
|
||||
// 6. column number (1 based) or virtual offset (0 based)
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
const std::regex REGEX_HERMES(
|
||||
R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)");
|
||||
|
||||
std::string line;
|
||||
std::stringstream strStream(error.getStack());
|
||||
|
||||
std::vector<JsErrorHandler::ParsedError::StackFrame> frames;
|
||||
|
||||
while (std::getline(strStream, line, '\n')) {
|
||||
auto searchResults = std::smatch{};
|
||||
|
||||
if (isHermes) {
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
if (std::regex_search(line, searchResults, REGEX_HERMES)) {
|
||||
std::string str2 = std::string(searchResults[2]);
|
||||
if (str2.compare("native")) {
|
||||
frames.push_back({
|
||||
.file = std::string(searchResults[4]),
|
||||
.methodName = std::string(searchResults[1]),
|
||||
.lineNumber = std::stoi(searchResults[5]),
|
||||
.column = std::stoi(searchResults[6]),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
if (std::regex_search(line, searchResults, REGEX_GECKO)) {
|
||||
frames.push_back({
|
||||
.file = std::string(searchResults[3]),
|
||||
.methodName = std::string(searchResults[1]),
|
||||
.lineNumber = std::stoi(searchResults[4]),
|
||||
.column = std::stoi(searchResults[5]),
|
||||
});
|
||||
} else if (
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
std::regex_search(line, searchResults, REGEX_CHROME) ||
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
std::regex_search(line, searchResults, REGEX_NODE)) {
|
||||
frames.push_back({
|
||||
.file = std::string(searchResults[2]),
|
||||
.methodName = std::string(searchResults[1]),
|
||||
.lineNumber = std::stoi(searchResults[3]),
|
||||
.column = std::stoi(searchResults[4]),
|
||||
});
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
.message = "EarlyJsError: " + error.getMessage(),
|
||||
.originalMessage = std::nullopt,
|
||||
.name = std::nullopt,
|
||||
.componentStack = std::nullopt,
|
||||
.stack = std::move(frames),
|
||||
.id = 0,
|
||||
.isFatal = isFatal,
|
||||
.extraData = jsi::Object(runtime),
|
||||
};
|
||||
}
|
||||
|
||||
JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError)
|
||||
: _onJsError(std::move(onJsError)),
|
||||
_hasHandledFatalError(false){
|
||||
@@ -183,8 +110,75 @@ void JsErrorHandler::handleFatalError(
|
||||
<< "Original js error: " << error.getMessage() << std::endl;
|
||||
}
|
||||
}
|
||||
// This is a hacky way to get Hermes stack trace.
|
||||
ParsedError parsedError = parseErrorStack(runtime, error, true, false);
|
||||
|
||||
auto message = error.getMessage();
|
||||
auto errorObj = error.value().getObject(runtime);
|
||||
auto componentStackValue = errorObj.getProperty(runtime, "componentStack");
|
||||
if (!isLooselyNull(componentStackValue)) {
|
||||
message += "\n" + stringifyToCpp(runtime, componentStackValue);
|
||||
}
|
||||
|
||||
auto nameValue = errorObj.getProperty(runtime, "name");
|
||||
auto name = (isLooselyNull(nameValue) || isEmptyString(runtime, nameValue))
|
||||
? std::nullopt
|
||||
: std::optional(stringifyToCpp(runtime, nameValue));
|
||||
|
||||
if (name && !message.starts_with(*name + ": ")) {
|
||||
message = *name + ": " + message;
|
||||
}
|
||||
|
||||
auto jsEngineValue = errorObj.getProperty(runtime, "jsEngine");
|
||||
|
||||
if (!isLooselyNull(jsEngineValue)) {
|
||||
message += ", js engine: " + stringifyToCpp(runtime, jsEngineValue);
|
||||
}
|
||||
|
||||
// TODO: What about spreading in decoratedExtraDataKey?
|
||||
auto extraData = jsi::Object(runtime);
|
||||
extraData.setProperty(runtime, "jsEngine", jsEngineValue);
|
||||
extraData.setProperty(runtime, "rawStack", error.getStack());
|
||||
|
||||
auto cause = errorObj.getProperty(runtime, "cause");
|
||||
if (cause.isObject()) {
|
||||
auto causeObj = cause.asObject(runtime);
|
||||
// TODO: Consider just forwarding all properties. For now, just forward the
|
||||
// stack properties to maintain symmetry with js pipeline
|
||||
auto stackSymbols = causeObj.getProperty(runtime, "stackSymbols");
|
||||
extraData.setProperty(runtime, "stackSymbols", stackSymbols);
|
||||
|
||||
auto stackReturnAddresses =
|
||||
causeObj.getProperty(runtime, "stackReturnAddresses");
|
||||
extraData.setProperty(
|
||||
runtime, "stackReturnAddresses", stackReturnAddresses);
|
||||
|
||||
auto stackElements = causeObj.getProperty(runtime, "stackElements");
|
||||
extraData.setProperty(runtime, "stackElements", stackElements);
|
||||
}
|
||||
|
||||
auto originalMessage = message == error.getMessage()
|
||||
? std::nullopt
|
||||
: std::optional(error.getMessage());
|
||||
|
||||
auto componentStack = !componentStackValue.isString()
|
||||
? std::nullopt
|
||||
: std::optional(componentStackValue.asString(runtime).utf8(runtime));
|
||||
|
||||
auto isHermes = runtime.global().hasProperty(runtime, "HermesInternal");
|
||||
auto stackFrames = StackTraceParser::parse(isHermes, error.getStack());
|
||||
|
||||
auto id = nextExceptionId();
|
||||
|
||||
ParsedError parsedError = {
|
||||
.message = "EarlyJsError: " + message,
|
||||
.originalMessage = originalMessage,
|
||||
.name = name,
|
||||
.componentStack = componentStack,
|
||||
.stack = stackFrames,
|
||||
.id = id,
|
||||
.isFatal = true,
|
||||
.extraData = std::move(extraData),
|
||||
};
|
||||
|
||||
_onJsError(runtime, parsedError);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ folly_version = folly_config[:version]
|
||||
folly_dep_name = folly_config[:dep_name]
|
||||
|
||||
boost_config = get_boost_config()
|
||||
boost_compiler_flags = boost_config[:compiler_flags]
|
||||
boost_compiler_flags = boost_config[:compiler_flags]
|
||||
react_native_path = ".."
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
@@ -35,7 +35,7 @@ Pod::Spec.new do |s|
|
||||
s.platforms = min_supported_versions
|
||||
s.source = source
|
||||
s.header_dir = "jserrorhandler"
|
||||
s.source_files = "JsErrorHandler.{cpp,h}"
|
||||
s.source_files = "JsErrorHandler.{cpp,h}", "StackTraceParser.{cpp,h}"
|
||||
s.pod_target_xcconfig = {
|
||||
"USE_HEADERMAP" => "YES",
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard()
|
||||
@@ -52,7 +52,7 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-cxxreact"
|
||||
s.dependency "glog"
|
||||
add_dependency(s, "React-debug")
|
||||
|
||||
|
||||
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
|
||||
s.dependency 'hermes-engine'
|
||||
end
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "StackTraceParser.h"
|
||||
#include <glog/logging.h>
|
||||
#include <charconv>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
const std::string UNKNOWN_FUNCTION = "<unknown>";
|
||||
|
||||
// TODO(T198763073): Migrate away from std::regex in this file
|
||||
// @lint-ignore-every CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
|
||||
/**
|
||||
* Stack trace parsing for other jsvms:
|
||||
* Port of https://github.com/errwischt/stacktrace-parser
|
||||
*/
|
||||
namespace {
|
||||
|
||||
std::optional<int> toInt(std::string_view input) {
|
||||
int out;
|
||||
const std::from_chars_result result =
|
||||
std::from_chars(input.data(), input.data() + input.size(), out);
|
||||
if (result.ec == std::errc::invalid_argument ||
|
||||
result.ec == std::errc::result_out_of_range) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
JsErrorHandler::ParsedError::StackFrame parseStackFrame(
|
||||
std::string_view file,
|
||||
std::string_view methodName,
|
||||
std::string_view lineStr,
|
||||
std::string_view columnStr) {
|
||||
JsErrorHandler::ParsedError::StackFrame frame;
|
||||
frame.file = file.empty() ? std::nullopt : std::optional(file);
|
||||
frame.methodName = !methodName.empty() ? methodName : UNKNOWN_FUNCTION;
|
||||
frame.lineNumber = !lineStr.empty() ? toInt(lineStr) : std::nullopt;
|
||||
auto columnOpt = !columnStr.empty() ? toInt(columnStr) : std::nullopt;
|
||||
frame.column = columnOpt ? std::optional(*columnOpt - 1) : std::nullopt;
|
||||
return frame;
|
||||
}
|
||||
|
||||
std::optional<JsErrorHandler::ParsedError::StackFrame> parseChrome(
|
||||
const std::string& line) {
|
||||
static const std::regex chromeRe(
|
||||
R"(^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|\/|[a-z]:\\|\\\\).*?)(?::(\d+))?(?::(\d+))?\)?\s*$)",
|
||||
std::regex::icase);
|
||||
static const std::regex chromeEvalRe(R"(\((\S*)(?::(\d+))(?::(\d+))\))");
|
||||
std::smatch match;
|
||||
|
||||
if (!std::regex_match(line, match, chromeRe)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string methodName = match[1].str();
|
||||
std::string file = match[2].str();
|
||||
std::string lineStr = match[3].str();
|
||||
std::string columnStr = match[4].str();
|
||||
|
||||
bool isNative = std::regex_search(file, std::regex("^native"));
|
||||
bool isEval = std::regex_search(file, std::regex("^eval"));
|
||||
std::string evalFile;
|
||||
std::string evalLine;
|
||||
std::string evalColumn;
|
||||
if (isEval && std::regex_search(file, match, chromeEvalRe)) {
|
||||
evalFile = match[1].str();
|
||||
evalLine = match[2].str();
|
||||
evalColumn = match[3].str();
|
||||
file = evalFile;
|
||||
lineStr = evalLine;
|
||||
columnStr = evalColumn;
|
||||
}
|
||||
std::string actualFile = !isNative ? file : "";
|
||||
return parseStackFrame(actualFile, methodName, lineStr, columnStr);
|
||||
}
|
||||
|
||||
std::optional<JsErrorHandler::ParsedError::StackFrame> parseWinjs(
|
||||
const std::string& line) {
|
||||
static const std::regex winjsRe(
|
||||
R"(^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$)",
|
||||
std::regex::icase);
|
||||
std::smatch match;
|
||||
if (!std::regex_match(line, match, winjsRe)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string methodName = match[1].str();
|
||||
std::string file = match[2].str();
|
||||
std::string lineStr = match[3].str();
|
||||
std::string columnStr = match[4].str();
|
||||
return parseStackFrame(file, methodName, lineStr, columnStr);
|
||||
}
|
||||
|
||||
std::optional<JsErrorHandler::ParsedError::StackFrame> parseGecko(
|
||||
const std::string& line) {
|
||||
static const std::regex geckoRe(
|
||||
R"(^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$)",
|
||||
std::regex::icase);
|
||||
static const std::regex geckoEvalRe(
|
||||
R"((\S+) line (\d+)(?: > eval line \d+)* > eval)", std::regex::icase);
|
||||
std::smatch match;
|
||||
if (!std::regex_match(line, match, geckoRe)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string methodName = match[1].str();
|
||||
std::string tmpStr = match[2].str();
|
||||
std::string file = match[3].str();
|
||||
std::string lineStr = match[4].str();
|
||||
std::string columnStr = match[5].str();
|
||||
bool isEval = std::regex_search(file, std::regex(" > eval"));
|
||||
std::string evalFile;
|
||||
std::string evalLine;
|
||||
if (isEval && std::regex_search(file, match, geckoEvalRe)) {
|
||||
evalFile = match[1].str();
|
||||
evalLine = match[2].str();
|
||||
file = evalFile;
|
||||
lineStr = evalLine;
|
||||
columnStr = ""; // No column number in eval
|
||||
}
|
||||
return parseStackFrame(file, methodName, lineStr, columnStr);
|
||||
}
|
||||
|
||||
std::optional<JsErrorHandler::ParsedError::StackFrame> parseJSC(
|
||||
const std::string& line) {
|
||||
static const std::regex javaScriptCoreRe(
|
||||
R"(^\s*(?:([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)",
|
||||
std::regex::icase);
|
||||
std::smatch match;
|
||||
if (!std::regex_match(line, match, javaScriptCoreRe)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string methodName = match[1].str();
|
||||
std::string tmpStr =
|
||||
match[2].str(); // This captures any string within parentheses if present
|
||||
std::string file = match[3].str();
|
||||
std::string lineStr = match[4].str();
|
||||
std::string columnStr = match[5].str();
|
||||
return parseStackFrame(file, methodName, lineStr, columnStr);
|
||||
}
|
||||
|
||||
std::optional<JsErrorHandler::ParsedError::StackFrame> parseNode(
|
||||
const std::string& line) {
|
||||
static const std::regex nodeRe(
|
||||
R"(^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$)",
|
||||
std::regex::icase);
|
||||
std::smatch match;
|
||||
if (!std::regex_match(line, match, nodeRe)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string methodName = match[1].str();
|
||||
std::string file = match[2].str();
|
||||
std::string lineStr = match[3].str();
|
||||
std::string columnStr = match[4].str();
|
||||
return parseStackFrame(file, methodName, lineStr, columnStr);
|
||||
}
|
||||
|
||||
std::vector<JsErrorHandler::ParsedError::StackFrame> parseOthers(
|
||||
const std::string& stackString) {
|
||||
std::vector<JsErrorHandler::ParsedError::StackFrame> stack;
|
||||
std::istringstream iss(stackString);
|
||||
std::string line;
|
||||
|
||||
while (std::getline(iss, line)) {
|
||||
std::optional<JsErrorHandler::ParsedError::StackFrame> frame =
|
||||
parseChrome(line);
|
||||
|
||||
if (!frame) {
|
||||
frame = parseWinjs(line);
|
||||
}
|
||||
if (!frame) {
|
||||
frame = parseGecko(line);
|
||||
}
|
||||
if (!frame) {
|
||||
frame = parseNode(line);
|
||||
}
|
||||
if (!frame) {
|
||||
frame = parseJSC(line);
|
||||
}
|
||||
|
||||
if (frame) {
|
||||
stack.push_back(*frame);
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* Hermes stack trace parsing logic
|
||||
*/
|
||||
namespace {
|
||||
struct HermesStackLocation {
|
||||
std::string type;
|
||||
std::string sourceUrl;
|
||||
int line1Based{};
|
||||
int column1Based{};
|
||||
int virtualOffset0Based{};
|
||||
};
|
||||
|
||||
struct HermesStackEntry {
|
||||
std::string type;
|
||||
std::string functionName;
|
||||
HermesStackLocation location;
|
||||
int count{};
|
||||
};
|
||||
|
||||
bool isInternalBytecodeSourceUrl(const std::string& sourceUrl) {
|
||||
return sourceUrl == "InternalBytecode.js";
|
||||
}
|
||||
|
||||
std::vector<JsErrorHandler::ParsedError::StackFrame> convertHermesStack(
|
||||
const std::vector<HermesStackEntry>& stack) {
|
||||
std::vector<JsErrorHandler::ParsedError::StackFrame> frames;
|
||||
for (const auto& entry : stack) {
|
||||
if (entry.type != "FRAME") {
|
||||
continue;
|
||||
}
|
||||
if (entry.location.type == "NATIVE" ||
|
||||
entry.location.type == "INTERNAL_BYTECODE") {
|
||||
continue;
|
||||
}
|
||||
JsErrorHandler::ParsedError::StackFrame frame;
|
||||
frame.methodName = entry.functionName;
|
||||
frame.file = entry.location.sourceUrl;
|
||||
frame.lineNumber = entry.location.line1Based;
|
||||
if (entry.location.type == "SOURCE") {
|
||||
frame.column = entry.location.column1Based - 1;
|
||||
} else {
|
||||
frame.column = entry.location.virtualOffset0Based;
|
||||
}
|
||||
frames.push_back(frame);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
HermesStackEntry parseLine(const std::string& line) {
|
||||
static const std::regex RE_FRAME(
|
||||
R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)");
|
||||
static const std::regex RE_SKIPPED(R"(^ {4}... skipping (\d+) frames$)");
|
||||
HermesStackEntry entry;
|
||||
std::smatch match;
|
||||
if (std::regex_match(line, match, RE_FRAME)) {
|
||||
entry.type = "FRAME";
|
||||
entry.functionName = match[1].str();
|
||||
std::string type = match[2].str();
|
||||
std::string addressAt = match[3].str();
|
||||
std::string sourceUrl = match[4].str();
|
||||
if (type == "native") {
|
||||
entry.location.type = "NATIVE";
|
||||
} else {
|
||||
int line1Based = std::stoi(match[5].str());
|
||||
int columnOrOffset = std::stoi(match[6].str());
|
||||
if (addressAt == "address at ") {
|
||||
if (isInternalBytecodeSourceUrl(sourceUrl)) {
|
||||
entry.location = {
|
||||
"INTERNAL_BYTECODE", sourceUrl, line1Based, 0, columnOrOffset};
|
||||
} else {
|
||||
entry.location = {
|
||||
"BYTECODE", sourceUrl, line1Based, 0, columnOrOffset};
|
||||
}
|
||||
} else {
|
||||
entry.location = {"SOURCE", sourceUrl, line1Based, columnOrOffset, 0};
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
if (std::regex_match(line, match, RE_SKIPPED)) {
|
||||
entry.type = "SKIPPED";
|
||||
entry.count = std::stoi(match[1].str());
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
std::vector<JsErrorHandler::ParsedError::StackFrame> parseHermes(
|
||||
const std::string& stack) {
|
||||
static const std::regex RE_COMPONENT_NO_STACK(R"(^ {4}at .*?$)");
|
||||
std::istringstream stream(stack);
|
||||
std::string line;
|
||||
std::vector<HermesStackEntry> entries;
|
||||
std::smatch match;
|
||||
while (std::getline(stream, line)) {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
HermesStackEntry entry = parseLine(line);
|
||||
if (!entry.type.empty()) {
|
||||
entries.push_back(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::regex_match(line, match, RE_COMPONENT_NO_STACK)) {
|
||||
continue;
|
||||
}
|
||||
entries.clear();
|
||||
}
|
||||
return convertHermesStack(entries);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<JsErrorHandler::ParsedError::StackFrame> StackTraceParser::parse(
|
||||
const bool isHermes,
|
||||
const std::string& stackString) {
|
||||
std::vector<JsErrorHandler::ParsedError::StackFrame> stackFrames =
|
||||
isHermes ? parseHermes(stackString) : parseOthers(stackString);
|
||||
return stackFrames;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "JsErrorHandler.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class StackTraceParser {
|
||||
public:
|
||||
static std::vector<JsErrorHandler::ParsedError::StackFrame> parse(
|
||||
bool isHermes,
|
||||
const std::string& stackString);
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
File diff suppressed because it is too large
Load Diff
@@ -365,6 +365,17 @@ bool isTruthy(jsi::Runtime& runtime, const jsi::Value& value) {
|
||||
return Boolean.call(runtime, value).getBool();
|
||||
}
|
||||
|
||||
jsi::Value wrapInErrorIfNecessary(
|
||||
jsi::Runtime& runtime,
|
||||
const jsi::Value& value) {
|
||||
auto Error = runtime.global().getPropertyAsFunction(runtime, "Error");
|
||||
auto isError =
|
||||
value.isObject() && value.asObject(runtime).instanceOf(runtime, Error);
|
||||
auto error = isError ? value.getObject(runtime)
|
||||
: Error.callAsConstructor(runtime, value);
|
||||
return jsi::Value(runtime, error);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ReactInstance::initializeRuntime(
|
||||
@@ -412,8 +423,8 @@ void ReactInstance::initializeRuntime(
|
||||
}
|
||||
|
||||
if (isFatal) {
|
||||
auto jsError =
|
||||
jsi::JSError(runtime, jsi::Value(runtime, args[0]));
|
||||
auto jsError = jsi::JSError(
|
||||
runtime, wrapInErrorIfNecessary(runtime, args[0]));
|
||||
jsErrorHandler->handleFatalError(runtime, jsError);
|
||||
return jsi::Value(true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user