Skip to content

Commit 07dc04d

Browse files
authored
Merge pull request #8961 from acdlite/fiberbreakonuncaught
[Fiber] Preserve "Break on all/uncaught exceptions" behavior in DEV mode
2 parents 0934ab9 + 2c59713 commit 07dc04d

File tree

8 files changed

+237
-122
lines changed

8 files changed

+237
-122
lines changed

scripts/fiber/tests-passing.txt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,11 +1667,20 @@ src/renderers/shared/stack/reconciler/__tests__/Transaction-test.js
16671667
* should allow nesting of transactions
16681668

16691669
src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js
1670-
* should call the callback with only the passed argument
1671-
* should catch errors
1672-
* should rethrow caught errors
1673-
* should call the callback with only the passed argument
1674-
* should use invokeGuardedCallbackWithCatch in production
1670+
* it should rethrow errors caught by invokeGuardedCallbackAndCatchFirstError (development)
1671+
* should call the callback the passed arguments (development)
1672+
* should call the callback with the provided context (development)
1673+
* should return a caught error (development)
1674+
* should return null if no error is thrown (development)
1675+
* can nest with same debug name (development)
1676+
* does not return nested errors (development)
1677+
* it should rethrow errors caught by invokeGuardedCallbackAndCatchFirstError (production)
1678+
* should call the callback the passed arguments (production)
1679+
* should call the callback with the provided context (production)
1680+
* should return a caught error (production)
1681+
* should return null if no error is thrown (production)
1682+
* can nest with same debug name (production)
1683+
* does not return nested errors (production)
16751684

16761685
src/renderers/shared/utils/__tests__/accumulateInto-test.js
16771686
* throws if the second item is null

src/renderers/native/__tests__/ReactNativeEvents-test.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
var RCTEventEmitter;
1515
var React;
16-
var ReactErrorUtils;
1716
var ReactNative;
1817
var ResponderEventPlugin;
1918
var UIManager;
@@ -24,16 +23,10 @@ beforeEach(() => {
2423

2524
RCTEventEmitter = require('RCTEventEmitter');
2625
React = require('React');
27-
ReactErrorUtils = require('ReactErrorUtils');
2826
ReactNative = require('ReactNative');
2927
ResponderEventPlugin = require('ResponderEventPlugin');
3028
UIManager = require('UIManager');
3129
createReactNativeComponentClass = require('createReactNativeComponentClass');
32-
33-
// Ensure errors from event callbacks are properly surfaced (otherwise,
34-
// jest/jsdom swallows them when we do the .dispatchEvent call)
35-
ReactErrorUtils.invokeGuardedCallback =
36-
ReactErrorUtils.invokeGuardedCallbackWithCatch;
3730
});
3831

3932
it('handles events', () => {

src/renderers/shared/fiber/ReactFiberCommitWork.js

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var {
2626
} = ReactTypeOfWork;
2727
var { commitCallbacks } = require('ReactFiberUpdateQueue');
2828
var { onCommitUnmount } = require('ReactFiberDevToolsHook');
29+
var { invokeGuardedCallback } = require('ReactErrorUtils');
2930

3031
var {
3132
Placement,
@@ -54,22 +55,35 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
5455

5556
// Capture errors so they don't interrupt unmounting.
5657
function safelyCallComponentWillUnmount(current, instance) {
57-
try {
58-
instance.componentWillUnmount();
59-
} catch (error) {
60-
captureError(current, error);
58+
if (__DEV__) {
59+
const unmountError = invokeGuardedCallback(null, instance.componentWillUnmount, instance);
60+
if (unmountError) {
61+
captureError(current, unmountError);
62+
}
63+
} else {
64+
try {
65+
instance.componentWillUnmount();
66+
} catch (unmountError) {
67+
captureError(current, unmountError);
68+
}
6169
}
6270
}
6371

64-
// Capture errors so they don't interrupt unmounting.
6572
function safelyDetachRef(current : Fiber) {
66-
try {
67-
const ref = current.ref;
68-
if (ref !== null) {
69-
ref(null);
73+
const ref = current.ref;
74+
if (ref !== null) {
75+
if (__DEV__) {
76+
const refError = invokeGuardedCallback(null, ref, null, null);
77+
if (refError !== null) {
78+
captureError(current, refError);
79+
}
80+
} else {
81+
try {
82+
ref(null);
83+
} catch (refError) {
84+
captureError(current, refError);
85+
}
7086
}
71-
} catch (error) {
72-
captureError(current, error);
7387
}
7488
}
7589

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var {
3535
getStackAddendumByWorkInProgressFiber,
3636
} = require('ReactComponentTreeHook');
3737
var { logCapturedError } = require('ReactFiberErrorLogger');
38+
var { invokeGuardedCallback } = require('ReactErrorUtils');
3839

3940
var ReactFiberBeginWork = require('ReactFiberBeginWork');
4041
var ReactFiberCompleteWork = require('ReactFiberCompleteWork');
@@ -164,6 +165,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
164165
// Keeps track of whether we're currently in a work loop.
165166
let isPerformingWork : boolean = false;
166167

168+
// Keeps track of whether the current deadline has expired.
169+
let deadlineHasExpired : boolean = false;
170+
167171
// Keeps track of whether we should should batch sync updates.
168172
let isBatchingUpdates : boolean = false;
169173

@@ -414,9 +418,17 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
414418
// ref unmounts.
415419
nextEffect = firstEffect;
416420
while (nextEffect !== null) {
417-
try {
418-
commitAllHostEffects(finishedWork);
419-
} catch (error) {
421+
let error = null;
422+
if (__DEV__) {
423+
error = invokeGuardedCallback(null, commitAllHostEffects, null, finishedWork);
424+
} else {
425+
try {
426+
commitAllHostEffects(finishedWork);
427+
} catch (e) {
428+
error = e;
429+
}
430+
}
431+
if (error !== null) {
420432
invariant(
421433
nextEffect !== null,
422434
'Should have next effect. This error is likely caused by a bug ' +
@@ -444,9 +456,17 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
444456
// This pass also triggers any renderer-specific initial effects.
445457
nextEffect = firstEffect;
446458
while (nextEffect !== null) {
447-
try {
448-
commitAllLifeCycles(finishedWork, nextEffect);
449-
} catch (error) {
459+
let error = null;
460+
if (__DEV__) {
461+
error = invokeGuardedCallback(null, commitAllLifeCycles, null, finishedWork);
462+
} else {
463+
try {
464+
commitAllLifeCycles(finishedWork);
465+
} catch (e) {
466+
error = e;
467+
}
468+
}
469+
if (error !== null) {
450470
invariant(
451471
nextEffect !== null,
452472
'Should have next effect. This error is likely caused by a bug ' +
@@ -675,7 +695,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
675695
}
676696
}
677697

678-
function workLoop(priorityLevel, deadline : Deadline | null, deadlineHasExpired : boolean) : boolean {
698+
function workLoop(priorityLevel, deadline : Deadline | null) {
679699
// Clear any errors.
680700
clearErrors();
681701

@@ -743,8 +763,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
743763
if (hostRootTimeMarker) {
744764
console.timeEnd(hostRootTimeMarker);
745765
}
746-
747-
return deadlineHasExpired;
748766
}
749767

750768
function performWork(priorityLevel : PriorityLevel, deadline : Deadline | null) {
@@ -755,7 +773,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
755773
);
756774
isPerformingWork = true;
757775
const isPerformingDeferredWork = Boolean(deadline);
758-
let deadlineHasExpired = false;
759776

760777
// This outer loop exists so that we can restart the work loop after
761778
// catching an error. It also lets us flush Task work at the end of a
@@ -776,18 +793,25 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
776793
// Nothing in performWork should be allowed to throw. All unsafe
777794
// operations must happen within workLoop, which is extracted to a
778795
// separate function so that it can be optimized by the JS engine.
779-
try {
780-
priorityContextBeforeReconciliation = priorityContext;
781-
priorityContext = nextPriorityLevel;
782-
deadlineHasExpired = workLoop(priorityLevel, deadline, deadlineHasExpired);
783-
} catch (error) {
796+
priorityContextBeforeReconciliation = priorityContext;
797+
let error = null;
798+
if (__DEV__) {
799+
error = invokeGuardedCallback(null, workLoop, null, priorityLevel, deadline);
800+
} else {
801+
try {
802+
workLoop(priorityLevel, deadline);
803+
} catch (e) {
804+
error = e;
805+
}
806+
}
807+
// Reset the priority context to its value before reconcilation.
808+
priorityContext = priorityContextBeforeReconciliation;
809+
810+
if (error !== null) {
784811
// We caught an error during either the begin or complete phases.
785812
const failedWork = nextUnitOfWork;
786813

787814
if (failedWork !== null) {
788-
// Reset the priority context to its value before reconciliation.
789-
priorityContext = priorityContextBeforeReconciliation;
790-
791815
// "Capture" the error by finding the nearest boundary. If there is no
792816
// error boundary, the nearest host container acts as one. If
793817
// captureError returns null, the error was intentionally ignored.
@@ -818,8 +842,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
818842
// inside resetAfterCommit.
819843
fatalError = error;
820844
}
821-
} finally {
822-
priorityContext = priorityContextBeforeReconciliation;
823845
}
824846

825847
// Stop performing work
@@ -862,6 +884,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(config : HostConfig<T, P,
862884

863885
// We're done performing work. Time to clean up.
864886
isPerformingWork = false;
887+
deadlineHasExpired = false;
865888
fatalError = null;
866889
firstUncaughtError = null;
867890
capturedErrors = null;

src/renderers/shared/shared/event/EventPluginUtils.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,7 @@ if (__DEV__) {
8989
function executeDispatch(event, simulated, listener, inst) {
9090
var type = event.type || 'unknown-event';
9191
event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);
92-
if (simulated) {
93-
ReactErrorUtils.invokeGuardedCallbackWithCatch(
94-
type,
95-
listener,
96-
event
97-
);
98-
} else {
99-
ReactErrorUtils.invokeGuardedCallback(type, listener, event);
100-
}
92+
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
10193
event.currentTarget = null;
10294
}
10395

src/renderers/shared/stack/reconciler/ReactCompositeComponent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ var ReactCompositeComponent = {
557557
if (safely) {
558558
if (!skipLifecycle) {
559559
var name = this.getName() + '.componentWillUnmount()';
560-
ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst));
560+
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(name, inst.componentWillUnmount, inst);
561561
}
562562
} else {
563563
if (__DEV__) {

0 commit comments

Comments
 (0)