Files
react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp
T
Paige Sun 5449148482 4/n Display a RedBox with the JS stack (instead of native stack) when an unhandled JS exceptions occurs - Try 2
Summary:
Changelog: [iOS][Changed] Display a RedBox with the JS stack (instead of native stack) when an unhandled JS exceptions occurs

-----

# A0) Bridge mode, Unhandled Exception, FBiOS
Unhandled exception goes to FBReactModule, but the JS Stack is not parsed correctly.
https://www.internalfb.com/code/fbsource/[312d5cbdd7278247a84619786b12a44c4400fcc0]/fbobjc/Apps/Wilde/FBReactModule2/FBReactModuleAPI/FBReactModuleAPI/Exported/FBReactModule.mm?lines=1488%2C1493

See `****** FBReactModule handleFatalError` in the logs P539306390, and compare with correct behavior in (A1) in the Test Plan.

https://pxl.cl/2h6h3

{F782257996}

-----

# A) Before diff, rn-tester
Open to rn-tester -> APIs -> Crash Examples -> JS Crash.

Set `RCTParseUnhandledJSErrorStackNatively` to YES.
https://www.internalfb.com/code/fbsource/[98880e52ee78be3614e5d9a2ce3292f6a7b5e413]/xplat/js/react-native-github/React/Base/RCTConstants.m?lines=73

{F783395297}

----

build_on_commit[ios_fbios-arm64-dylibs-jackalope-iphoneos-production_build]

Reviewed By: RSNara

Differential Revision: D40613108

fbshipit-source-id: f36c3b39a2167402ee3730db8b40b53a0b80aa60
2022-10-23 16:42:00 -07:00

110 lines
4.1 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 "JsErrorHandler.h"
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#include <regex>
#include <sstream>
#include <string>
#include <vector>
namespace facebook {
namespace react {
using facebook::react::JSErrorHandlerKey;
MapBuffer JsErrorHandler::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(kFrameFileName, std::string(searchResults[4]));
frame.putString(kFrameMethodName, std::string(searchResults[1]));
frame.putInt(kFrameLineNumber, std::stoi(searchResults[5]));
frame.putInt(kFrameColumnNumber, std::stoi(searchResults[6]));
frames.push_back(frame.build());
}
}
} else {
if (std::regex_search(line, searchResults, REGEX_GECKO)) {
frame.putString(kFrameFileName, std::string(searchResults[3]));
frame.putString(kFrameMethodName, std::string(searchResults[1]));
frame.putInt(kFrameLineNumber, std::stoi(searchResults[4]));
frame.putInt(kFrameColumnNumber, std::stoi(searchResults[5]));
} else if (
std::regex_search(line, searchResults, REGEX_CHROME) ||
std::regex_search(line, searchResults, REGEX_NODE)) {
frame.putString(kFrameFileName, std::string(searchResults[2]));
frame.putString(kFrameMethodName, std::string(searchResults[1]));
frame.putInt(kFrameLineNumber, std::stoi(searchResults[3]));
frame.putInt(kFrameColumnNumber, std::stoi(searchResults[4]));
} else {
continue;
}
frames.push_back(frame.build());
}
}
errorObj.putMapBufferList(kAllStackFrames, std::move(frames));
errorObj.putString(kErrorMessage, "EarlyJsError: " + error.getMessage());
// TODO: If needed, can increment exceptionId by 1 each time
errorObj.putInt(kExceptionId, 0);
errorObj.putBool(kIsFatal, 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 = JsErrorHandler::parseErrorStack(error, isFatal, false);
_jsErrorHandlingFunc(std::move(errorMap));
}
} // namespace react
} // namespace facebook