diff --git a/.eslintrc.json b/.eslintrc.json
index fa8f161..2846ddf 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -133,12 +133,7 @@
}
],
"no-path-concat": "error",
- "no-plusplus": [
- "error",
- {
- "allowForLoopAfterthoughts": true
- }
- ],
+ "no-plusplus": "off",
"no-process-env": "error",
"no-process-exit": "error",
"no-proto": "error",
diff --git a/src/index.html b/src/index.html
index ac12762..d250647 100644
--- a/src/index.html
+++ b/src/index.html
@@ -222,7 +222,6 @@
Codemirror - Marijn Haverbeke
Emmet - Sergey Chikuyonok
Esprima - Ariya Hidayat
- Escodegen - Yusuke Suzuki
Inlet - Ian Johnson
Web Maker! - whhat!
@@ -573,7 +572,6 @@
-
diff --git a/src/script.js b/src/script.js
index d6e8304..acf71fb 100644
--- a/src/script.js
+++ b/src/script.js
@@ -610,9 +610,9 @@ onboardDontShowInTabOptionBtn, TextareaAutoComplete, savedItemCountEl, indentati
showErrors('js', [ { lineNumber: e.lineNumber - 1, message: e.description } ]);
} finally {
if (shouldPreventInfiniteLoops !== false) {
- utils.addInfiniteLoopProtection(ast);
+ code = utils.addInfiniteLoopProtection(code);
}
- d.resolve(escodegen.generate(ast));
+ d.resolve(code);
}
} else if (jsMode === JsModes.COFFEESCRIPT) {
var coffeeCode;
@@ -625,13 +625,10 @@ onboardDontShowInTabOptionBtn, TextareaAutoComplete, savedItemCountEl, indentati
} catch (e) {
showErrors('js', [ { lineNumber: e.location.first_line, message: e.message } ]);
} finally {
- ast = esprima.parse(coffeeCode, {
- tolerant: true
- });
if (shouldPreventInfiniteLoops !== false) {
- utils.addInfiniteLoopProtection(ast);
+ code = utils.addInfiniteLoopProtection(code);
}
- d.resolve(escodegen.generate(ast));
+ d.resolve(code);
}
} else if (jsMode === JsModes.ES6) {
if (!window.Babel) {
@@ -650,19 +647,18 @@ onboardDontShowInTabOptionBtn, TextareaAutoComplete, savedItemCountEl, indentati
// No JSX block
// result = escodegen.generate(ast);
if (shouldPreventInfiniteLoops !== false) {
- utils.addInfiniteLoopProtection(ast);
+ code = utils.addInfiniteLoopProtection(code);
}
- d.resolve(Babel.transform(escodegen.generate(ast), { presets: ['latest', 'stage-2', 'react'] }).code);
+ // FIX ME, add jsx for loop protection above
+ d.resolve(Babel.transform(code, { presets: ['latest', 'stage-2', 'react'] }).code);
} catch (e) {
// If we failed, means probably the AST contains JSX which cannot be parsed by escodegen.
code = Babel.transform(code, { presets: ['latest', 'stage-2', 'react'] }).code;
- ast = esprima.parse(code, {
- tolerant: true
- });
+
if (shouldPreventInfiniteLoops !== false) {
- utils.addInfiniteLoopProtection(ast);
+ code = utils.addInfiniteLoopProtection(code);
}
- d.resolve(escodegen.generate(ast));
+ d.resolve(code);
}
}
} else if (jsMode === JsModes.TS) {
@@ -677,17 +673,10 @@ onboardDontShowInTabOptionBtn, TextareaAutoComplete, savedItemCountEl, indentati
/* eslint-disable no-throw-literal */
throw ({ description: code.diagnostics[0].messageText, lineNumber: ts.getLineOfLocalPosition(code.diagnostics[0].file,code.diagnostics[0].start) });
}
- try {
- ast = esprima.parse(code.outputText, {
- tolerant: true,
- jsx: true
- });
- } finally {
- if (shouldPreventInfiniteLoops !== false) {
- utils.addInfiniteLoopProtection(ast);
- }
- d.resolve(escodegen.generate(ast));
+ if (shouldPreventInfiniteLoops !== false) {
+ code = utils.addInfiniteLoopProtection(ast);
}
+ d.resolve(code);
} catch (e) {
showErrors('js', [ { lineNumber: e.lineNumber - 1, message: e.description } ]);
}
diff --git a/src/utils.js b/src/utils.js
index 62c9af7..073417a 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -42,52 +42,55 @@
}
}
- // Generate 2 ASTs for the code to be inserted in loops for infinite run protection.
- // The `myVar` variable names would be changed later for every insertion.
- function getLoopProtectorBlocks() {
- var ast1 = esprima.parse('var myvar = Date.now();');
- var ast2 = esprima.parse('while(a){if (Date.now() - a787897 > 1000) { window.top.previewException(new Error("Infinite loop")); break;}}');
- return {
- before: ast1.body[0],
- inside: ast2.body[0].body.body[0]
- }
- }
-
/**
- * Add timed limit on the loops found in the passed AST body
- * @param {ASTBody} Body of an AST generated by esprima or any ES compliant AST
+ * Adds timed limit on the loops found in the passed code.
+ * Contributed by Ariya Hidayat!
+ * @param code {string} Code to be protected from infinite loops.
*/
- function addInfiniteLoopProtection(astBody) {
- if (!astBody) { return; }
- if (!Array.isArray(astBody)) {
- addInfiniteLoopProtection(astBody.body);
- return;
- }
- var el, randomVariableName, insertionBLocks;
+ function addInfiniteLoopProtection(code) {
+ var loopId = 1;
+ var patches = [];
+ var varPrefix = '_wmloopvar';
+ var varStr = 'var %d = Date.now();\n'
+ var checkStr = '\nif (Date.now() - %d > 1000) { window.top.previewException(new Error("Infinite loop")); break;}\n'
- for (var i = astBody.length; i--;) {
- el = astBody[i];
- if (el && el.type === 'ForStatement' || el.type === 'WhileStatement' || el.type === 'DoWhileStatement') {
- randomVariableName = '_' + generateRandomId(3);
- insertionBLocks = getLoopProtectorBlocks();
- insertionBLocks.before.declarations[0].id.name = insertionBLocks.inside.test.left.right.name = randomVariableName;
- // Insert time variable assignment
- astBody.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)) {
- el.body = {
- body: [ el.body ],
- type: 'BlockStatement'
- };
+ esprima.parse(code, { tolerant: true, range: true }, function (node) {
+ switch (node.type) {
+ case 'DoWhileStatement':
+ case 'ForStatement':
+ case 'ForInStatement':
+ case 'ForOfStatement':
+ case 'WhileStatement':
+ var start = 1 + node.body.range[0];
+ var end = node.body.range[1];
+ var prolog = checkStr.replace('%d', varPrefix + loopId);
+ var epilog = '';
+
+ if (node.body.type !== 'BlockStatement') {
+ // `while(1) doThat()` becomes `while(1) {doThat()}`
+ prolog = '{' + prolog;
+ epilog = '}';
+ --start;
}
- // Insert IfStatement
- el.body.body.unshift(insertionBLocks.inside);
+
+ patches.push({ pos: start, str: prolog });
+ patches.push({ pos: end, str: epilog });
+ patches.push({ pos: node.range[0], str: varStr.replace('%d', varPrefix + loopId) });
+ ++loopId;
+ break;
+
+ default:
+ break;
}
- if (el.body) {
- addInfiniteLoopProtection(el.body);
- }
- }
+ });
+
+ /* eslint-disable no-param-reassign */
+ patches.sort(function (a, b) { return b.pos - a.pos }).forEach(function (patch) {
+ code = code.slice(0, patch.pos) + patch.str + code.slice(patch.pos);
+ });
+
+ /* eslint-disable no-param-reassign */
+ return code;
}
function getHumanDate(timestamp) {