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 = 1500;
    
  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. };