How to prevent loops in JavaScript that crash the browser or Apps?
Asked Answered
A

6

6

I am creating a live editor in Windows 8.1 App using JavaScript. Almost done with that, but the problem is whenever I run such bad loops or functions then it automatically hangs or exits.

I test it with a loop such as:( It just a example-user may write its loop in its own way..)

for(i=0;i<=50000;i++)
{
   for(j=0;j<5000;j++){
     $('body').append('hey I am a bug<br>');
   }
}

I know that this is a worst condition for any app or browser to handle that kind of loop. So here I want that if user uses such a loop then how I handle it, to produce their output?

Or if its not possible to protect my app for that kind of loop, if it is dangerous to my app so I alert the user that:

Running this snippet may crash the app!

I have an idea to check the code by using regular expressions if code have something like for(i=0;i<=5000;i++) then the above alert will show, how to do a Regex for that?

Also able to include C# as back-end .

Annoying answered 18/2, 2014 at 11:47 Comment(10)
this loop runs for 50000*50000 times that too in blocking synchronous manner ,you can check #4289259Jamison
Ya,that's why i write such code to check for errors on my live editor, And want to detect that type of loop ?? is their any way?Annoying
I just update the question.. @ArunKilluAnnoying
If your users can create their own JavaScript, then you may need to rely on the browser's slowness detection for this - regular expressions doesn't "feel right" for this, as there will always be cases that it fails to catch. Firefox has a slowness detector and offers users an opportunity to abort the current script. So, providing you have saved your users work, maybe that is enough? I am not sure what slowness detection is offered by the other browsers though.Sorcha
Somewhat related, though not a solution: https://mcmap.net/q/1769897/-how-to-prevent-possible-javascript-memory-hogging-from-crashing-a-browser/472495Sorcha
I have C# as a beck-end so it is possible to check if my app is responsive or not ? if not then i restart it by restoring the user's data..@SorchaAnnoying
I'm not familiar with MS environments, but what are you using to run JavaScript? Is this an IE control that plugs into your app?Sorcha
Yes its Under IE @SorchaAnnoying
Can you use worker threads?Tejeda
But they are not able to interact with DOM @AkashKavaAnnoying
H
2

I've got 2 solutions:

1.

My first solution would be defining a variable startSeconds=new Date().getSeconds();.

Then, using regex, I'm inserting this piece of code inside the nested loop.

;if(startSecond < new Date().getSeconds())break;

So, what it does is each time the loop runs, it does two things:

Checks if startSecond is less than current seconds new Date().getSeconds();.

For example, startSecond may be 22. new Date().getSeconds() may return 24.Now, the if condition succeeds so it breaks the loop.

Mostly, a non dangerous loop should run for about 2 to 3 seconds

Small loops like for(var i=0;i<30;i++){} will run fully, but big loops will run for 3 to 4 seconds, which is perfectly ok.

My solution uses your own example of 50000*5000, but it doesn't crash!

Live demo:http://jsfiddle.net/nHqUj/4

2.

My second solution would be defining two variables start, max.

Max should be the maximum number of loops that you are willing to run. Example 1000.

Then, using regex, I'm inserting this piece of code inside the nested loop.

;start+=1;if(start>max)break;

So, what it does is each time the loop runs, it does two things:

  1. Increments the value of start by 1.

  2. Checks whether start is greater than the max. If yes, it breaks the loop.

This solution also uses your own example of 50000*5000, but it doesn't crash!

Updated demo:http://jsfiddle.net/nHqUj/3

Regex I'm using:(?:(for|while|do)\s*\([^\{\}]*\))\s*\{([^\{\}]+)\}

Hurwit answered 15/3, 2014 at 7:38 Comment(3)
Awesome approach :) ,but doesn't get Success on , "for(i=0;i<=i;i++){for(j=0;j<=j;j++){$('body').append('hey I am a bug<br>');}}" this Kind of loops as said by @DivakarDassAnnoying
Your solution ,where also affect setInterval(function(){...}) because of your regex string.match(/\{([^\{\}]+)\}/)[1] that detect only inner most {...} of any string rather then only for loop!!Annoying
Also include your ...another previous answer..coz i prefer that one instead of this..start+=1;if(start>max)break; and replace your regex so that it only get inner most for loop.Annoying
T
5

Unfortunately, without doing some deep and complex code analysis of the edited code, you'll not be able to fully prevent errant JavaScript that kills your application. You could use, for example, a library that builds an abstract syntax tree from JavaScript and not allow code execution if certain patterns are found. But, the number of patterns that could cause an infinite loop are large, so it would not be simple to find, and it's likely to not be robust enough.

In the for example, you could modify the code to be like this:

for(i=0;!timeout() && i<=50000;i++)
{
   for(j=0;!timeout() && j<5000;j++){
     $('body').append('hey I am a bug<br>');
   }
}

I've "injected" a call to a function you'd write called timeout. In there, it would need to be able to detect whether the loop should be aborted because the script has been running too long.

But, that could have been written with a do-while, so that type of loop would need to be handled.

The example of using jQuery for example in a tight loop, and modifying the DOM means that solutions that trying to isolate the JavaScript into a Web Worker would be complex, as it's not allowed to manipulate the DOM directly. It can only send/receive "string" messages.

If you had used the XAML/C# WebView to host (and build) the JavaScript editor, you could have considered using an event that is raised called WebView.LongRunningScriptDetected. It is raised when a long running script is detected, providing the host the ability to kill the script before the entire application becomes unresponsive and is killed.

Unfortunately, this same event is not available in the x-ms-webview control which is available in a WinJS project.

Towroy answered 18/2, 2014 at 19:14 Comment(4)
Actully i am working in html5 app and create an iframe in the iframe i put the output of User's what ever he code!,I know there is a way to interact with c# in HTML5 app also.. by using run-time component ,Is the C# able to handle my iframe of long script running? @TowroyAnnoying
As I said, if you created a C# host for your web content, you could prevent the JavaScript from being a "run-away" process. Otherwise, you'd need to consider the other ideas -- they won't be as robust.Towroy
I like to use x-web-view but its not supports full features of HTML5Annoying
The web view, in both cases (XAML and WinJS) is just a full isolated instance of Internet Explorer 10+. In WinJS, the x-ms-webview component is only available in Windows 8.1, so it's IE11.Towroy
S
2

One idea, but not sure what is your editor is capable of..

If some how you can understand that this loop may cause problem(like if a loop is more than 200 times then its a issue) and for a loop like that from user if you can change the code to below to provide the output then it will not hang. But frankly not sure if it will work for you.

var j = 0;
var inter = setInterval( function(){
    if( j<5000  ){
      $('#test').append('hey I am a bug<br>');
      ++j;  
    } else {
        clearInterval(inter);
    }
}, 100 );
Stile answered 18/2, 2014 at 12:18 Comment(5)
You right but i am giving the example of the loop ,user code whatever ,i just want to check if it is dangerous then i follow the other operation to run the user's code !! @A PaulAnnoying
ok. So you want to understand the code is dangerous. I donot think it is possible by some exisiting code or library. You have to write your own engine then to take the decision. This is one link that you can check provides some answer like how to detect browser hang. But look like will not be much help to you. #14193258Stile
@AshishMishra - Sorry, not able to help you. But I will be really eager to see how you solve it. Please update the post with your answer/ how you solve it if finally you dont get any solution from here.Stile
No, Sorry!! :) Definitely..@A Paul I think the only solution is regexp,that i doesn't know how to apply ?Annoying
Hi,@A Paul here is the solution..That i Accept as answer,Is it good or there is any possibility of bugs in it..??Annoying
G
2

Perhaps inject timers around for loops and check time at the first line. Do this for every loop.

Regex: /for\([^{]*\)[\s]*{/

Example:

/for\([^{]*\)[\s]*{/.test("for(var i=0; i<length; i++){");
> true

Now, if you use replace and wrap the for in a grouping you can get the result you want.

var code = "for(var i=0; i<length; i++){",
    testRegex = /(?:for\([^{]*\)[\s]*{)/g,
    matchReplace = "var timeStarted = new Date().getTime();" +
                   "$1" +
                   "if (new Date().getTime() - timeStarted > maxPossibleTime) {" +
                       "return; // do something here" +
                   "}";

code.replace(textRegex, matchReplace);
Gooey answered 11/3, 2014 at 21:13 Comment(3)
What is the maxPossibleTime ? And how i define it,in seconds or min?Annoying
It is some variable you should define. You should define it in milliseconds.Gooey
@AshishMishra well, you need to debug it. I don't know what your code looks like. It doesn't account for all cases. I'm not doing all your work for you. I'm giving you an idea.Gooey
V
2

You cannot find what user is trying to do with a simple regex. Lets say, the user writes his code like...

for(i=0;i<=5;i++)
{
   for(j=0;j<=5;j++){
     if(j>=3){
         i = i * 5000;
         j = j * 5000;
     }
     $('body').append('hey I am a bug<br>');
   }
}

Then with a simple regex you cannot avoid this. Because the value of i is increased after a time period. So the best way to solve the problem is to have a benchmark. Say, your app hangs after continuos processing of 3 minutes(Assume, until your app hits 3 minutes of processing time, its running fine). Then, whatever the code the user tries to run, you just start a timer before the process and if the process takes more than 2.5 minutes, then you just kill that process in your app and raise a popup to the user saying 'Running this snippet may crash the app!'... By doing this way you dont even need a regex or to verify users code if it is bad...

Try this... Might help... Cheers!!!

Vitrification answered 14/3, 2014 at 15:53 Comment(1)
Great, Tip ! but how to kill the particular process or script that is running ? it is more complex then identify the loop ... @DivakarDassAnnoying
H
2

I've got 2 solutions:

1.

My first solution would be defining a variable startSeconds=new Date().getSeconds();.

Then, using regex, I'm inserting this piece of code inside the nested loop.

;if(startSecond < new Date().getSeconds())break;

So, what it does is each time the loop runs, it does two things:

Checks if startSecond is less than current seconds new Date().getSeconds();.

For example, startSecond may be 22. new Date().getSeconds() may return 24.Now, the if condition succeeds so it breaks the loop.

Mostly, a non dangerous loop should run for about 2 to 3 seconds

Small loops like for(var i=0;i<30;i++){} will run fully, but big loops will run for 3 to 4 seconds, which is perfectly ok.

My solution uses your own example of 50000*5000, but it doesn't crash!

Live demo:http://jsfiddle.net/nHqUj/4

2.

My second solution would be defining two variables start, max.

Max should be the maximum number of loops that you are willing to run. Example 1000.

Then, using regex, I'm inserting this piece of code inside the nested loop.

;start+=1;if(start>max)break;

So, what it does is each time the loop runs, it does two things:

  1. Increments the value of start by 1.

  2. Checks whether start is greater than the max. If yes, it breaks the loop.

This solution also uses your own example of 50000*5000, but it doesn't crash!

Updated demo:http://jsfiddle.net/nHqUj/3

Regex I'm using:(?:(for|while|do)\s*\([^\{\}]*\))\s*\{([^\{\}]+)\}

Hurwit answered 15/3, 2014 at 7:38 Comment(3)
Awesome approach :) ,but doesn't get Success on , "for(i=0;i<=i;i++){for(j=0;j<=j;j++){$('body').append('hey I am a bug<br>');}}" this Kind of loops as said by @DivakarDassAnnoying
Your solution ,where also affect setInterval(function(){...}) because of your regex string.match(/\{([^\{\}]+)\}/)[1] that detect only inner most {...} of any string rather then only for loop!!Annoying
Also include your ...another previous answer..coz i prefer that one instead of this..start+=1;if(start>max)break; and replace your regex so that it only get inner most for loop.Annoying
H
1

Let's assume you are doing this in the window context and not in a worker. Put a function called rocketChair in every single inner loop. This function is simple. It increments a global counter and checks the value against a global ceiling. When the ceiling is reached rocketChair summarily throws "eject from perilous code". At this time you can also save to a global state variable any state you wish to preserve.

Wrap your entire app in a single try catch block and when rocket chair ejects you can save the day like the hero you are.

Haeres answered 8/3, 2014 at 22:4 Comment(4)
I ,understand what you tell, but how I detect if it is a for loop, and start my function ` rocketChair `! and I don't want to use any kind worker here! @crisAnnoying
Use regex to find for loops, while loops. Then hidden from the user, insert your function in the first line of every loop ( not just innermost since what if it only executes conditionally ? ). If the user did not use curly braces to specific the block, you will need to add them since your code will now be at least two statements long: one rocket chair, one original ( possibly empty) statement. Then eval that modified user code inside the try catch.Haeres
What the regEXP for do so, i try for this, but i can't find good regEXP,Is you able to provide that regexp that detect for loops and their value like "i<=50000" so it give me 50000 then i alert user that it is dangerous or insert a function!! @crisAnnoying
Ok, just slow doe and think about what you need. Then, if you can't make a regex yourself, make a new question. Also, note you DO NOT need to identify loop conditions. Counting them, from invocations to rocketChair is more general and sufficient to count the big numbers. Write some code then make a new question is my suggestion, thank you. @AshishMishraHaeres

© 2022 - 2024 — McMap. All rights reserved.