Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Route all js errors caught in C++ through JsErrorHandler #45619

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@
*/

#include "JsErrorHandler.h"
#include <cxxreact/ErrorUtils.h>

Check failure on line 9 in packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

View workflow job for this annotation

GitHub Actions / test_ios_rntester_ruby_3_2_0

'cxxreact/ErrorUtils.h' file not found

Check failure on line 9 in packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

View workflow job for this annotation

GitHub Actions / test_ios_rntester (JSC, OldArch)

'cxxreact/ErrorUtils.h' file not found

Check failure on line 9 in packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

View workflow job for this annotation

GitHub Actions / test_ios_rntester (JSC, NewArch)

'cxxreact/ErrorUtils.h' file not found

Check failure on line 9 in packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

View workflow job for this annotation

GitHub Actions / test_ios_rntester (Hermes, NewArch)

'cxxreact/ErrorUtils.h' file not found

Check failure on line 9 in packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

View workflow job for this annotation

GitHub Actions / test_ios_rntester_dynamic_frameworks (Hermes)

'cxxreact/ErrorUtils.h' file not found

Check failure on line 9 in packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

View workflow job for this annotation

GitHub Actions / test_ios_rntester_dynamic_frameworks (Hermes)

'cxxreact/ErrorUtils.h' file not found

Check failure on line 9 in packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

View workflow job for this annotation

GitHub Actions / test_ios_rntester_dynamic_frameworks (JSC)

'cxxreact/ErrorUtils.h' file not found

Check failure on line 9 in packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

View workflow job for this annotation

GitHub Actions / test_ios_rntester_dynamic_frameworks (JSC)

'cxxreact/ErrorUtils.h' file not found

Check failure on line 9 in packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

View workflow job for this annotation

GitHub Actions / test_ios_rntester (Hermes, OldArch)

'cxxreact/ErrorUtils.h' file not found
#include <glog/logging.h>
#include <regex>
#include <sstream>
#include <string>
#include <vector>

namespace facebook::react {

// TODO(T198763073): Migrate away from std::regex in this function
static JsErrorHandler::ParsedError
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.
*/
// NOLINTNEXTLINE(facebook-hte-StdRegexIsAwful)
const std::regex REGEX_CHROME(
R"(^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$)");
// NOLINTNEXTLINE(facebook-hte-StdRegexIsAwful)
const std::regex REGEX_GECKO(
R"(^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)");
// NOLINTNEXTLINE(facebook-hte-StdRegexIsAwful)
const std::regex REGEX_NODE(
R"(^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$)");

Expand All @@ -34,6 +40,7 @@
// 4. source URL (filename)
// 5. line number (1 based)
// 6. column number (1 based) or virtual offset (0 based)
// NOLINTNEXTLINE(facebook-hte-StdRegexIsAwful)
const std::regex REGEX_HERMES(
R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)");

Expand All @@ -46,6 +53,7 @@
auto searchResults = std::smatch{};

if (isHermes) {
// NOLINTNEXTLINE(facebook-hte-StdRegexIsAwful)
if (std::regex_search(line, searchResults, REGEX_HERMES)) {
std::string str2 = std::string(searchResults[2]);
if (str2.compare("native")) {
Expand All @@ -58,6 +66,7 @@
}
}
} else {
// NOLINTNEXTLINE(facebook-hte-StdRegexIsAwful)
if (std::regex_search(line, searchResults, REGEX_GECKO)) {
frames.push_back({
.fileName = std::string(searchResults[3]),
Expand All @@ -66,7 +75,9 @@
.columnNumber = std::stoi(searchResults[5]),
});
} else if (
// NOLINTNEXTLINE(facebook-hte-StdRegexIsAwful)
std::regex_search(line, searchResults, REGEX_CHROME) ||
// NOLINTNEXTLINE(facebook-hte-StdRegexIsAwful)
std::regex_search(line, searchResults, REGEX_NODE)) {
frames.push_back({
.fileName = std::string(searchResults[2]),
Expand Down Expand Up @@ -96,10 +107,26 @@

JsErrorHandler::~JsErrorHandler() {}

void JsErrorHandler::handleFatalError(const jsi::JSError& error) {
void JsErrorHandler::handleFatalError(
jsi::Runtime& runtime,
jsi::JSError& error) {
// TODO: Current error parsing works and is stable. Can investigate using
// REGEX_HERMES to get additional Hermes data, though it requires JS setup.
_hasHandledFatalError = true;

if (_isJsPipelineReady) {
try {
handleJSError(runtime, error, true);
return;
} catch (jsi::JSError& e) {
LOG(ERROR)
<< "JsErrorHandler: Failed to report js error using js pipeline. Using C++ pipeline instead."
<< std::endl
<< "Reporting failure: " << e.getMessage() << std::endl
<< "Original js error: " << error.getMessage() << std::endl;
}
}
// This is a hacky way to get Hermes stack trace.
ParsedError parsedError = parseErrorStack(error, true, false);
_onJsError(parsedError);
}
Expand All @@ -108,4 +135,8 @@
return _hasHandledFatalError;
}

void JsErrorHandler::setJsPipelineReady() {
_isJsPipelineReady = true;
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,21 @@ class JsErrorHandler {
explicit JsErrorHandler(OnJsError onJsError);
~JsErrorHandler();

void handleFatalError(const jsi::JSError& error);
void handleFatalError(jsi::Runtime& runtime, jsi::JSError& error);
bool hasHandledFatalError();
void setJsPipelineReady();

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 _isJsPipelineReady{};
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "RuntimeScheduler_Modern.h"
#include "SchedulerPriorityUtils.h"

#include <cxxreact/ErrorUtils.h>
#include <cxxreact/SystraceSection.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <utility>
Expand All @@ -19,23 +20,33 @@ namespace facebook::react {
namespace {
std::unique_ptr<RuntimeSchedulerBase> getRuntimeSchedulerImplementation(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now) {
std::function<RuntimeSchedulerTimePoint()> now,
RuntimeSchedulerTaskErrorHandler onTaskError) {
if (ReactNativeFeatureFlags::useModernRuntimeScheduler()) {
return std::make_unique<RuntimeScheduler_Modern>(
std::move(runtimeExecutor), std::move(now));
std::move(runtimeExecutor), std::move(now), std::move(onTaskError));
} else {
return std::make_unique<RuntimeScheduler_Legacy>(
std::move(runtimeExecutor), std::move(now));
std::move(runtimeExecutor), std::move(now), std::move(onTaskError));
}
}

} // namespace

RuntimeScheduler::RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now)
std::function<RuntimeSchedulerTimePoint()> now,
RuntimeSchedulerTaskErrorHandler onTaskError)
: runtimeSchedulerImpl_(getRuntimeSchedulerImplementation(
std::move(runtimeExecutor),
std::move(now))) {}
std::move(now),
std::move(onTaskError))) {}

/* static */ void RuntimeScheduler::handleTaskErrorDefault(
jsi::Runtime& runtime,
jsi::JSError& error) {
handleJSError(runtime, error, true);
}

void RuntimeScheduler::scheduleWork(RawCallback&& callback) noexcept {
return runtimeSchedulerImpl_->scheduleWork(std::move(callback));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ namespace facebook::react {
using RuntimeSchedulerRenderingUpdate = std::function<void()>;
using RuntimeSchedulerTimeout = std::chrono::milliseconds;

using RuntimeSchedulerTaskErrorHandler =
std::function<void(jsi::Runtime& runtime, jsi::JSError& error)>;

// This is a temporary abstract class for RuntimeScheduler forks to implement
// (and use them interchangeably).
class RuntimeSchedulerBase {
Expand Down Expand Up @@ -60,7 +63,8 @@ class RuntimeScheduler final : RuntimeSchedulerBase {
explicit RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now =
RuntimeSchedulerClock::now);
RuntimeSchedulerClock::now,
RuntimeSchedulerTaskErrorHandler onTaskError = handleTaskErrorDefault);

/*
* Not copyable.
Expand Down Expand Up @@ -163,6 +167,10 @@ class RuntimeScheduler final : RuntimeSchedulerBase {
// Actual implementation, stored as a unique pointer to simplify memory
// management.
std::unique_ptr<RuntimeSchedulerBase> runtimeSchedulerImpl_;

static void handleTaskErrorDefault(
jsi::Runtime& runtime,
jsi::JSError& error);
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "RuntimeScheduler_Legacy.h"
#include "SchedulerPriorityUtils.h"

#include <cxxreact/ErrorUtils.h>
#include <cxxreact/SystraceSection.h>
#include <react/renderer/consistency/ScopedShadowTreeRevisionLock.h>
#include <utility>
Expand All @@ -19,8 +18,11 @@ namespace facebook::react {

RuntimeScheduler_Legacy::RuntimeScheduler_Legacy(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now)
: runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {}
std::function<RuntimeSchedulerTimePoint()> now,
RuntimeSchedulerTaskErrorHandler onTaskError)
: runtimeExecutor_(std::move(runtimeExecutor)),
now_(std::move(now)),
onTaskError_(std::move(onTaskError)) {}

void RuntimeScheduler_Legacy::scheduleWork(RawCallback&& callback) noexcept {
SystraceSection s("RuntimeScheduler::scheduleWork");
Expand Down Expand Up @@ -167,7 +169,7 @@ void RuntimeScheduler_Legacy::callExpiredTasks(jsi::Runtime& runtime) {
executeTask(runtime, topPriorityTask, didUserCallbackTimeout);
}
} catch (jsi::JSError& error) {
handleJSError(runtime, error, true);
onTaskError_(runtime, error);
}

currentPriority_ = previousPriority;
Expand Down Expand Up @@ -224,7 +226,7 @@ void RuntimeScheduler_Legacy::startWorkLoop(jsi::Runtime& runtime) {
executeTask(runtime, topPriorityTask, didUserCallbackTimeout);
}
} catch (jsi::JSError& error) {
handleJSError(runtime, error, true);
onTaskError_(runtime, error);
}

currentPriority_ = previousPriority;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase {
public:
explicit RuntimeScheduler_Legacy(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now);
std::function<RuntimeSchedulerTimePoint()> now,
RuntimeSchedulerTaskErrorHandler onTaskError);

/*
* Not copyable.
Expand Down Expand Up @@ -179,6 +180,8 @@ class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase {

ShadowTreeRevisionConsistencyManager* shadowTreeRevisionConsistencyManager_{
nullptr};

RuntimeSchedulerTaskErrorHandler onTaskError_;
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "RuntimeScheduler_Modern.h"
#include "SchedulerPriorityUtils.h"

#include <cxxreact/ErrorUtils.h>
#include <cxxreact/SystraceSection.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/consistency/ScopedShadowTreeRevisionLock.h>
Expand All @@ -33,8 +32,11 @@ std::chrono::milliseconds getResolvedTimeoutForIdleTask(

RuntimeScheduler_Modern::RuntimeScheduler_Modern(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now)
: runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {}
std::function<RuntimeSchedulerTimePoint()> now,
RuntimeSchedulerTaskErrorHandler onTaskError)
: runtimeExecutor_(std::move(runtimeExecutor)),
now_(std::move(now)),
onTaskError_(std::move(onTaskError)) {}

void RuntimeScheduler_Modern::scheduleWork(RawCallback&& callback) noexcept {
SystraceSection s("RuntimeScheduler::scheduleWork");
Expand Down Expand Up @@ -384,7 +386,7 @@ void RuntimeScheduler_Modern::executeTask(
task.callback = result.getObject(runtime).getFunction(runtime);
}
} catch (jsi::JSError& error) {
handleJSError(runtime, error, true);
onTaskError_(runtime, error);
}
}

Expand Down Expand Up @@ -418,7 +420,7 @@ void RuntimeScheduler_Modern::performMicrotaskCheckpoint(
break;
}
} catch (jsi::JSError& error) {
handleJSError(runtime, error, true);
onTaskError_(runtime, error);
}
retries++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase {
public:
explicit RuntimeScheduler_Modern(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now);
std::function<RuntimeSchedulerTimePoint()> now,
RuntimeSchedulerTaskErrorHandler onTaskError);

/*
* Not copyable.
Expand Down Expand Up @@ -222,6 +223,8 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase {
nullptr};

PerformanceEntryReporter* performanceEntryReporter_{nullptr};

RuntimeSchedulerTaskErrorHandler onTaskError_;
};

} // namespace facebook::react
Loading
Loading