diff --git a/index.js b/index.js index c934992..32c84bc 100644 --- a/index.js +++ b/index.js @@ -4,12 +4,12 @@ module.exports = function (ast, vars) { if (!vars) vars = {}; var FAIL = {}; - var result = (function walk (node, scopeVars) { + var result = (function walk (node, noExecute) { if (node.type === 'Literal') { return node.value; } else if (node.type === 'UnaryExpression'){ - var val = walk(node.argument) + var val = walk(node.argument, noExecute) if (node.operator === '+') return +val if (node.operator === '-') return -val if (node.operator === '~') return ~val @@ -19,7 +19,7 @@ module.exports = function (ast, vars) { else if (node.type === 'ArrayExpression') { var xs = []; for (var i = 0, l = node.elements.length; i < l; i++) { - var x = walk(node.elements[i]); + var x = walk(node.elements[i], noExecute); if (x === FAIL) return FAIL; xs.push(x); } @@ -31,7 +31,7 @@ module.exports = function (ast, vars) { var prop = node.properties[i]; var value = prop.value === null ? prop.value - : walk(prop.value) + : walk(prop.value, noExecute) ; if (value === FAIL) return FAIL; obj[prop.key.value || prop.key.name] = value; @@ -59,9 +59,9 @@ module.exports = function (ast, vars) { return r; } - var l = walk(node.left); + var l = walk(node.left, noExecute); if (l === FAIL) return FAIL; - var r = walk(node.right); + var r = walk(node.right, noExecute); if (r === FAIL) return FAIL; if (op === '==') return l == r; @@ -96,23 +96,29 @@ module.exports = function (ast, vars) { else return FAIL; } else if (node.type === 'CallExpression') { - var callee = walk(node.callee); + var callee = walk(node.callee, noExecute); if (callee === FAIL) return FAIL; if (typeof callee !== 'function') return FAIL; + - var ctx = node.callee.object ? walk(node.callee.object) : FAIL; + var ctx = node.callee.object ? walk(node.callee.object, noExecute) : FAIL; if (ctx === FAIL) ctx = null; var args = []; for (var i = 0, l = node.arguments.length; i < l; i++) { - var x = walk(node.arguments[i]); + var x = walk(node.arguments[i], noExecute); if (x === FAIL) return FAIL; args.push(x); } + + if (noExecute) { + return undefined; + } + return callee.apply(ctx, args); } else if (node.type === 'MemberExpression') { - var obj = walk(node.object); + var obj = walk(node.object, noExecute); // do not allow access to methods on Function if((obj === FAIL) || (typeof obj == 'function')){ return FAIL; @@ -121,26 +127,25 @@ module.exports = function (ast, vars) { if (isUnsafeProperty(node.property.name)) return FAIL; return obj[node.property.name]; } - var prop = walk(node.property); + var prop = walk(node.property, noExecute); if (prop === null || prop === FAIL) return FAIL; if (isUnsafeProperty(prop)) return FAIL; return obj[prop]; } else if (node.type === 'ConditionalExpression') { - var val = walk(node.test) + var val = walk(node.test, noExecute) if (val === FAIL) return FAIL; - return val ? walk(node.consequent) : walk(node.alternate) + return val ? walk(node.consequent) : walk(node.alternate, noExecute) } else if (node.type === 'ExpressionStatement') { - var val = walk(node.expression) + var val = walk(node.expression, noExecute) if (val === FAIL) return FAIL; return val; } else if (node.type === 'ReturnStatement') { - return walk(node.argument) + return walk(node.argument, noExecute) } else if (node.type === 'FunctionExpression') { - var bodies = node.body.body; // Create a "scope" for our arguments @@ -157,7 +162,7 @@ module.exports = function (ast, vars) { else return FAIL; } for(var i in bodies){ - if(walk(bodies[i]) === FAIL){ + if(walk(bodies[i], true) === FAIL){ return FAIL; } } @@ -173,14 +178,14 @@ module.exports = function (ast, vars) { else if (node.type === 'TemplateLiteral') { var str = ''; for (var i = 0; i < node.expressions.length; i++) { - str += walk(node.quasis[i]); - str += walk(node.expressions[i]); + str += walk(node.quasis[i], noExecute); + str += walk(node.expressions[i], noExecute); } - str += walk(node.quasis[i]); + str += walk(node.quasis[i], noExecute); return str; } else if (node.type === 'TaggedTemplateExpression') { - var tag = walk(node.tag); + var tag = walk(node.tag, noExecute); var quasi = node.quasi; var strings = quasi.quasis.map(walk); var values = quasi.expressions.map(walk); diff --git a/test/eval.js b/test/eval.js index 87cb6d9..ed5cf90 100644 --- a/test/eval.js +++ b/test/eval.js @@ -44,6 +44,20 @@ test('array methods', function(t) { t.deepEqual(evaluate(ast), [2, 4, 6]); }); +test('array methods invocation count', function(t) { + t.plan(2); + + var variables = { + values: [1, 2, 3], + receiver: [] + }; + var src = 'values.forEach(function(x) { receiver.push(x); })' + var ast = parse(src).body[0].expression; + evaluate(ast, variables); + t.equal(variables.receiver.length, 3); + t.deepEqual(variables.receiver, [1, 2, 3]); +}) + test('array methods with vars', function(t) { t.plan(1); @@ -146,4 +160,18 @@ test('short circuit evaluation OR', function(t) { var ast = parse(src).body[0].expression; evaluate(ast, variables); t.equals(fnInvoked, false); -}) \ No newline at end of file +}) + +test('function declaration does not invoke CallExpressions', function(t) { + t.plan(1); + + var invoked = false; + var variables = { + noop: function(){}, + onInvoke: function() {invoked = true} + }; + var src = 'noop(function(){ onInvoke(); })'; + var ast = parse(src).body[0].expression; + evaluate(ast, variables); + t.equal(invoked, false); +}); \ No newline at end of file