Skip to content

fix(bytecompiler): preserve numeric values in local postfix updates - #5581

Open
Niloyyy wants to merge 2 commits into
boa-dev:mainfrom
Niloyyy:fix/postfix-local-numeric-conversion
Open

Niloyyy wants to merge 2 commits into
boa-dev:mainfrom
Niloyyy:fix/postfix-local-numeric-conversion

Conversation

@Niloyyy

@Niloyyy Niloyyy commented Oct 14, 2026 •

Copy link
Copy Markdown

Local postfix updates can return the original value instead of its numeric conversion. For example, after let n = '1'; const old = n++;, old is incorrectly a string. The same problem affects decrement and function parameters. Additionally, because Inc and Dec previously took the source register with take_register before calling to_numeric(), any local update whose source operand was the local register (n++;, ++n;, const next = ++n;) cleared the local binding to undefined if ToNumeric threw an exception.

Related to #1913, which documents the required ToNumeric(oldValue) result.

It changes the following:

  • Emit Move(result, local); Inc(local, result) (or Dec) so the opcode converts the result register and writes the new value to the local. Previously it converted the local in place and left the result register holding the unconverted value.
  • Clone the source register via get_register(src.into()).clone() in Inc::operation and Dec::operation (matching Pos, Neg, BitNot, and ToInt32) instead of take_register(src.into()) so that an abrupt completion in to_numeric() does not overwrite a local register with undefined.
  • Update the instruction-sequence comments and add six regression tests covering local variables and parameters, primitive conversion, BigInt, NaN, negative zero, object conversion exactly once, and binding preservation when conversion throws across postfix (const old = n++, n++) and prefix (const next = ++n, ++n) updates.

The tests were added before changing production code and failed on unmodified main f0789a20:

test result: FAILED. 0 passed; 6 failed; 0 ignored; 0 measured; 1157 filtered out

One failing JavaScript assertion was:

(() => {
    let n = '1';
    const old = n++;
    return Object.is(old, 1) && Object.is(n, 2);
})()

And for abrupt completion on a discarded or prefix local update:

(() => {
    let n = { [Symbol.toPrimitive]() { throw 42; } };
    const original = n;
    try { n++; } catch (error) {
        return error === 42 && n === original;
    }
    return false;
})()

Both return true with the fix. All six new tests (1144 unit tests in boa_engine) now pass.

Cost: the optimized path still emits two instructions and uses the existing result register. This adds no bytecode instruction or temporary register. Inc and Dec clone JsValue from the source register before to_numeric() instead of taking it out of the register slot. Runtime performance was not benchmarked.

Validation on Rust 1.99.0:

  • cargo +1.99.0 test -p boa_engine --lib: 1,144 passed, 0 failed, 0 ignored.
  • cargo +1.99.0 clippy -p boa_engine --lib --tests -- -D warnings: passed.
  • cargo +1.99.0 fmt --check: passed.

Use the result register as the Inc/Dec source so postfix expressions return the converted old value while the local receives the new value. Preserve the existing two-instruction sequence and add regression coverage for coercion and abrupt completion.
@Niloyyy
Niloyyy requested a review from a team as a code owner October 14, 2026 01:13
@github-actions github-actions Bot added Waiting On Review Waiting on reviews from the maintainers C-Tests Issues and PRs related to the tests. C-VM Issues and PRs related to the Boa Virtual Machine. labels Oct 14, 2026
@github-actions github-actions Bot added this to the v0.23 milestone Oct 14, 2026
Avoid clearing the source register with take_register before calling to_numeric() so discarded (n++/n--) and prefix (++n/--n) local updates preserve their binding if ToNumeric throws.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-Tests Issues and PRs related to the tests. C-VM Issues and PRs related to the Boa Virtual Machine. Waiting On Review Waiting on reviews from the maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant