mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Component stacks have a similar problem to the problem with keyPath where we had to move it down and set it late right before recursing. Currently we work around that by popping exactly one off when something suspends. That doesn't work with the new server stacks being added which are more than one. It also meant that we kept having add a single frame that could be popped when there shouldn't need to be one. Unlike keyPath component stacks has this weird property that once something throws we might need the stack that was attempted for errors or the previous stack if we're going to retry and just recreate it. I've tried a few different approaches and I didn't like either but this is the one that seems least problematic. I first split out renderNodeDestructive into a retryNode helper. During retries only retryNode is called. When we first discover a node, we pass through renderNodeDestructive. Instead of add a component stack frame deep inside renderNodeDestructive after we've already refined a node, we now add it before in renderNodeDestructive. That way it's only added once before being attempted. This is similar to how Fiber works where in ChildFiber we match the node once to create the instance and then later do we attempt to actually render it and it's only the second part that's ever retried. This unfortunately means that we now have to refine the node down to element/lazy/thenables twice. To avoid refining the type too I move that to be done lazily.
75 lines
2.4 KiB
JavaScript
75 lines
2.4 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
* Copyright (c) 2017, Amjad Masad
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
// Based on https://repl.it/site/blog/infinite-loops.
|
|
|
|
// This should be reasonable for all loops in the source.
|
|
// Note that if the numbers are too large, the tests will take too long to fail
|
|
// for this to be useful (each individual test case might hit an infinite loop).
|
|
const MAX_SOURCE_ITERATIONS = 6000;
|
|
// Code in tests themselves is permitted to run longer.
|
|
// For example, in the fuzz tester.
|
|
const MAX_TEST_ITERATIONS = 5000;
|
|
|
|
module.exports = ({types: t, template}) => {
|
|
// We set a global so that we can later fail the test
|
|
// even if the error ends up being caught by the code.
|
|
const buildGuard = template(`
|
|
if (%%iterator%%++ > %%maxIterations%%) {
|
|
global.infiniteLoopError = new RangeError(
|
|
'Potential infinite loop: exceeded ' +
|
|
%%maxIterations%% +
|
|
' iterations.'
|
|
);
|
|
throw global.infiniteLoopError;
|
|
}
|
|
`);
|
|
|
|
return {
|
|
visitor: {
|
|
'WhileStatement|ForStatement|DoWhileStatement': (path, file) => {
|
|
const filename = file.file.opts.filename;
|
|
const maxIterations = t.logicalExpression(
|
|
'||',
|
|
t.memberExpression(
|
|
t.identifier('global'),
|
|
t.identifier('__MAX_ITERATIONS__')
|
|
),
|
|
t.numericLiteral(
|
|
filename.indexOf('__tests__') === -1
|
|
? MAX_SOURCE_ITERATIONS
|
|
: MAX_TEST_ITERATIONS
|
|
)
|
|
);
|
|
|
|
// An iterator that is incremented with each iteration
|
|
const iterator = path.scope.parent.generateUidIdentifier('loopIt');
|
|
const iteratorInit = t.numericLiteral(0);
|
|
path.scope.parent.push({
|
|
id: iterator,
|
|
init: iteratorInit,
|
|
});
|
|
// If statement and throw error if it matches our criteria
|
|
const guard = buildGuard({
|
|
iterator,
|
|
maxIterations,
|
|
});
|
|
// No block statement e.g. `while (1) 1;`
|
|
if (!path.get('body').isBlockStatement()) {
|
|
const statement = path.get('body').node;
|
|
path.get('body').replaceWith(t.blockStatement([guard, statement]));
|
|
} else {
|
|
path.get('body').unshiftContainer('body', guard);
|
|
}
|
|
},
|
|
},
|
|
};
|
|
};
|