Rewrote more of loop protection to carefully walk new lines

This ensures we catch multi-line loop conditions, and fixes #725.
This commit is contained in:
Remy Sharp 2013-07-21 00:14:52 +01:00
parent 36de005a4e
commit 208ea017fa
2 changed files with 52 additions and 54 deletions

View File

@ -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);

View File

@ -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);
});
});