fix: logical expression short-circuiting

This commit is contained in:
cha0s 2024-07-13 17:37:37 -05:00
parent 387e36613f
commit b1df45baa9
2 changed files with 81 additions and 1 deletions

View File

@ -149,7 +149,6 @@ export default class Sandbox {
case 'ChainExpression': case 'ChainExpression':
case 'ObjectExpression': case 'ObjectExpression':
case 'Identifier': case 'Identifier':
case 'LogicalExpression':
case 'MemberExpression': case 'MemberExpression':
case 'UnaryExpression': case 'UnaryExpression':
case 'UpdateExpression': { case 'UpdateExpression': {
@ -174,6 +173,45 @@ export default class Sandbox {
/* v8 ignore next 2 */ /* v8 ignore next 2 */
break; 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': { case 'BlockStatement': {
result = { result = {
value: undefined, value: undefined,

View File

@ -631,3 +631,45 @@ test('breaks loops', async () => {
) )
.to.deep.include({value: [1, 1, 2, 2, 3, 3]}); .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);
});