When allowing a user to input code that could potentially cause an infinite loop, it is nice to test the code first, right? Even, if the code doesn’t run infinitely, it would be nice to know if it takes longer than you would want. One way to nicely test for infinite loops or long running code is by using web workers. The following is a function which uses eval() in a web worker to execute the code and adds a timeout so that if the code takes too long to run, the web worker will be terminated:

Now let’s say that the user inputs the following loop in your textbox which has an ID of txtCode:


for (var i = 0; i != 10; i++) {
  if (i % 2 == 0) {
    i *= 2;
  }
}

Unfortunately, the above loop will run infinitely because i will progress as shown below:


0
1
4
5
12
13
14
29
30
61
62
125
126
253
254
509
510
1021
...

If you don’t believe me you can try the following code in the console 😉 :


// Let's stop the loop once we get to or above 1000
for (var i = 0; console.log(i + ' != 10'), i != 10 && i < 1000; i++) {
  if (i % 2 == 0) {
    i *= 2;
  }
}
[/code]

The good thing about web workers is that any code that runs in them will not interfere with your JS in the main process but the downside is that you cant innately determine if a web worker is still running after a specified amount of time (well not as easily as you might hope).  Fortunately, with the limitEval() function we find a way around this issue by sending and receiving messages.  Feel free to check out how I accomplish this by analyzing the code.

Now let's setup a way to determine if the user's input, found in txtCode, takes more than 3 seconds to run:
[code language=javascript]
var code = document.getElementById('txtCode').value;
limitEval(code, function(success, returnValue) {
  if (success) {
    // do whatever you want with the return value (returnValue)
  }
  else {
    alert('The code takes too long to run.  Is there is an infinite loop?');
  }
}, 3000);

So, if the code takes more than 3 seconds to run an alert will be shown after 3 seconds and the eval will be terminated.

As you can see limitEval takes two required parameters and one optional one:

  1. code {string}:
    The JS code to be eval'd.
  2. fnOnStop {Function}:
    The function which will be called either after the eval completes or after the timeout occurs. The following are the arguments that will be passed:

    1. success {boolean}:
      true will be passed if the eval executed successfully before the timeout. Otherwise false will be passed.
    2. opt_returnValue {*}:
      If the eval executes successfully this will be the return value. If the timeout occurs this will not be passed and arguments.length will be 1.
  3. opt_timeoutInMS {number}:
    Optional. Defaults to 1000. The number of milliseconds before timing out.

It is important to note that this solution, although very useful, only works on newer browsers. According to MDN, it will work in the following browsers and higher:

  • Chrome 23
  • Firefox (Gecko) 21
  • Internet Explorer 11
  • Safari (WebKit) 7

Hopefully you find the limitEval() function useful. Have fun! 😎


4 Comments

shivakumar · March 25, 2016 at 3:29 AM

Didn’t understood the logic of the limitEval function as it always returns to be true in the second postMessage call.

‘onmessage=function(a){a=a.data;postMessage({i:a.i+1});postMessage({r:eval.call(this,a.c),i:a.i})};’]

the reason being, for first postMessage always its false since data.i never equal to i and for second postMessage it’s always equal and returns onDone(true, data.r);

JDuwe · October 18, 2018 at 3:11 PM

Thanks for that, helped me a lot.

Oleksandr · October 19, 2018 at 8:46 AM

Nice article. With node.js you might check infinite code blocks with parent-child process communication. Idea is implemented in the harakiri: https://github.com/knyga/harakiri

kyle · March 4, 2019 at 3:21 PM

Love the concept! Awesome article btw!

I wanted to play with this. Here is some code I ran in a chrome snippet.

function limitEval(code, fnOnStop, opt_timeoutInMS) {
var id = Math.random() + 1,
blob = new Blob(
[‘onmessage=function(a){a=a.data;postMessage({i:a.i+1});postMessage({r:eval.call(this,a.c),i:a.i})};’],
{ type:’text/javascript’ }
);
var myWorker = new Worker(URL.createObjectURL(blob));

function onDone() {
URL.revokeObjectURL(blob);
fnOnStop.apply(this, arguments);
}

myWorker.onmessage = function (data) {
data = data.data;
if (data) {
if (data.i === id) {
id = 0;
onDone(true, data.r);
}
else if (data.i === id + 1) {
setTimeout(function() {
if (id) {
myWorker.terminate();
onDone(false);
}
}, opt_timeoutInMS || 1000);
}
}
};

myWorker.postMessage({ c: code, i: id });
}

limitEval(function() {
for (var i = 0; i != 10; i++) {
for (var i = 0; i != 10; i++) {
if (i % 2 == 0) {
i *= 2;
}
}
}
},
() => console.log(“we done homie”),
1)

But I got the following error message:

NoInfiniteLoops:1 DOMException: Failed to execute ‘postMessage’ on ‘Worker’: function() {
for (var i = 0; i != 10; i++) {
for (var i = 0; i != 10; i++) {
if (i %……
} could not be cloned.
at limitEval (snippet:///NoInfiniteLoops:32:12)
at snippet:///NoInfiniteLoops:36:1

Leave a Reply

Your email address will not be published. Required fields are marked *