1/n Add new JsErrorHandler buck target to parse JS errors in C++

Summary:
Changelog: [Internal][Added] 1/n Add new JsErrorHandler buck target to parse JS errors in C++

This JsErrorHandler.cpp class uses the same regex logic as [stack-trace-parser.js](https://github.com/errwischt/stacktrace-parser/blob/master/src/stack-trace-parser.js#L121) in order to parse JS errors into an array of stack frames.

In RN, stacktrace-parser is called from [Devtools/parseErrorStack.js](8bd3edec88/Libraries/Core/Devtools/parseErrorStack.js (L46)).

Reviewed By: sammy-SC

Differential Revision: D40296024

fbshipit-source-id: 8e1b034e8a1ee1a8cc808bfc36c7104a8f40f9cc
This commit is contained in:
Paige Sun 2022-10-13 21:40:08 -07:00 коммит произвёл Facebook GitHub Bot
Родитель a5754720f6
Коммит 84225573c1
3 изменённых файлов: 173 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,31 @@
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "CXX", "react_native_xplat_target", "rn_xplat_cxx_library")
# TODO: Expolre merging this module into venice so we don't to load this library seperately
rn_xplat_cxx_library(
name = "jserrorhandler",
srcs = glob(["*.cpp"]),
header_namespace = "",
exported_headers = {"JsErrorHandler/JsErrorHandler.h": "JsErrorHandler.h"},
compiler_flags = [
"-fexceptions",
"-frtti",
],
labels = [
"pfh:ReactNative_CommonInfrastructurePlaceholder",
"supermodule:xplat/default/public.react_native.infra",
],
platforms = (ANDROID, APPLE, CXX),
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
visibility = [
"PUBLIC",
],
deps = [
"//xplat/folly:dynamic",
"//xplat/folly:json",
"//xplat/jsi:jsi",
react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"),
],
)

Просмотреть файл

@ -0,0 +1,105 @@
/*
* 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 <react/renderer/mapbuffer/MapBufferBuilder.h>
#include <regex>
#include <sstream>
#include <string>
#include <vector>
namespace facebook {
namespace react {
static MapBuffer
parseErrorStack(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.
*/
const std::regex REGEX_CHROME(
R"(^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$)");
const std::regex REGEX_GECKO(
R"(^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)");
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)
const std::regex REGEX_HERMES(
R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)");
std::string line;
std::stringstream strStream(error.getStack());
auto errorObj = MapBufferBuilder();
std::vector<MapBuffer> frames;
while (std::getline(strStream, line, '\n')) {
auto frame = MapBufferBuilder();
auto searchResults = std::smatch{};
if (isHermes) {
if (std::regex_search(line, searchResults, REGEX_HERMES)) {
std::string str2 = std::string(searchResults[2]);
if (str2.compare("native")) {
frame.putString(0, std::string(searchResults[4]));
frame.putString(1, std::string(searchResults[1]));
frame.putInt(2, std::stoi(searchResults[5]));
frame.putInt(3, std::stoi(searchResults[6]));
frames.push_back(frame.build());
}
}
} else {
if (std::regex_search(line, searchResults, REGEX_GECKO)) {
frame.putString(0, std::string(searchResults[3]));
frame.putString(1, std::string(searchResults[1]));
frame.putInt(2, std::stoi(searchResults[4]));
frame.putInt(3, std::stoi(searchResults[5]));
} else if (
std::regex_search(line, searchResults, REGEX_CHROME) ||
std::regex_search(line, searchResults, REGEX_NODE)) {
frame.putString(0, std::string(searchResults[2]));
frame.putString(1, std::string(searchResults[1]));
frame.putInt(2, std::stoi(searchResults[3]));
frame.putInt(3, std::stoi(searchResults[4]));
} else {
continue;
}
frames.push_back(frame.build());
}
}
errorObj.putMapBufferList(4, std::move(frames));
errorObj.putString(5, error.getMessage());
// TODO: If needed, can increment exceptionId by 1 each time
errorObj.putInt(6, 0);
errorObj.putBool(7, isFatal);
return errorObj.build();
}
JsErrorHandler::JsErrorHandler(
JsErrorHandler::JsErrorHandlingFunc jsErrorHandlingFunc) {
this->_jsErrorHandlingFunc = jsErrorHandlingFunc;
};
JsErrorHandler::~JsErrorHandler() {}
void JsErrorHandler::handleJsError(const jsi::JSError &error, bool isFatal) {
// TODO: Current error parsing works and is stable. Can investigate using
// REGEX_HERMES to get additional Hermes data, though it requires JS setup.
MapBuffer errorMap = parseErrorStack(error, isFatal, false);
_jsErrorHandlingFunc(std::move(errorMap));
}
} // namespace react
} // namespace facebook

Просмотреть файл

@ -0,0 +1,37 @@
/*
* 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 <jsi/jsi.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
namespace facebook {
namespace react {
const int FILE_KEY_OF_JS_ERROR = 0;
const int METHOD_NAME_KEY_OF_JS_ERROR = 1;
const int LINE_NUMBER_KEY_OF_JS_ERROR = 2;
const int COLUMN_KEY_OF_JS_ERROR = 3;
const int FRAMES_KEY_OF_JS_ERROR = 4;
const int MESSAGE_KEY_OF_JS_ERROR = 5;
const int ID_KEY_OF_JS_ERROR = 6;
const int IS_FATAL_KEY_OF_JS_ERROR = 7;
class JsErrorHandler {
public:
using JsErrorHandlingFunc = std::function<void(MapBuffer errorMap)>;
JsErrorHandler(JsErrorHandlingFunc jsErrorHandlingFunc);
~JsErrorHandler();
void handleJsError(const jsi::JSError &error, bool isFatal);
private:
JsErrorHandlingFunc _jsErrorHandlingFunc;
};
} // namespace react
} // namespace facebook