main
js 74 lines 2.37 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * Copyright (c) 2017, Amjad Masad
4 *
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9 'use strict';
10
11 // Based on https://repl.it/site/blog/infinite-loops.
12
13 // This should be reasonable for all loops in the source.
14 // Note that if the numbers are too large, the tests will take too long to fail
15 // for this to be useful (each individual test case might hit an infinite loop).
16 const MAX_SOURCE_ITERATIONS = 6000;
17 // Code in tests themselves is permitted to run longer.
18 // For example, in the fuzz tester.
19 const MAX_TEST_ITERATIONS = 5000;
20
21 module.exports = ({types: t, template}) => {
22 // We set a global so that we can later fail the test
23 // even if the error ends up being caught by the code.
24 const buildGuard = template(`
25 if (%%iterator%%++ > %%maxIterations%%) {
26 global.infiniteLoopError = new RangeError(
27 'Potential infinite loop: exceeded ' +
28 %%maxIterations%% +
29 ' iterations.'
30 );
31 throw global.infiniteLoopError;
32 }
33 `);
34
35 return {
36 visitor: {
37 'WhileStatement|ForStatement|DoWhileStatement': (path, file) => {
38 const filename = file.file.opts.filename;
39 const maxIterations = t.logicalExpression(
40 '||',
41 t.memberExpression(
42 t.identifier('global'),
43 t.identifier('__MAX_ITERATIONS__')
44 ),
45 t.numericLiteral(
46 filename.indexOf('__tests__') === -1
47 ? MAX_SOURCE_ITERATIONS
48 : MAX_TEST_ITERATIONS
49 )
50 );
51
52 // An iterator that is incremented with each iteration
53 const iterator = path.scope.parent.generateUidIdentifier('loopIt');
54 const iteratorInit = t.numericLiteral(0);
55 path.scope.parent.push({
56 id: iterator,
57 init: iteratorInit,
58 });
59 // If statement and throw error if it matches our criteria
60 const guard = buildGuard({
61 iterator,
62 maxIterations,
63 });
64 // No block statement e.g. `while (1) 1;`
65 if (!path.get('body').isBlockStatement()) {
66 const statement = path.get('body').node;
67 path.get('body').replaceWith(t.blockStatement([guard, statement]));
68 } else {
69 path.get('body').unshiftContainer('body', guard);
70 }
71 },
72 },
73 };
74 };