Register forwarding can reread a mutable left operand after right-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:
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: 5Expected 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.
Source: boa-dev/boa