first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB js_error_handler_SRC CONFIGURE_DEPENDS *.cpp)
add_library(
jserrorhandler
OBJECT
${js_error_handler_SRC}
)
target_include_directories(jserrorhandler PUBLIC .)
react_native_android_selector(marbufferjni marbufferjni "")
target_link_libraries(jserrorhandler
jsi
callinvoker
folly_runtime
${mapbufferjni}
react_featureflags
)
target_compile_reactnative_options(jserrorhandler PRIVATE)

View File

@@ -0,0 +1,431 @@
/*
* 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 "JsErrorHandler.h"
#include <cxxreact/ErrorUtils.h>
#include <glog/logging.h>
#include <react/bridging/Bridging.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <string>
#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 isEqualTo(
jsi::Runtime& runtime,
const jsi::Value& value,
const std::string& str) {
return jsi::Value::strictEquals(
runtime, value, jsi::String::createFromUtf8(runtime, str));
}
std::string stringifyToCpp(jsi::Runtime& runtime, const jsi::Value& value) {
return value.toString(runtime).utf8(runtime);
}
bool isTruthy(jsi::Runtime& runtime, const jsi::Value& value) {
auto Boolean = runtime.global().getPropertyAsFunction(runtime, "Boolean");
return Boolean.call(runtime, value).getBool();
}
void objectAssign(
jsi::Runtime& runtime,
jsi::Object& target,
const jsi::Object& value) {
auto Object = runtime.global().getPropertyAsObject(runtime, "Object");
auto assign = Object.getPropertyAsFunction(runtime, "assign");
assign.callWithThis(runtime, Object, target, value);
}
jsi::Object 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).getObject(runtime);
return error;
}
class SetFalseOnDestruct {
std::shared_ptr<bool> _value;
public:
SetFalseOnDestruct(const SetFalseOnDestruct&) = delete;
SetFalseOnDestruct& operator=(const SetFalseOnDestruct&) = delete;
SetFalseOnDestruct(SetFalseOnDestruct&&) = delete;
SetFalseOnDestruct& operator=(SetFalseOnDestruct&&) = delete;
explicit SetFalseOnDestruct(std::shared_ptr<bool> value)
: _value(std::move(value)) {}
~SetFalseOnDestruct() {
*_value = false;
}
};
void logErrorWhileReporting(
std::string message,
jsi::JSError& error,
jsi::JSError& originalError) {
LOG(ERROR) << "JsErrorHandler::" << message << std::endl
<< "Js error message: " << error.getMessage() << std::endl
<< "Original js error message: " << originalError.getMessage()
<< std::endl;
}
jsi::Value getBundleMetadata(jsi::Runtime& runtime, jsi::JSError& error) {
auto jsGetBundleMetadataValue =
runtime.global().getProperty(runtime, "__getBundleMetadata");
if (!jsGetBundleMetadataValue.isObject() ||
!jsGetBundleMetadataValue.asObject(runtime).isFunction(runtime)) {
return jsi::Value::null();
}
auto jsGetBundleMetadataValueFn =
jsGetBundleMetadataValue.asObject(runtime).asFunction(runtime);
try {
auto bundleMetadataValue = jsGetBundleMetadataValueFn.call(runtime);
if (bundleMetadataValue.isObject()) {
return bundleMetadataValue;
}
return bundleMetadataValue;
} catch (jsi::JSError& ex) {
logErrorWhileReporting(
"getBundleMetadata(): Error raised while calling __getBundleMetadata(). Returning null.",
ex,
error);
}
return jsi::Value::null();
}
} // namespace
namespace facebook::react {
template <>
struct Bridging<JsErrorHandler::ProcessedError::StackFrame> {
static jsi::Object toJs(
jsi::Runtime& runtime,
const JsErrorHandler::ProcessedError::StackFrame& frame) {
auto stackFrame = jsi::Object(runtime);
auto file = bridging::toJs(runtime, frame.file, nullptr);
auto lineNumber = bridging::toJs(runtime, frame.lineNumber, nullptr);
auto column = bridging::toJs(runtime, frame.column, nullptr);
stackFrame.setProperty(runtime, "file", file);
stackFrame.setProperty(runtime, "methodName", frame.methodName);
stackFrame.setProperty(runtime, "lineNumber", lineNumber);
stackFrame.setProperty(runtime, "column", column);
return stackFrame;
}
};
template <>
struct Bridging<JsErrorHandler::ProcessedError> {
static jsi::Object toJs(
jsi::Runtime& runtime,
const JsErrorHandler::ProcessedError& error) {
auto data = jsi::Object(runtime);
data.setProperty(runtime, "message", error.message);
data.setProperty(
runtime,
"originalMessage",
bridging::toJs(runtime, error.originalMessage, nullptr));
data.setProperty(
runtime, "name", bridging::toJs(runtime, error.name, nullptr));
data.setProperty(
runtime,
"componentStack",
bridging::toJs(runtime, error.componentStack, nullptr));
auto stack = jsi::Array(runtime, error.stack.size());
for (size_t i = 0; i < error.stack.size(); i++) {
auto& frame = error.stack[i];
stack.setValueAtIndex(runtime, i, bridging::toJs(runtime, frame));
}
data.setProperty(runtime, "stack", stack);
data.setProperty(runtime, "id", error.id);
data.setProperty(runtime, "isFatal", error.isFatal);
data.setProperty(runtime, "extraData", error.extraData);
return data;
}
};
std::ostream& operator<<(
std::ostream& os,
const JsErrorHandler::ProcessedError::StackFrame& frame) {
auto file = frame.file ? quote(*frame.file) : "nil";
auto methodName = quote(frame.methodName);
auto lineNumber =
frame.lineNumber ? std::to_string(*frame.lineNumber) : "nil";
auto column = frame.column ? std::to_string(*frame.column) : "nil";
os << "StackFrame { .file = " << file << ", .methodName = " << methodName
<< ", .lineNumber = " << lineNumber << ", .column = " << column << " }";
return os;
}
std::ostream& operator<<(
std::ostream& os,
const JsErrorHandler::ProcessedError& error) {
auto message = quote(error.message);
auto originalMessage =
error.originalMessage ? quote(*error.originalMessage) : "nil";
auto name = error.name ? quote(*error.name) : "nil";
auto componentStack =
error.componentStack ? quote(*error.componentStack) : "nil";
auto id = std::to_string(error.id);
auto isFatal = std::to_string(static_cast<int>(error.isFatal));
auto extraData = "jsi::Object{ <omitted> } ";
os << "ProcessedError {\n"
<< " .message = " << message << "\n"
<< " .originalMessage = " << originalMessage << "\n"
<< " .name = " << name << "\n"
<< " .componentStack = " << componentStack << "\n"
<< " .stack = [\n";
for (const auto& frame : error.stack) {
os << " " << frame << ", \n";
}
os << " ]\n"
<< " .id = " << id << "\n"
<< " .isFatal " << isFatal << "\n"
<< " .extraData = " << extraData << "\n"
<< "}";
return os;
}
JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError)
: _onJsError(std::move(onJsError)),
_inErrorHandler(std::make_shared<bool>(false)) {
};
JsErrorHandler::~JsErrorHandler() {}
void JsErrorHandler::handleError(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal,
bool logToConsole) {
// TODO: Current error parsing works and is stable. Can investigate using
// REGEX_HERMES to get additional Hermes data, though it requires JS setup
if (!ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling() &&
_isRuntimeReady) {
try {
handleJSError(runtime, error, isFatal);
return;
} catch (jsi::JSError& ex) {
logErrorWhileReporting(
"handleError(): Error raised while reporting using js pipeline. Using c++ pipeline instead.",
ex,
error);
// Re-try reporting using the c++ pipeline
_hasHandledFatalError = false;
}
}
handleErrorWithCppPipeline(runtime, error, isFatal, logToConsole);
}
void JsErrorHandler::handleErrorWithCppPipeline(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal,
bool logToConsole) {
*_inErrorHandler = true;
SetFalseOnDestruct temp{_inErrorHandler};
auto message = error.getMessage();
auto errorObj = wrapInErrorIfNecessary(runtime, error.value());
auto componentStackValue = errorObj.getProperty(runtime, "componentStack");
if (!isLooselyNull(componentStackValue)) {
message += "\n" + stringifyToCpp(runtime, componentStackValue);
}
auto nameValue = errorObj.getProperty(runtime, "name");
auto name = (isLooselyNull(nameValue) || isEqualTo(runtime, nameValue, ""))
? std::nullopt
: std::optional(stringifyToCpp(runtime, nameValue));
if (name && !message.starts_with(*name + ": ")) {
message = *name + ": " + message;
}
auto extraDataKey = jsi::PropNameID::forUtf8(runtime, "RN$ErrorExtraDataKey");
auto extraDataValue = errorObj.getProperty(runtime, extraDataKey);
auto extraData = jsi::Object(runtime);
if (extraDataValue.isObject()) {
objectAssign(runtime, extraData, extraDataValue.asObject(runtime));
}
auto jsEngineValue = errorObj.getProperty(runtime, "jsEngine");
auto isDEV =
isTruthy(runtime, runtime.global().getProperty(runtime, "__DEV__"));
extraData.setProperty(runtime, "jsEngine", jsEngineValue);
extraData.setProperty(runtime, "rawStack", error.getStack());
extraData.setProperty(runtime, "__DEV__", isDEV);
extraData.setProperty(
runtime, "bundleMetadata", getBundleMetadata(runtime, error));
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();
ProcessedError processedError = {
.message =
_isRuntimeReady ? message : ("[runtime not ready]: " + message),
.originalMessage = originalMessage,
.name = name,
.componentStack = componentStack,
.stack = stackFrames,
.id = id,
.isFatal = isFatal,
.extraData = std::move(extraData),
};
auto data = bridging::toJs(runtime, processedError);
auto isComponentError =
isTruthy(runtime, errorObj.getProperty(runtime, "isComponentError"));
data.setProperty(runtime, "isComponentError", isComponentError);
if (logToConsole) {
try {
auto console = runtime.global().getPropertyAsObject(runtime, "console");
auto errorFn = console.getPropertyAsFunction(runtime, "error");
auto finalMessage =
jsi::String::createFromUtf8(runtime, processedError.message);
errorFn.callWithThis(runtime, console, finalMessage);
} catch (jsi::JSError& ex) {
logErrorWhileReporting(
"handleErrorWithCppPipeline(): Error while logToConsole: ",
ex,
error);
}
}
std::shared_ptr<bool> shouldPreventDefault = std::make_shared<bool>(false);
auto preventDefault = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "preventDefault"),
0,
[shouldPreventDefault](
jsi::Runtime& /*rt*/,
const jsi::Value& /*thisVal*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
*shouldPreventDefault = true;
return jsi::Value::undefined();
});
data.setProperty(runtime, "preventDefault", preventDefault);
for (auto& errorListener : _errorListeners) {
try {
errorListener(runtime, jsi::Value(runtime, data));
} catch (jsi::JSError& ex) {
logErrorWhileReporting(
"handleErrorWithCppPipeline(): Error raised inside an error listener. Executing next listener.",
ex,
error);
}
}
if (*shouldPreventDefault) {
return;
}
auto errorType = errorObj.getProperty(runtime, "type");
auto isWarn = isEqualTo(runtime, errorType, "warn");
if (isFatal || !isWarn) {
if (isFatal) {
if (_hasHandledFatalError) {
return;
}
_hasHandledFatalError = true;
}
_onJsError(runtime, processedError);
}
}
void JsErrorHandler::registerErrorListener(
const std::function<void(jsi::Runtime&, jsi::Value)>& errorListener) {
_errorListeners.push_back(errorListener);
}
bool JsErrorHandler::hasHandledFatalError() {
return _hasHandledFatalError;
}
void JsErrorHandler::setRuntimeReady() {
_isRuntimeReady = true;
}
bool JsErrorHandler::isRuntimeReady() {
return _isRuntimeReady;
}
void JsErrorHandler::notifyOfFatalError() {
_hasHandledFatalError = true;
}
bool JsErrorHandler::inErrorHandler() {
return *_inErrorHandler;
}
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* 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 <jsi/jsi.h>
#include <iostream>
#include <optional>
namespace facebook::react {
class JsErrorHandler {
public:
struct ProcessedError {
struct StackFrame {
std::optional<std::string> file;
std::string methodName;
std::optional<int> lineNumber;
std::optional<int> column;
friend std::ostream &operator<<(std::ostream &os, const StackFrame &frame);
};
std::string message;
std::optional<std::string> originalMessage;
std::optional<std::string> name;
std::optional<std::string> componentStack;
std::vector<StackFrame> stack;
int id;
bool isFatal;
jsi::Object extraData;
friend std::ostream &operator<<(std::ostream &os, const ProcessedError &error);
};
using OnJsError = std::function<void(jsi::Runtime &runtime, const ProcessedError &error)>;
explicit JsErrorHandler(OnJsError onJsError);
~JsErrorHandler();
void handleError(jsi::Runtime &runtime, jsi::JSError &error, bool isFatal, bool logToConsole = true);
bool hasHandledFatalError();
void registerErrorListener(const std::function<void(jsi::Runtime &, jsi::Value)> &listener);
void setRuntimeReady();
bool isRuntimeReady();
void notifyOfFatalError();
bool inErrorHandler();
private:
/**
* This callback:
* 1. Shouldn't retain the ReactInstance. So that we don't get retain cycles.
* 2. Should be implemented by something that can outlive the react instance
* (both before init and after teardown). So that errors during init and
* teardown get reported properly.
**/
OnJsError _onJsError;
bool _hasHandledFatalError{};
bool _isRuntimeReady{};
std::shared_ptr<bool> _inErrorHandler;
std::vector<std::function<void(jsi::Runtime &, jsi::Value)>> _errorListeners;
void handleErrorWithCppPipeline(jsi::Runtime &runtime, jsi::JSError &error, bool isFatal, bool logToConsole);
};
} // namespace facebook::react

View File

@@ -0,0 +1,52 @@
# 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 were 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
react_native_path = ".."
Pod::Spec.new do |s|
s.name = "React-jserrorhandler"
s.version = version
s.summary = "-"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.header_dir = "jserrorhandler"
s.source_files = podspec_sources(["JsErrorHandler.{cpp,h}", "StackTraceParser.{cpp,h}"], ["JsErrorHandler.h", "StackTraceParser.h"])
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard()
}
resolve_use_frameworks(s, header_mappings_dir: '../', module_name: "React_jserrorhandler")
s.dependency "React-jsi"
s.dependency "React-cxxreact"
s.dependency "ReactCommon/turbomodule/bridging"
add_dependency(s, "React-featureflags")
add_dependency(s, "React-debug")
if use_hermes()
s.dependency 'hermes-engine'
end
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,319 @@
/*
* 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>
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::ProcessedError::StackFrame parseStackFrame(
std::string_view file,
std::string_view methodName,
std::string_view lineStr,
std::string_view columnStr) {
JsErrorHandler::ProcessedError::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::ProcessedError::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::ProcessedError::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::ProcessedError::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::ProcessedError::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::ProcessedError::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::ProcessedError::StackFrame> parseOthers(
const std::string& stackString) {
std::vector<JsErrorHandler::ProcessedError::StackFrame> stack;
std::istringstream iss(stackString);
std::string line;
while (std::getline(iss, line)) {
std::optional<JsErrorHandler::ProcessedError::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::ProcessedError::StackFrame> convertHermesStack(
const std::vector<HermesStackEntry>& stack) {
std::vector<JsErrorHandler::ProcessedError::StackFrame> frames;
for (const auto& entry : stack) {
if (entry.type != "FRAME") {
continue;
}
if (entry.location.type == "NATIVE" ||
entry.location.type == "INTERNAL_BYTECODE") {
continue;
}
JsErrorHandler::ProcessedError::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::ProcessedError::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::ProcessedError::StackFrame> StackTraceParser::parse(
const bool isHermes,
const std::string& stackString) {
std::vector<JsErrorHandler::ProcessedError::StackFrame> stackFrames =
isHermes ? parseHermes(stackString) : parseOthers(stackString);
return stackFrames;
}
} // namespace facebook::react

View File

@@ -0,0 +1,21 @@
/*
* 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::ProcessedError::StackFrame> parse(bool isHermes, const std::string &stackString);
};
} // namespace facebook::react

File diff suppressed because it is too large Load Diff