diff --git a/app/astride/sandbox.js b/app/astride/sandbox.js index fe51520..0f11711 100644 --- a/app/astride/sandbox.js +++ b/app/astride/sandbox.js @@ -149,7 +149,6 @@ export default class Sandbox { case 'ChainExpression': case 'ObjectExpression': case 'Identifier': - case 'LogicalExpression': case 'MemberExpression': case 'UnaryExpression': case 'UpdateExpression': { @@ -174,6 +173,45 @@ export default class Sandbox { /* v8 ignore next 2 */ break; } + case 'LogicalExpression': { + const shouldVisitChild = (child) => ( + isReplaying + ? (!this.$$execution.stack[depth + 1] || this.$$execution.stack[depth + 1] === child) + : true + ); + if (shouldVisitChild(node.left)) { + const left = this.executeSync(node.left, depth + 1); + if (left.yield) { + return left; + } + this.$$execution.deferred.set(node.left, left); + } + const left = this.$$execution.deferred.get(node.left); + this.$$execution.deferred.delete(node.left); + if ('||' === node.operator && left.value) { + result = { + value: true, + yield: YIELD_NONE, + }; + break; + } + if ('&&' === node.operator && !left.value) { + result = { + value: false, + yield: YIELD_NONE, + }; + break; + } + const right = this.executeSync(node.right, depth + 1); + if (right.yield) { + return right; + } + result = { + value: !!right.value, + yield: YIELD_NONE, + }; + break; + } case 'BlockStatement': { result = { value: undefined, diff --git a/app/astride/sandbox.test.js b/app/astride/sandbox.test.js index 4269b4b..a23163e 100644 --- a/app/astride/sandbox.test.js +++ b/app/astride/sandbox.test.js @@ -631,3 +631,45 @@ test('breaks loops', async () => { ) .to.deep.include({value: [1, 1, 2, 2, 3, 3]}); }); + +test('short-circuits logical expressions', async () => { + let x = 0; + expect( + (new Sandbox( + await parse(` + let y = 0; + if (test || test()) { + y = 1; + } + y + `), + { + test: () => { + x = 1; + }, + } + )).run() + ) + .to.deep.include({value: 1}); + expect(x) + .to.equal(0); + expect( + (new Sandbox( + await parse(` + let y = 0; + if (!test && test()) { + y = 1; + } + y + `), + { + test: () => { + x = 1; + }, + } + )).run() + ) + .to.deep.include({value: 0}); + expect(x) + .to.equal(0); +});