jiuyiUniapp/service/node_modules/react-native/ReactCommon/jserrorhandler/StackTraceParser.cpp

318 lines
9.8 KiB
C++

/*
* 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;
}