Skip to content

Commit

Permalink
earlyjs: Integrate c++ pipeline with console.error (#47169)
Browse files Browse the repository at this point in the history
Summary:

## Changes
If the c++ pipeline is active:

If someone calls console.error:
- The c++ pipeline will report it as a soft error

If someone reports an error:
- The c++ pipeline will log it via console.error


Changelog: [Internal]

Differential Revision: D64506069
  • Loading branch information
RSNara authored and facebook-github-bot committed Oct 25, 2024
1 parent e34fcb0 commit 30010b0
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 21 deletions.
33 changes: 22 additions & 11 deletions packages/react-native/Libraries/Core/ExceptionsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ let inExceptionHandler = false;
*/
function handleException(e: mixed, isFatal: boolean) {
// TODO(T196834299): We should really use a c++ turbomodule for this
const reportToConsole = true;
if (
!global.RN$handleException ||
!global.RN$handleException(e, isFatal)
!global.RN$handleException(e, isFatal, reportToConsole)
) {
let error: Error;
if (e instanceof Error) {
Expand All @@ -161,7 +162,7 @@ function handleException(e: mixed, isFatal: boolean) {
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
* parameters */
// $FlowFixMe[incompatible-call]
reportException(error, isFatal, /*reportToConsole*/ true);
reportException(error, isFatal, reportToConsole);
} finally {
inExceptionHandler = false;
}
Expand All @@ -176,7 +177,10 @@ function reactConsoleErrorHandler(...args) {
if (!console.reportErrorsAsExceptions) {
return;
}
if (inExceptionHandler) {
if (
inExceptionHandler ||
(global.RN$inExceptionHandler && global.RN$inExceptionHandler())
) {
// The fundamental trick here is that are multiple entry point to logging errors:
// (see D19743075 for more background)
//
Expand Down Expand Up @@ -230,14 +234,21 @@ function reactConsoleErrorHandler(...args) {
error.name = 'console.error';
}

reportException(
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
* parameters */
// $FlowFixMe[incompatible-call]
error,
false, // isFatal
false, // reportToConsole
);
const isFatal = false;
const reportToConsole = false;
if (
!global.RN$handleException ||
!global.RN$handleException(error, isFatal, reportToConsole)
) {
reportException(
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
* parameters */
// $FlowFixMe[incompatible-call]
error,
isFatal,
reportToConsole,
);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ jsi::Object wrapInErrorIfNecessary(
: 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;
}
};

} // namespace

namespace facebook::react {
Expand Down Expand Up @@ -162,7 +178,7 @@ std::ostream& operator<<(

JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError)
: _onJsError(std::move(onJsError)),
_hasHandledFatalError(false){
_inErrorHandler(std::make_shared<bool>(false)){

};

Expand All @@ -171,7 +187,8 @@ JsErrorHandler::~JsErrorHandler() {}
void JsErrorHandler::handleError(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal) {
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 (_isRuntimeReady) {
Expand All @@ -191,13 +208,17 @@ void JsErrorHandler::handleError(
}
}

emitError(runtime, error, isFatal);
handleErrorWithCppPipeline(runtime, error, isFatal, logToConsole);
}

void JsErrorHandler::emitError(
void JsErrorHandler::handleErrorWithCppPipeline(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal) {
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");
Expand Down Expand Up @@ -278,6 +299,14 @@ void JsErrorHandler::emitError(
isTruthy(runtime, errorObj.getProperty(runtime, "isComponentError"));
data.setProperty(runtime, "isComponentError", isComponentError);

if (logToConsole) {
auto console = runtime.global().getPropertyAsObject(runtime, "console");
auto errorFn = console.getPropertyAsFunction(runtime, "error");
auto finalMessage =
jsi::String::createFromUtf8(runtime, parsedError.message);
errorFn.callWithThis(runtime, console, finalMessage);
}

std::shared_ptr<bool> shouldPreventDefault = std::make_shared<bool>(false);
auto preventDefault = jsi::Function::createFromHostFunction(
runtime,
Expand Down Expand Up @@ -330,4 +359,8 @@ void JsErrorHandler::notifyOfFatalError() {
_hasHandledFatalError = true;
}

bool JsErrorHandler::inErrorHandler() {
return *_inErrorHandler;
}

} // namespace facebook::react
14 changes: 12 additions & 2 deletions packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,18 @@ class JsErrorHandler {
explicit JsErrorHandler(OnJsError onJsError);
~JsErrorHandler();

void handleError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal);
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:
/**
Expand All @@ -62,9 +67,14 @@ class JsErrorHandler {
OnJsError _onJsError;
bool _hasHandledFatalError;
bool _isRuntimeReady{};
std::shared_ptr<bool> _inErrorHandler;
std::vector<std::function<void(jsi::Runtime&, jsi::Value)>> _errorListeners;

void emitError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal);
void handleErrorWithCppPipeline(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal,
bool logToConsole);
};

} // namespace facebook::react
28 changes: 25 additions & 3 deletions packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,14 +409,29 @@ void ReactInstance::initializeRuntime(

defineReactInstanceFlags(runtime, options);

defineReadOnlyGlobal(
runtime,
"RN$inExceptionHandler",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "inExceptionHandler"),
0,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& /*runtime*/,
const jsi::Value& /*unused*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
return jsErrorHandler->inErrorHandler();
}));

// TODO(T196834299): We should really use a C++ turbomodule for this
defineReadOnlyGlobal(
runtime,
"RN$handleException",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "handleException"),
2,
3,
[jsErrorHandler = jsErrorHandler_](
jsi::Runtime& runtime,
const jsi::Value& /*unused*/,
Expand All @@ -425,7 +440,7 @@ void ReactInstance::initializeRuntime(
if (count < 2) {
throw jsi::JSError(
runtime,
"handleException requires 2 arguments: error, isFatal");
"handleException requires 3 arguments: error, isFatal, logToConsole (optional)");
}

auto isFatal = isTruthy(runtime, args[1]);
Expand All @@ -439,7 +454,14 @@ void ReactInstance::initializeRuntime(

auto jsError =
jsi::JSError(runtime, jsi::Value(runtime, args[0]));
jsErrorHandler->handleError(runtime, jsError, isFatal);

if (count == 2) {
jsErrorHandler->handleError(runtime, jsError, isFatal);
} else {
auto logToConsole = isTruthy(runtime, args[2]);
jsErrorHandler->handleError(
runtime, jsError, isFatal, logToConsole);
}

return jsi::Value(true);
}));
Expand Down

0 comments on commit 30010b0

Please sign in to comment.