Skip to content

Commit 0946f04

Browse files
RSNarafacebook-github-bot
authored andcommitted
earlyjs: Integrate c++ pipeline with console.error
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] Reviewed By: javache Differential Revision: D64506069
1 parent 79a933f commit 0946f04

File tree

4 files changed

+101
-22
lines changed

4 files changed

+101
-22
lines changed

packages/react-native/Libraries/Core/ExceptionsManager.js

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,11 @@ let inExceptionHandler = false;
142142
*/
143143
function handleException(e: mixed, isFatal: boolean) {
144144
// TODO(T196834299): We should really use a c++ turbomodule for this
145-
if (!global.RN$handleException || !global.RN$handleException(e, isFatal)) {
145+
const reportToConsole = true;
146+
if (
147+
!global.RN$handleException ||
148+
!global.RN$handleException(e, isFatal, reportToConsole)
149+
) {
146150
let error: Error;
147151
if (e instanceof Error) {
148152
error = e;
@@ -158,7 +162,7 @@ function handleException(e: mixed, isFatal: boolean) {
158162
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
159163
* parameters */
160164
// $FlowFixMe[incompatible-call]
161-
reportException(error, isFatal, /*reportToConsole*/ true);
165+
reportException(error, isFatal, reportToConsole);
162166
} finally {
163167
inExceptionHandler = false;
164168
}
@@ -173,7 +177,10 @@ function reactConsoleErrorHandler(...args) {
173177
if (!console.reportErrorsAsExceptions) {
174178
return;
175179
}
176-
if (inExceptionHandler) {
180+
if (
181+
inExceptionHandler ||
182+
(global.RN$inExceptionHandler && global.RN$inExceptionHandler())
183+
) {
177184
// The fundamental trick here is that are multiple entry point to logging errors:
178185
// (see D19743075 for more background)
179186
//
@@ -227,14 +234,21 @@ function reactConsoleErrorHandler(...args) {
227234
error.name = 'console.error';
228235
}
229236

230-
reportException(
231-
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
232-
* parameters */
233-
// $FlowFixMe[incompatible-call]
234-
error,
235-
false, // isFatal
236-
false, // reportToConsole
237-
);
237+
const isFatal = false;
238+
const reportToConsole = false;
239+
if (
240+
!global.RN$handleException ||
241+
!global.RN$handleException(error, isFatal, reportToConsole)
242+
) {
243+
reportException(
244+
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
245+
* parameters */
246+
// $FlowFixMe[incompatible-call]
247+
error,
248+
isFatal,
249+
reportToConsole,
250+
);
251+
}
238252
}
239253

240254
/**

packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ jsi::Object wrapInErrorIfNecessary(
6262
: Error.callAsConstructor(runtime, value).getObject(runtime);
6363
return error;
6464
}
65+
66+
class SetFalseOnDestruct {
67+
std::shared_ptr<bool> _value;
68+
69+
public:
70+
SetFalseOnDestruct(const SetFalseOnDestruct&) = delete;
71+
SetFalseOnDestruct& operator=(const SetFalseOnDestruct&) = delete;
72+
SetFalseOnDestruct(SetFalseOnDestruct&&) = delete;
73+
SetFalseOnDestruct& operator=(SetFalseOnDestruct&&) = delete;
74+
explicit SetFalseOnDestruct(std::shared_ptr<bool> value)
75+
: _value(std::move(value)) {}
76+
~SetFalseOnDestruct() {
77+
*_value = false;
78+
}
79+
};
80+
6581
} // namespace
6682

6783
namespace facebook::react {
@@ -162,7 +178,7 @@ std::ostream& operator<<(
162178

163179
JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError)
164180
: _onJsError(std::move(onJsError)),
165-
_hasHandledFatalError(false){
181+
_inErrorHandler(std::make_shared<bool>(false)){
166182

167183
};
168184

@@ -171,7 +187,8 @@ JsErrorHandler::~JsErrorHandler() {}
171187
void JsErrorHandler::handleError(
172188
jsi::Runtime& runtime,
173189
jsi::JSError& error,
174-
bool isFatal) {
190+
bool isFatal,
191+
bool logToConsole) {
175192
// TODO: Current error parsing works and is stable. Can investigate using
176193
// REGEX_HERMES to get additional Hermes data, though it requires JS setup
177194
if (_isRuntimeReady) {
@@ -191,13 +208,17 @@ void JsErrorHandler::handleError(
191208
}
192209
}
193210

194-
emitError(runtime, error, isFatal);
211+
handleErrorWithCppPipeline(runtime, error, isFatal, logToConsole);
195212
}
196213

197-
void JsErrorHandler::emitError(
214+
void JsErrorHandler::handleErrorWithCppPipeline(
198215
jsi::Runtime& runtime,
199216
jsi::JSError& error,
200-
bool isFatal) {
217+
bool isFatal,
218+
bool logToConsole) {
219+
*_inErrorHandler = true;
220+
SetFalseOnDestruct temp{_inErrorHandler};
221+
201222
auto message = error.getMessage();
202223
auto errorObj = wrapInErrorIfNecessary(runtime, error.value());
203224
auto componentStackValue = errorObj.getProperty(runtime, "componentStack");
@@ -278,6 +299,14 @@ void JsErrorHandler::emitError(
278299
isTruthy(runtime, errorObj.getProperty(runtime, "isComponentError"));
279300
data.setProperty(runtime, "isComponentError", isComponentError);
280301

302+
if (logToConsole) {
303+
auto console = runtime.global().getPropertyAsObject(runtime, "console");
304+
auto errorFn = console.getPropertyAsFunction(runtime, "error");
305+
auto finalMessage =
306+
jsi::String::createFromUtf8(runtime, parsedError.message);
307+
errorFn.callWithThis(runtime, console, finalMessage);
308+
}
309+
281310
std::shared_ptr<bool> shouldPreventDefault = std::make_shared<bool>(false);
282311
auto preventDefault = jsi::Function::createFromHostFunction(
283312
runtime,
@@ -330,4 +359,8 @@ void JsErrorHandler::notifyOfFatalError() {
330359
_hasHandledFatalError = true;
331360
}
332361

362+
bool JsErrorHandler::inErrorHandler() {
363+
return *_inErrorHandler;
364+
}
365+
333366
} // namespace facebook::react

packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,18 @@ class JsErrorHandler {
4343
explicit JsErrorHandler(OnJsError onJsError);
4444
~JsErrorHandler();
4545

46-
void handleError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal);
46+
void handleError(
47+
jsi::Runtime& runtime,
48+
jsi::JSError& error,
49+
bool isFatal,
50+
bool logToConsole = true);
4751
bool hasHandledFatalError();
4852
void registerErrorListener(
4953
const std::function<void(jsi::Runtime&, jsi::Value)>& listener);
5054
void setRuntimeReady();
5155
bool isRuntimeReady();
5256
void notifyOfFatalError();
57+
bool inErrorHandler();
5358

5459
private:
5560
/**
@@ -60,11 +65,16 @@ class JsErrorHandler {
6065
* teardown get reported properly.
6166
**/
6267
OnJsError _onJsError;
63-
bool _hasHandledFatalError;
68+
bool _hasHandledFatalError{};
6469
bool _isRuntimeReady{};
70+
std::shared_ptr<bool> _inErrorHandler;
6571
std::vector<std::function<void(jsi::Runtime&, jsi::Value)>> _errorListeners;
6672

67-
void emitError(jsi::Runtime& runtime, jsi::JSError& error, bool isFatal);
73+
void handleErrorWithCppPipeline(
74+
jsi::Runtime& runtime,
75+
jsi::JSError& error,
76+
bool isFatal,
77+
bool logToConsole);
6878
};
6979

7080
} // namespace facebook::react

packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,14 +409,29 @@ void ReactInstance::initializeRuntime(
409409

410410
defineReactInstanceFlags(runtime, options);
411411

412+
defineReadOnlyGlobal(
413+
runtime,
414+
"RN$inExceptionHandler",
415+
jsi::Function::createFromHostFunction(
416+
runtime,
417+
jsi::PropNameID::forAscii(runtime, "inExceptionHandler"),
418+
0,
419+
[jsErrorHandler = jsErrorHandler_](
420+
jsi::Runtime& /*runtime*/,
421+
const jsi::Value& /*unused*/,
422+
const jsi::Value* /*args*/,
423+
size_t /*count*/) {
424+
return jsErrorHandler->inErrorHandler();
425+
}));
426+
412427
// TODO(T196834299): We should really use a C++ turbomodule for this
413428
defineReadOnlyGlobal(
414429
runtime,
415430
"RN$handleException",
416431
jsi::Function::createFromHostFunction(
417432
runtime,
418433
jsi::PropNameID::forAscii(runtime, "handleException"),
419-
2,
434+
3,
420435
[jsErrorHandler = jsErrorHandler_](
421436
jsi::Runtime& runtime,
422437
const jsi::Value& /*unused*/,
@@ -425,7 +440,7 @@ void ReactInstance::initializeRuntime(
425440
if (count < 2) {
426441
throw jsi::JSError(
427442
runtime,
428-
"handleException requires 2 arguments: error, isFatal");
443+
"handleException requires 3 arguments: error, isFatal, logToConsole (optional)");
429444
}
430445

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

440455
auto jsError =
441456
jsi::JSError(runtime, jsi::Value(runtime, args[0]));
442-
jsErrorHandler->handleError(runtime, jsError, isFatal);
457+
458+
if (count == 2) {
459+
jsErrorHandler->handleError(runtime, jsError, isFatal);
460+
} else {
461+
auto logToConsole = isTruthy(runtime, args[2]);
462+
jsErrorHandler->handleError(
463+
runtime, jsError, isFatal, logToConsole);
464+
}
443465

444466
return jsi::Value(true);
445467
}));

0 commit comments

Comments
 (0)