Skip to content

Commit

Permalink
Implement always available js error handling (facebook#47466)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#47466

Now, when the useAlwaysAvailableJSErrorHandling feature flag is true, React Native will use the earlyjs c++ error reporting pipeline for handling all javascript errors!

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D64715159

fbshipit-source-id: 597a5278eb792f87dca10e06fa9816b3a8c47b84
  • Loading branch information
RSNara authored and facebook-github-bot committed Nov 7, 2024
1 parent 791afd3 commit 8b053a4
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 36 deletions.
42 changes: 42 additions & 0 deletions packages/polyfills/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,48 @@ if (global.nativeLoggingHook) {
assert: consoleAssertPolyfill,
};

// TODO(T206796580): This was copy-pasted from ExceptionsManager.js
// Delete the copy there after the c++ pipeline is rolled out everywhere.
if (global.RN$useAlwaysAvailableJSErrorHandling === true) {
let originalConsoleError = console.error;
console.reportErrorsAsExceptions = true;
function stringifySafe(arg) {
return inspect(arg, {depth: 10}).replaceAll(/\n\s*/g, ' ');
}
console.error = function (...args) {
originalConsoleError.apply(this, args);
if (!console.reportErrorsAsExceptions) {
return;
}
if (global.RN$inExceptionHandler?.()) {
return;
}
let error;

const firstArg = args[0];
if (firstArg?.stack) {
// RN$handleException will console.error this with high enough fidelity.
error = firstArg;
} else {
if (typeof firstArg === 'string' && firstArg.startsWith('Warning: ')) {
// React warnings use console.error so that a stack trace is shown, but
// we don't (currently) want these to show a redbox
return;
}
const message = args
.map(arg => (typeof arg === 'string' ? arg : stringifySafe(arg)))
.join(' ');

error = new Error(message);
error.name = 'console.error';
}

const isFatal = false;
const reportToConsole = false;
global.RN$handleException(error, isFatal, reportToConsole);
};
}

Object.defineProperty(console, '_isPolyfilled', {
value: true,
enumerable: false,
Expand Down
12 changes: 6 additions & 6 deletions packages/polyfills/error-guard.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ type Fn<Args, Return> = (...Args) => Return;
* when loading a module. This will report any errors encountered before
* ExceptionsManager is configured.
*/
let _globalHandler: ErrorHandler = function onError(
e: mixed,
isFatal: boolean,
) {
throw e;
};
let _globalHandler: ErrorHandler =
global.RN$useAlwaysAvailableJSErrorHandling === true
? global.RN$handleException
: (e: mixed, isFatal: boolean) => {
throw e;
};

/**
* The particular require runtime that we are using looks for a global
Expand Down
5 changes: 1 addition & 4 deletions packages/react-native/Libraries/Core/ExceptionsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,7 @@ function reactConsoleErrorHandler(...args) {
if (!console.reportErrorsAsExceptions) {
return;
}
if (
inExceptionHandler ||
(global.RN$inExceptionHandler && global.RN$inExceptionHandler())
) {
if (inExceptionHandler || global.RN$inExceptionHandler?.()) {
// The fundamental trick here is that are multiple entry point to logging errors:
// (see D19743075 for more background)
//
Expand Down
38 changes: 20 additions & 18 deletions packages/react-native/Libraries/Core/setUpErrorHandling.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,26 @@

'use strict';

/**
* Sets up the console and exception handling (redbox) for React Native.
* You can use this module directly, or just require InitializeCore.
*/
const ExceptionsManager = require('./ExceptionsManager');
ExceptionsManager.installConsoleErrorReporter();
if (global.RN$useAlwaysAvailableJSErrorHandling !== true) {
/**
* Sets up the console and exception handling (redbox) for React Native.
* You can use this module directly, or just require InitializeCore.
*/
const ExceptionsManager = require('./ExceptionsManager');
ExceptionsManager.installConsoleErrorReporter();

// Set up error handler
if (!global.__fbDisableExceptionsManager) {
const handleError = (e: mixed, isFatal: boolean) => {
try {
ExceptionsManager.handleException(e, isFatal);
} catch (ee) {
console.log('Failed to print error: ', ee.message);
throw e;
}
};
// Set up error handler
if (!global.__fbDisableExceptionsManager) {
const handleError = (e: mixed, isFatal: boolean) => {
try {
ExceptionsManager.handleException(e, isFatal);
} catch (ee) {
console.log('Failed to print error: ', ee.message);
throw e;
}
};

const ErrorUtils = require('../vendor/core/ErrorUtils');
ErrorUtils.setGlobalHandler(handleError);
const ErrorUtils = require('../vendor/core/ErrorUtils');
ErrorUtils.setGlobalHandler(handleError);
}
}
2 changes: 1 addition & 1 deletion packages/react-native/Libraries/LogBox/LogBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ if (__DEV__) {
if (global.RN$registerExceptionListener != null) {
global.RN$registerExceptionListener(
(error: ExtendedExceptionData & {preventDefault: () => mixed}) => {
if (!error.isFatal) {
if (global.RN$isRuntimeReady?.() || !error.isFatal) {
error.preventDefault();
addException(error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ target_link_libraries(jserrorhandler
jsi
folly_runtime
mapbufferjni
react_featureflags
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <cxxreact/ErrorUtils.h>
#include <glog/logging.h>
#include <react/bridging/Bridging.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <string>
#include "StackTraceParser.h"

Expand Down Expand Up @@ -228,7 +229,9 @@ void JsErrorHandler::handleError(
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) {

if (!ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling() &&
_isRuntimeReady) {
if (isFatal) {
_hasHandledFatalError = true;
}
Expand Down Expand Up @@ -325,7 +328,7 @@ void JsErrorHandler::handleErrorWithCppPipeline(
auto id = nextExceptionId();

ParsedError parsedError = {
.message = "EarlyJsError: " + message,
.message = _isRuntimeReady ? message : ("EarlyJsError: " + message),
.originalMessage = originalMessage,
.name = name,
.componentStack = componentStack,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Pod::Spec.new do |s|
s.dependency "React-cxxreact"
s.dependency "glog"
s.dependency "ReactCommon/turbomodule/bridging"
add_dependency(s, "React-featureflags")
add_dependency(s, "React-debug")

if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
Expand Down
35 changes: 30 additions & 5 deletions packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,27 @@ void ReactInstance::initializeRuntime(

defineReactInstanceFlags(runtime, options);

defineReadOnlyGlobal(
runtime,
"RN$useAlwaysAvailableJSErrorHandling",
jsi::Value(
ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling()));

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

defineReadOnlyGlobal(
runtime,
"RN$inExceptionHandler",
Expand Down Expand Up @@ -444,12 +465,16 @@ void ReactInstance::initializeRuntime(
}

auto isFatal = isTruthy(runtime, args[1]);
if (jsErrorHandler->isRuntimeReady()) {
if (isFatal) {
jsErrorHandler->notifyOfFatalError();
}

return jsi::Value(false);
if (!ReactNativeFeatureFlags::
useAlwaysAvailableJSErrorHandling()) {
if (jsErrorHandler->isRuntimeReady()) {
if (isFatal) {
jsErrorHandler->notifyOfFatalError();
}

return jsi::Value(false);
}
}

auto jsError =
Expand Down

0 comments on commit 8b053a4

Please sign in to comment.