Skip to content

Commit

Permalink
fix: update readme, plus more coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
remy committed Jan 16, 2018
1 parent 2fba7ea commit 77c5c0e
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 31 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Note that this does *not* solve the [halting problem](http://en.wikipedia.org/wi

With loop protection in place, it means that a user can enter the code as follows on JS Bin, and the final `console.log` will still work.

The code is transformed from this:

```js
while (true) {
doSomething();
Expand All @@ -20,6 +22,21 @@ while (true) {
console.log('All finished');
```

…to this:

```js
let i = 0;
var _LP = Date.now();
while (true) {
if (Date.now() - _LP > 100)
break;

doSomething();
}

console.log('All finished');
```

## Usage

The loop protection is a babel transform, so can be used on the server or in the client.
Expand Down
26 changes: 18 additions & 8 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const generateBefore = (t, id) =>
),
]);

const generateInside = ({ t, id, line, timeout, extra } = {}) => {
const generateInside = ({ t, id, line, ch, timeout, extra } = {}) => {
return t.ifStatement(
t.binaryExpression(
'>',
Expand All @@ -25,11 +25,11 @@ const generateInside = ({ t, id, line, timeout, extra } = {}) => {
),
extra
? t.blockStatement([
t.expressionStatement(
t.callExpression(extra, [t.numericLiteral(line)])
),
t.breakStatement(),
])
t.expressionStatement(
t.callExpression(extra, [t.numericLiteral(line), t.numericLiteral(ch)])
),
t.breakStatement(),
])
: t.breakStatement()
);
};
Expand All @@ -41,6 +41,7 @@ const protect = (t, timeout, extra) => path => {
t,
id,
line: path.node.loc.start.line,
ch: path.node.loc.start.column,
timeout,
extra,
});
Expand All @@ -60,10 +61,19 @@ module.exports = (timeout = 100, extra = null) => {
extra = `() => console.error("${string.replace(/"/g, '\\"')}")`;
}
return ({ types: t, transform }) => {
const callback = extra
? transform(extra).ast.program.body[0].expression
const node = extra
? transform(extra).ast.program.body[0]
: null;

// console.log(node && node.type)

let callback = null;
if (t.isExpressionStatement(node)) {
callback = node.expression;
} else if (t.isFunctionDeclaration(node)) {
callback = t.functionExpression(null, node.params, node.body);
}

return {
visitor: {
WhileStatement: protect(t, timeout, callback),
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "loop-protect",
"description": "Prevent infinite loops in dynamically eval'd JavaScript.",
"main": "lib/",
"version": "2.1.0",
"version": "2.1.2",
"homepage": "https://github.com/jsbin/loop-protect",
"repository": {
"type": "git",
Expand Down
57 changes: 57 additions & 0 deletions test/callback.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-env node, jest */
const Babel = require('babel-standalone');

const code = `let i = 0; while (true) { i++; }; done(i)`;

let done = jest.fn();

beforeEach(() => {
done = jest.fn();
});

const transform = id => code => Babel.transform(new Function(code).toString(), {
plugins: [id],
}).code; // eslint-disable-line no-new-func

const run = code => {
// console.log(code);
eval(`(${code})()`); // eslint-disable-line no-eval
};

test('no callback', () => {
const id = 'lp1';
Babel.registerPlugin(id, require('../lib')(100));
const after = transform(id)(code);
run(after);
expect(done).toBeCalledWith(expect.any(Number));
});

test('anonymous callback', () => {
const id = 'lp2';
Babel.registerPlugin(id, require('../lib')(100, line => done(`line: ${line}`)));
const after = transform(id)(code);
run(after);
expect(done).toHaveBeenCalledWith('line: 2');
});

test('arrow function callback', () => {
const id = 'lp3';
const callback = line => done(`lp3: ${line}`);

Babel.registerPlugin(id, require('../lib')(100, callback));
const after = transform(id)(code);
run(after);
expect(done).toHaveBeenCalledWith(`${id}: 2`);
});

test('named function callback', () => {
const id = 'lp4';
function callback(line, ch) {
done(`lp4: ${line}`);
}

Babel.registerPlugin(id, require('../lib')(100, callback));
const after = transform(id)(code);
run(after);
expect(done).toHaveBeenCalledWith(`${id}: 2`);
});
42 changes: 21 additions & 21 deletions test/loop-protect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ const loopProtect = code =>
}).code; // eslint-disable-line no-new-func
const run = code => eval(`(${code})()`); // eslint-disable-line no-eval

describe('loop', function() {
beforeEach(function() {
describe('loop', function () {
beforeEach(function () {
spy = sinon.spy(run);
});

Expand Down Expand Up @@ -139,15 +139,15 @@ describe('loop', function() {
assert(run(compiled) === true);
});

it('should ignore comments', function() {
it('should ignore comments', function () {
var c = code.ignorecomments;
var compiled = loopProtect(c);
// console.log('\n---------\n' + c + '\n---------\n' + compiled);
var result = run(compiled);
assert(result === true);
});

it('should rewrite for loops', function() {
it('should rewrite for loops', function () {
var c = code.simplefor;
var compiled = loopProtect(c);
assert(compiled !== c);
Expand All @@ -161,15 +161,15 @@ describe('loop', function() {
assert(result === 10);
});

it('should handle one liner for with an inline function', function() {
it('should handle one liner for with an inline function', function () {
var c = code.onelineforinline;
var compiled = loopProtect(c);
assert(compiled !== c);
var result = run(compiled);
assert(result === true, 'value is ' + result);
});

it('should rewrite one line for loops', function() {
it('should rewrite one line for loops', function () {
var c = code.onelinefor;
var compiled = loopProtect(c);
assert(compiled !== c);
Expand All @@ -186,7 +186,7 @@ describe('loop', function() {
assert(result === 0);
});

it('should rewrite one line while loops', function() {
it('should rewrite one line while loops', function () {
var c = code.onelinewhile2;
var compiled = loopProtect(c);
assert(compiled !== c);
Expand All @@ -195,38 +195,38 @@ describe('loop', function() {
assert(result === undefined);
});

it('should protect infinite while', function() {
it('should protect infinite while', function () {
var c = code.whiletrue;
var compiled = loopProtect(c);

assert(compiled !== c);
assert(spy(compiled) === true);
});

it('should protect infinite for', function() {
it('should protect infinite for', function () {
var c = code.irl1;
var compiled = loopProtect(c);
assert(compiled !== c);
// assert(spy(compiled) === 0);
});

it('should allow nested loops to run', function() {
it('should allow nested loops to run', function () {
var c = code.irl2;
var compiled = loopProtect(c);
var r = run(compiled);
expect(compiled).not.toBe(c);
expect(r).toBe(60000);
});

it('should rewrite loops when curlies are on the next line', function() {
it('should rewrite loops when curlies are on the next line', function () {
var c = code.dirtybraces;
var compiled = loopProtect(c);
var r = spy(compiled);
assert(compiled !== c);
assert(r === 10000, r);
});

it('should find one liners on multiple lines', function() {
it('should find one liners on multiple lines', function () {
var c = code.onelinenewliner;
var compiled = loopProtect(c);
var r = spy(compiled);
Expand All @@ -238,21 +238,21 @@ describe('loop', function() {
assert(r === 10000, 'return value does not match 10000: ' + r);
});

it('should handle brackets inside of loop conditionals', function() {
it('should handle brackets inside of loop conditionals', function () {
var c = code.brackets;
var compiled = loopProtect(c);
assert(compiled !== c);
assert(spy(compiled) === 11);
});

it('should not corrupt multi-line (on more than one line) loops', function() {
it('should not corrupt multi-line (on more than one line) loops', function () {
var c = code.lotolines;
var compiled = loopProtect(c);
assert(compiled !== c);
assert(spy(compiled) === 8);
});

it('should protect do loops', function() {
it('should protect do loops', function () {
var c = code.dowhile;
var compiled = loopProtect(c);
assert(compiled !== c);
Expand All @@ -274,12 +274,12 @@ describe('loop', function() {
});
});

describe('labels', function() {
beforeEach(function() {
describe('labels', function () {
beforeEach(function () {
spy = sinon.spy(run);
});

it('should handle continue statements and gotos', function() {
it('should handle continue statements and gotos', function () {
var c = code.continues;
var compiled = loopProtect(c);
assert(spy(compiled) === 10);
Expand All @@ -289,13 +289,13 @@ describe('labels', function() {
assert(spy(compiled) === 2);
});

it('should handle labels with comments', function() {
it('should handle labels with comments', function () {
var c = code.labelWithComment;
var compiled = loopProtect(c);
assert(spy(compiled) === 10);
});

it('should handle things that *look* like labels', function() {
it('should handle things that *look* like labels', function () {
var c = code.notlabels2;
var compiled = loopProtect(c);
assert(compiled !== c);
Expand All @@ -315,7 +315,7 @@ describe('labels', function() {
// assert(result === 10, 'actual ' + result);
});

it('should handle if statement without {}', function() {
it('should handle if statement without {}', function () {
var c = code.loopbehindif;
var compiled = loopProtect(c);
assert(compiled !== c);
Expand Down

0 comments on commit 77c5c0e

Please sign in to comment.