From 208ea017fabdef911dfcf67fa3c155bf4e6d0499 Mon Sep 17 00:00:00 2001 From: Remy Sharp Date: Sun, 21 Jul 2013 00:14:52 +0100 Subject: [PATCH] Rewrote more of loop protection to carefully walk new lines This ensures we catch multi-line loop conditions, and fixes #725. --- public/js/runner/loop-protect.js | 90 ++++++++++++++------------------ test/loop_detection_test.js | 16 +++++- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/public/js/runner/loop-protect.js b/public/js/runner/loop-protect.js index e28acc5f..2a640965 100644 --- a/public/js/runner/loop-protect.js +++ b/public/js/runner/loop-protect.js @@ -29,10 +29,11 @@ var loopProtect = (function () { return ';' + method + '({ line: ' + lineNum + ', reset: true });\n' + line; }; - lines.forEach(function (line, i) { + lines.forEach(function (line, lineNum) { var next = line, index = 0, - lineNum = i - offset + 1, // +1 since we're humans and don't read lines numbers from zero + originalLineNum = lineNum, + printLineNumber = lineNum - offset + 1, // +1 since we're humans and don't read lines numbers from zero character = '', cont = true, oneliner = false, @@ -40,7 +41,7 @@ var loopProtect = (function () { match = (line.match(re) || [null,''])[1], openBrackets = 0; - if (ignore[i]) return; + if (ignore[lineNum]) return; if (match && line.indexOf('jsbin') === -1) { @@ -73,6 +74,7 @@ var loopProtect = (function () { while (index < line.length) { character = line.substr(index, 1); + // console.log(character, index); if (character === '(') { openBrackets++; @@ -81,67 +83,51 @@ var loopProtect = (function () { if (character === ')') { openBrackets--; - if (openBrackets === 0) { + if (openBrackets === 0 && terminator === false) { terminator = index; } } - if (terminator !== false && character === ';') { - // this is the end of a oneliner - oneliner = true; + if (openBrackets === 0 && (character === ';' || character === '{')) { - // insert the loop protection extra new lines ensure we clear any comments on the original line - line = line.substring(0, terminator + 1) + '{\nif (' + method + '({ line: ' + lineNum + ' })) break;\n' + line.substring(terminator + 1) + '\n}\n'; - recompiled.push(insertReset(lineNum, line)); - return; - } + // if we're a non-curlies loop, then convert to curlies to get our code inserted + if (character === ';') { + if (lineNum !== originalLineNum) { + // affect the compiled line + recompiled[originalLineNum] = recompiled[originalLineNum].substring(0, terminator + 1) + '{\nif (' + method + '({ line: ' + printLineNumber + ' })) break;\n'; + line += '\n}\n'; + } else { + // simpler + line = line.substring(0, terminator + 1) + '{\nif (' + method + '({ line: ' + printLineNumber + ' })) break;\n' + line.substring(terminator + 1) + '\n}\n'; + } - if (openBrackets === 0 && character === '{') { - // we've found the start of the loop, so insert the loop protection - line = line.substring(0, index + 1) + ';\nif (' + method + '({ line: ' + lineNum + ' })) break;'; - recompiled.push(insertReset(lineNum, line)); + } else if (character === '{') { + line = line.substring(0, index + 1) + ';\nif (' + method + '({ line: ' + printLineNumber + ' })) break;'; + } + + // work out where to put the reset + if (lineNum === originalLineNum) { + line = insertReset(printLineNumber, line); + } else { + // insert the reset above the originalLineNum + recompiled[originalLineNum] = insertReset(printLineNumber, recompiled[originalLineNum]); + } + + recompiled.push(line); return; } index++; - } - // if we didn't find the start of the loop program, - // then move on to the next line to work out whether - // this is a one liner or if there's a new line to - // get to the the curly. - next = lines[i+1]; - - // reset the index to the start of the line and work forwards - index = 0; - do { - character = next.substr(index, 1); - - if (character === '{' || character === ';') { - - // we found a curly, so we need to insert: `if (...)\n { dostuff();\n}` - if (character === '{') { - // we've found the start of the loop, so insert the loop protection - next = next.substring(0, index + 1) + ';\nif (' + method + '({ line: ' + lineNum + ' })) break;'; - } - - // this is the end of a mutliline one-liner: `if (...)\n dostuff();` - if (character === ';') { - // insert the loop protection extra new lines ensure we clear any comments on the original line - next = '{\nif (' + method + '({ line: ' + lineNum + ' })) break;\n' + next + '\n}\n'; - } - - recompiled.push(insertReset(lineNum, line)); - recompiled.push(next); - ignore[i + 1] = true; - return; + if (index === line.length && lineNum < (lines.length-1)) { + // move to the next line + recompiled.push(line); + lineNum++; + line = lines[lineNum]; + ignore[lineNum] = true; + index = 0; } - - } while (++index < next.length); - - - // just in case...but really we shouldn't get here. - recompiled.push(line); + } } else { // else we're a regular line, and we shouldn't be touched recompiled.push(line); diff --git a/test/loop_detection_test.js b/test/loop_detection_test.js index fc5ae89a..5b5cb979 100644 --- a/test/loop_detection_test.js +++ b/test/loop_detection_test.js @@ -14,7 +14,7 @@ var code = { onelinefor: 'var i = 0, j = 0;\nfor (; i < 10; i++) j = i * 10;\nreturn i;', simplewhile: 'var i = 0; while (i < 100) {\ni += 10;\n}\nreturn i;', onelinewhile: 'var i = 0; while (i < 100) i += 10;\nreturn i;', - onelinewhile2: 'while (1) console.log("Ha.");', + onelinewhile2: 'function noop(){}; while (1) noop("Ha.");', whiletrue: 'var i = 0;\nwhile(true) {\ni++;\n}\nreturn true;', irl1: 'var nums = [0,1];\n var total = 8;\n for(var i = 0; i <= total; i++){\n var newest = nums[i--]\n nums.push(newest);\n }\n return i;', irl2: 'var a = 0;\n for(var j=1;j<=2;j++){\n for(var i=1;i<=60000;i++) {\n a += 1;\n }\n }\n return a;', @@ -25,6 +25,7 @@ var code = { onelinenewliner: "var b=0;\n function f(){b+=1;}\n for(var j=1;j<120000; j++)\n f();\nreturn j;", irl3: "Todos.Todo = DS.Model.extend({\n title: DS.attr('string'),\n isCompleted: DS.attr('boolean')\n });", brackets: 'var NUM=103, i, sqrt;\nfor(i=2; i<=Math.sqrt(NUM); i+=1){\n if(NUM % i === 0){\n console.log(NUM + " can be divided by " + i + ".");\n break;\n }\n}\nreturn i;', + lotolines: 'var LIMIT = 10;\nvar num, lastNum, tmp;\nfor(num = 1, lastNum = 0;\n num < LIMIT;\n lastNum = num, num = tmp){\n\n tmp = num + lastNum;\n}\nreturn lastNum;', }; @@ -71,7 +72,7 @@ describe('loop', function () { var c = code.onelinewhile2; var compiled = loopProtect.rewriteLoops(c); assert(compiled !== c); - console.log('---------' + c + '---------' + compiled); + // console.log('\n---------\n' + c + '\n---------\n' + compiled); var result = run(compiled); assert(result === undefined); }); @@ -133,6 +134,10 @@ describe('loop', function () { it('should find one liners on multiple lines', function () { var c = code.onelinenewliner; var compiled = loopProtect.rewriteLoops(c); + // console.log('\n----------'); + // console.log(c); + // console.log('\n----------'); + // console.log(compiled); assert(compiled !== c); assert(spy(compiled) === 120000); }); @@ -143,4 +148,11 @@ describe('loop', function () { assert(compiled !== c); assert(spy(compiled) === 11); }); + + it('should not corrupt multi-line (on more than one line) loops', function () { + var c = code.lotolines; + var compiled = loopProtect.rewriteLoops(c); + assert(compiled !== c); + assert(spy(compiled) === 8); + }); });