Skip to content

Register forwarding can reread a mutable left operand after right-side effects #5456

Description

@dev-willbird1936

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions