Skip to content

Commit 63a6c61

Browse files
committed
Ensure the temporary corruption of internal state goes unnoticed
1 parent 6763e4e commit 63a6c61

File tree

2 files changed

+26
-11
lines changed

2 files changed

+26
-11
lines changed

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,7 @@ describe('ReactFlightDOMNode', () => {
806806
resolvePendingPromise = value => {
807807
p2.status = 'fulfilled';
808808
p2.value = value;
809+
resolve(value);
809810
};
810811
});
811812
const p3 = new Promise(() => {});
@@ -885,11 +886,11 @@ describe('ReactFlightDOMNode', () => {
885886
},
886887
);
887888

889+
resolvePendingPromise('custom-instrum-resolve');
888890
await serverAct(
889891
async () =>
890892
new Promise(resolve => {
891893
setImmediate(() => {
892-
resolvePendingPromise();
893894
clientAbortController.abort();
894895
resolve();
895896
});
@@ -905,7 +906,9 @@ describe('ReactFlightDOMNode', () => {
905906
expect(normalizeCodeLocInfo(componentStack)).toBe(
906907
'\n' +
907908
' in SharedComponent (at **)\n' +
908-
' in ServerComponent\n' +
909+
' in ServerComponent' +
910+
(gate(flags => flags.enableAsyncDebugInfo) ? ' (at **)' : '') +
911+
'\n' +
909912
' in Suspense\n' +
910913
' in body\n' +
911914
' in html\n' +
@@ -927,11 +930,13 @@ describe('ReactFlightDOMNode', () => {
927930
expect(ignoreListStack(ownerStack)).toBe(
928931
// eslint-disable-next-line react-internal/safe-string-coercion
929932
'' +
933+
// The concrete location may change as this test is updated.
934+
// Just make sure they still point at React.use(p2)
930935
(gate(flags => flags.enableAsyncDebugInfo)
931936
? '\n at SharedComponent (./ReactFlightDOMNode-test.js:791:7)'
932937
: '') +
933-
'\n at ServerComponent (file://./ReactFlightDOMNode-test.js:812:26)' +
934-
'\n at App (file://./ReactFlightDOMNode-test.js:829:25)',
938+
'\n at ServerComponent (file://./ReactFlightDOMNode-test.js:813:26)' +
939+
'\n at App (file://./ReactFlightDOMNode-test.js:830:25)',
935940
);
936941
} else {
937942
expect(ownerStack).toBeNull();
@@ -1522,12 +1527,12 @@ describe('ReactFlightDOMNode', () => {
15221527
'\n' +
15231528
' in Dynamic' +
15241529
(gate(flags => flags.enableAsyncDebugInfo)
1525-
? ' (file://ReactFlightDOMNode-test.js:1392:27)\n'
1530+
? ' (file://ReactFlightDOMNode-test.js:1397:27)\n'
15261531
: '\n') +
15271532
' in body\n' +
15281533
' in html\n' +
1529-
' in App (file://ReactFlightDOMNode-test.js:1409:25)\n' +
1530-
' in ClientRoot (ReactFlightDOMNode-test.js:1484:16)',
1534+
' in App (file://ReactFlightDOMNode-test.js:1414:25)\n' +
1535+
' in ClientRoot (ReactFlightDOMNode-test.js:1489:16)',
15311536
);
15321537
} else {
15331538
expect(
@@ -1536,7 +1541,7 @@ describe('ReactFlightDOMNode', () => {
15361541
'\n' +
15371542
' in body\n' +
15381543
' in html\n' +
1539-
' in ClientRoot (ReactFlightDOMNode-test.js:1484:16)',
1544+
' in ClientRoot (ReactFlightDOMNode-test.js:1489:16)',
15401545
);
15411546
}
15421547

@@ -1546,16 +1551,16 @@ describe('ReactFlightDOMNode', () => {
15461551
normalizeCodeLocInfo(ownerStack, {preserveLocation: true}),
15471552
).toBe(
15481553
'\n' +
1549-
' in Dynamic (file://ReactFlightDOMNode-test.js:1392:27)\n' +
1550-
' in App (file://ReactFlightDOMNode-test.js:1409:25)',
1554+
' in Dynamic (file://ReactFlightDOMNode-test.js:1397:27)\n' +
1555+
' in App (file://ReactFlightDOMNode-test.js:1414:25)',
15511556
);
15521557
} else {
15531558
expect(
15541559
normalizeCodeLocInfo(ownerStack, {preserveLocation: true}),
15551560
).toBe(
15561561
'' +
15571562
'\n' +
1558-
' in App (file://ReactFlightDOMNode-test.js:1409:25)',
1563+
' in App (file://ReactFlightDOMNode-test.js:1414:25)',
15591564
);
15601565
}
15611566
} else {

packages/react-server/src/ReactFizzThenable.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,22 @@ export function ensureSuspendableThenableStateDEV(
255255
): () => void {
256256
if (__DEV__) {
257257
const lastThenable = thenableState[thenableState.length - 1];
258+
// Reset the last thenable back to pending.
258259
switch (lastThenable.status) {
259260
case 'fulfilled':
260261
const previousThenableValue = lastThenable.value;
262+
// $FlowIgnore[method-unbinding] We rebind .then immediately.
263+
const previousThenableThen = lastThenable.then.bind(lastThenable);
261264
delete lastThenable.value;
262265
delete (lastThenable: any).status;
266+
// We'll call .then again if we resuspend. Since we potentially corrupted
267+
// the internal state of unknown classes, we need to diffuse the potential
268+
// crash by replacing the .then method with a noop.
269+
// $FlowFixMe[cannot-write] Custom userspace Thenables may not be but native Promises are.
270+
lastThenable.then = noop;
263271
return () => {
272+
// $FlowFixMe[cannot-write] Custom userspace Thenables may not be but native Promises are.
273+
lastThenable.then = previousThenableThen;
264274
lastThenable.value = previousThenableValue;
265275
lastThenable.status = 'fulfilled';
266276
};

0 commit comments

Comments
 (0)