Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Website crash #20

Open
joooKiwi opened this issue Jun 9, 2023 · 5 comments
Open

Website crash #20

joooKiwi opened this issue Jun 9, 2023 · 5 comments

Comments

@joooKiwi
Copy link

joooKiwi commented Jun 9, 2023

While attempting to do a performance difference between different kind of loops (with at least 100,000 entries),
I've done a mistake in the loop. Making it an infinite loop.

But, for some reason, the application cannot recover from it
https://jsperf.app/pexuwo/1/preview

The code was something like

const array = [...new Array(100000)].map((_,i)=>i)
// first loop
for(const a of array);
// second loop
const a = array[Symbol.iterator]()
const b = a.next()
while(!b.done);

But with that, it was an infinite loop.

@rd13
Copy link
Owner

rd13 commented Jun 9, 2023

There's not much I can do to prevent from an infinite loop crashing the browser in this instance - the only alternative I can see here to prevent the crash is running tests in a worker thread. I'll have a think at what else can be done but since the tests are run on the main thread then whatever code you run has the potential to crash the tab. It's the same as if you were to run the code from your console.

@ChuckTerry
Copy link

How about something along these lines:

class Loopless {
  static loopLimit = 2500;
  
  /* Format: [
   *   RegExp Object to match loop syntax,
   *   Array that is joined using a random number to replace the original loop
   * ][]
   */
  static regExps = [
    [
      /\s*?for\s*?\((.*?;.*?;[^\)]*?)\)\s*?\{(?!\nloop_)/,
      [
        ';\nconst loop_',
        ' = loopless.next();\nfor ($1) {\nloop_',
        '();\n'
      ]
    ],
    // ... Other loop structures go here ...
  ];
  
  constructor() {
    this.loops = [];
  }
  
  // Called before a loop, returns a function to increment the loops personal counter
  next() {
    const id = loops.length;
    this.loops.push(0);
    return () => {
      const count = this.loops[id]++;
      if (count > Loopless.loopLimit) {
        throw new Error('Infinite Loop Broken');
      }
    };
  }
  
  // Loops through each RegExp on our static property, then replaces loops as long as a match is found
  loopify(string) {
    let count = Loopless.regExps.length;
    while(--count > -1) {
      const [regExp, template] = Loopless.regExps[count];
      while(regExp.test(string)) {
        // random number in the variable name so that multiple loop counters can run simultaneously
        const randomNumber = Math.random().toString().split('.')[1];
        const replacer = template.join(randomNumber);
        string = string.replace(regExp, replacer);
      }
    }
    return string;
  }
}

// Instanciate
const loopless = new Loopless();

// This is an example of a test input by the end-user
const testInputString = `for (let index = 0, incrLoop = loopless.next(); index < 3000; index++) {
  incrLoop();
  console.log('Loop #' + index + ' complete');
}`;

// This will be the modified input
const modifiedString = loopless.loopify(testInputString);

// Pass the modified input to the test runner rather than logging it.
console.log(modifiedString);

Basically it injects an iteration counter into each loop and throws an error at some predetermined limit. Enable it by default and give users who want more precise results a button to disable it.

I haven't looked through the repository to see how feasible implementation is. Just happened to see the issue and thought I'd contribute to your brainstorming :)

@rd13
Copy link
Owner

rd13 commented Sep 26, 2023

@ChuckTerry Thank you for thinking about this and your contribution. I was thinking of something similar but to address the while loop in @joooKiwi 's example we could do similar to your code by extending with a conditional..

class Loopless {
  constructor() {
    this.loops = []
    this.loopLimit = 1000
  }

  static loopRegExp = [
    [
    /(?<keyword>while)\s+\((?<condition>.+)\)\s*/,
      (condition, id) => `
        while (${condition} && safeWhile.next(${id}))
      `
    ]
  ]

  // Method for adding a new protection conditional to while loops in string
  protect (string) {
    for (const [regExp, tpl] of Loopless.loopRegExp) {
      const [,,condition] = string.match(regExp)
      const id = this.loops.length
      this.loops.push(0)
      string = string.replace(regExp, tpl(condition, id))
    }
    return string
  }

  // Run for each loop, if loopLimit reached then error
  next (id) {
    if (this.loops[id]++ >= this.loopLimit) {
      throw new Error('prevented infinite loop')
      return false
    }
    return true
  }
}

const safeWhile = new Loopless()

const modifiedString = safeWhile.protect('let a = 1; while (a++ < 10000) {}')

// Example of protected while loop which would be run in a test
let a = 1; const someExistingCondition = () => a++ < 10000
while (someExistingCondition() && safeWhile.next(0)) {
  // Will throw an error after loop limit is reached
}

@ChuckTerry
Copy link

@rd13 Oh, that's even better! I hadn't considered a passive injection method into the loop initialization for solving this type of problem.

@joooKiwi
Copy link
Author

I agree that having an option to enable it or disable it could be great since sometimes, a predetermined amount of iteration is not enough for some tests.

So far, from my testing with loops,
utilizing the for (value of iterable) is almost always safe, but it depends on the iterator.
But the ones that are most at risk are the while(condition) and some modified for(i;condition;i++).

The rest of the loops are not a whole lot at risk if we follow a basic structure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants