Describe the bug
Register forwarding in the bytecode compiler can violate JavaScript's
left-to-right operand evaluation when the left operand is a mutable local and
the right operand mutates that local. The generated binary operation rereads
the mutated value instead of using the value captured before evaluating the
right operand.
To Reproduce
Run this script:
let a = 1;
a = a + (a = 5);
a;
Current main evaluates the final expression to 10.
The same stale-value problem affects other forwarded binary operations, for
example:
let a = 1;
a = a | (a = 4); // current result: 4, expected: 5
Expected behavior
The left operand must be evaluated and retained before the right operand is
evaluated. The first example should therefore produce 6, not 10.
Build environment (please complete the following information):
- OS: Windows 11
- Boa commit:
f54077467b4b01eb0fe221cfff470a8546ebe36c
- Target triple:
x86_64-pc-windows-gnu
- Rust toolchain:
1.95.0-x86_64-pc-windows-gnu
Additional context
The regression begins in the register-forwarding path introduced by #4845.
Immutable locals and cached constants can retain the fast path; mutable locals
need to be snapshotted before compiling an operand that may have side effects.
Describe the bug
Register forwarding in the bytecode compiler can violate JavaScript's
left-to-right operand evaluation when the left operand is a mutable local and
the right operand mutates that local. The generated binary operation rereads
the mutated value instead of using the value captured before evaluating the
right operand.
To Reproduce
Run this script:
Current
mainevaluates the final expression to10.The same stale-value problem affects other forwarded binary operations, for
example:
Expected behavior
The left operand must be evaluated and retained before the right operand is
evaluated. The first example should therefore produce
6, not10.Build environment (please complete the following information):
f54077467b4b01eb0fe221cfff470a8546ebe36cx86_64-pc-windows-gnu1.95.0-x86_64-pc-windows-gnuAdditional context
The regression begins in the register-forwarding path introduced by #4845.
Immutable locals and cached constants can retain the fast path; mutable locals
need to be snapshotted before compiling an operand that may have side effects.