未加星标

Web Maker: Preventing Infinite Loops

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二03 | 时间 2017 | 作者 红领巾 ] 0人收藏点击收藏
The Problem

When you write javascript inWeb Maker, it renders that JavaScript inside the preview window in realtime. What this means is in case you using any loop structure in your code, there will probably be a point when you mid way defining your loop variables, conditions, variant etc. And at that point if Web Maker renders your JavaScript, it would result in an infinite loop. Lets see with an example. Suppose I want to write a for loop to iterate 10 times, I’ll start with

for (var i = 0; i<10; [cursor_here]) { } Note that my cursor would be where it says [cursor_here] and I am still defining my loop and going to write i++ there. But as we render in realtime, this incomplete code would get executed in preview, resulting in an infinite loop.

Therefore we need a way to prevent such intermediate incomplete loops from running infinitely and choking the browser.

Solution

There is a lot of discussion on the Web regarding how difficult it is to detect an infinite loop in runtime, simply because any runtime cannot actually diffrentiate between a normal loop and one that is going to run infinitely. There are some analyzers that try to solve this issue but static analysis can only do so much.

The approach Web Maker takes to solve this is by keeping a check on the time spent inside a loop. To be specific, I check if the loop isn’t taking more than 1 second. Otherwise its marked as an infinite loop. To be able to keep track of this time, we need to have a time check inside the loop and hence this requires changing the actual loop code, also called as Code Instrumentation .

So if we have a code like this:

for ( var i = 0; i < 10;) { }

After our instrumentation, it should change to:

var startTime = Date.now(); for ( var i = 0; i < 10;) { if (Date.now() - startTime > 1000) { break; } }

To analyze a piece of code, we need a JavaScript parser. I used Esprima for that. Lets see how we can use Esprima to instrument our code.

Detecting a loop

Esprima converts a string of JavaScript code into an Abstract Syntax Tree (AST), which is basically a tree like structure representing the code snippet.

function instrumentCode(code) { var ast = esprima.parse(code); }

To get a sense of what an AST looks like, lets run esprima on the following code:

var foo = 3; for (var i = 0; i < 10; i++) {}

This is what we’ll get:


Web Maker: Preventing Infinite Loops

Notice how the return AST is a repeating nested array structure ( body inside body ), with each identifiable unit as an instance of some class. Eg. VariableDeclaration , ForStatement etc. This is something we can easily traverse using recursion, like so:

function processAst(ast) { var currentElement; // If this ins't actual body, recurse with the body if (!Array.isArray(astBody)) { processAst(astBody.body); return; } // Traverse the body for (var i = ast.length; i--;) { var currentElement = ast[i]; } } function instrumentCode(code) { var ast = esprima.parse(code); processAst(ast); }

With the above code in place, we’ll keep getting different syntax structures in currentElement . Next, we check them and inject the actual loop protection code.

Injecting the loop protection checks

We are concerned when currentElement is a for , while or do-while loop. This can be checked by simply testing currentElement.type . Lets add that.

function processAst(ast) { var currentElement; // If this ins't actual body, recurse with the body if (!Array.isArray(astBody)) { processAst(astBody.body); return; } // Traverse the body for (var i = ast.length; i--;) { var currentElement = ast[i]; if (currentElement && currentElement.type === 'ForStatement' || currentElement.type === 'WhileStatement' || currentElement.type === 'DoWhileStatement') { // We got a loop! } // Recurse on inner body if (currentElement.body) { processAst(currentElement.body); } } }

Next we need two of our statements to be injected in AST syntax. For that we can again use esprima. Once we covert the statements into AST objects, its just a matter of adding them to the right body in our AST.

if (currentElement && currentElement.type === 'ForStatement' || currentElement.type === 'WhileStatement' || currentElement.type === 'DoWhileStatement') { var ast1 = esprima.parse('var myvar = Date.now();'); var ast2 = esprima.parse('while(a){if (Date.now() - myvar > 1000) { break;}}'); var insertionBlocks = { before: ast1.body[0], inside: ast2.body[0].body.body[0] }; }

Notice, that we have assigned the current time in myVar . Now there could be multiple loops (even nested) in a single code snippet and they all need to be handled with their unique variables. So we replace the variable names in our insertion blocks with a 3 digit random string:

if (currentElement && currentElement.type === 'ForStatement' || currentElement.type === 'WhileStatement' || currentElement.type === 'DoWhileStatement') { var ast1 = esprima.parse('var myvar = Date.now();'); var ast2 = esprima.parse('while(a){if (Date.now() - myvar > 1000) { break;}}'); var insertionBlocks = { before: ast1.body[0], inside: ast2.body[0].body.body[0] }; randomVariableName = '_' + generateRandomId(3); insertionBLocks.before.declarations[0].id.name = insertionBLocks.inside.test.left.right.name = randomVariableName; }

All that is left now is to insert the insertion blocks at right places:

if (currentElement && currentElement.type === 'ForStatement' || currentElement.type === 'WhileStatement' || currentElement.type === 'DoWhileStatement') { var ast1 = esprima.parse('var myvar = Date.now();'); var ast2 = esprima.parse('while(a){if (Date.now() - myvar > 1000) { break;}}'); var insertionBlocks = { before: ast1.body[0], inside: ast2.body[0].body.body[0] }; randomVariableName = '_' + generateRandomId(3); insertionBLocks.before.declarations[0].id.name = insertionBLocks.inside.test.left.right.name = randomVariableName; // Insert time variable assignment as first child in the body array. ast.splice(i, 0, insertionBLocks.before); // If the loop's body is a single statement, then convert it into a block statement // so that we can insert our conditional break inside it. if (!Array.isArray(el.body)) { currentElement.body = { body: [ el.body ], type: 'BlockStatement' }; } // Insert the `If` Statement check currentElement.body.body.unshift(insertionBLocks.inside); }

And we are done.

You can also see the actual Web Maker source code for this logic .

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

主题: JavaScriptJava
分页:12
转载请注明
本文标题:Web Maker: Preventing Infinite Loops
本站链接:http://www.codesec.net/view/532106.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(43)