Skip to content

fix(json): preserve the sign of -0 in the integer fast path - #4061

Merged
bobzhang merged 1 commit into
mainfrom
agent/fix-json-negative-zero
Aug 15, 2026
Merged

fix(json): preserve the sign of -0 in the integer fast path#4061
bobzhang merged 1 commit into
mainfrom
agent/fix-json-negative-zero

Conversation

@bobzhang

@bobzhang bobzhang commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #4053

parse("-0") returned +0.0 while parse("-0.0") / parse("-0e0") returned -0.0 — the same mathematical literal parsed to different IEEE-754 values depending on spelling, contrary to RFC 8259 number semantics and JSON.parse. The bug is invisible to Json equality because -0.0 == 0.0 under Double comparison.

Root cause: the safe-integer fast path in lex_number_end (json/lex_number.mbt) negated the mantissa as an Int64 (-0L == 0L) before converting to Double. Fixed by negating after the conversion — exact for the whole safe-integer range.

Deterministic regression tests in json/lex_number_test.mbt cover every spelling of negative zero (-0, -0.0, -0e0, -0.00E-7) plus negative values that underflow to zero (-1e-400, -4.9e-325, huge negative exponents), which already preserved the sign.

Based directly on main; independent of #4056. The adversarial QuickCheck suite that found this bug is #4045.

Verified: moon test -p moonbitlang/core/json green on wasm-gc, js, and native; no .mbti changes.

🤖 Generated with Claude Code

@coveralls

coveralls commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 6067

Coverage decreased (-0.001%) to 90.665%

Details

  • Coverage decreased (-0.001%) from the base build.
  • Patch coverage: 2 of 2 lines across 1 file are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 17644
Covered Lines: 15997
Line Coverage: 90.67%
Coverage Strength: 150846.51 hits per line

💛 - Coveralls

Fixes #4053.

parse("-0") returned +0.0 while parse("-0.0") / parse("-0e0") returned
-0.0: the safe-integer fast path in lex_number_end negated the mantissa
as an Int64 (where -0 == 0) before converting to Double. Negate after
the conversion so every spelling of negative zero keeps the IEEE-754
sign bit, per RFC 8259 number semantics. The conversion-then-negation
is exact for the whole safe-integer range.

Deterministic regression tests in lex_number_test.mbt cover every
spelling of negative zero, including values that underflow to zero
(which already preserved the sign).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bobzhang
bobzhang force-pushed the agent/fix-json-negative-zero branch from 15d2ac4 to b93a00e Compare August 15, 2026 02:55
@bobzhang
bobzhang changed the base branch from agent/fix-json-lexer to main August 15, 2026 02:55
bobzhang added a commit that referenced this pull request Aug 15, 2026
…e policy

The parser now keeps strings Unicode well-formed (#4056): unpaired
surrogates — raw or as \uXXXX escapes — are rejected with a ParseError
instead of being passed through. Update the adversarial suite to match:

- the roundtrip and fully-escaped generators produce only Unicode
  scalar values (astral pairs still included), and the AdvString
  shrinker drops whole characters so candidates stay well-formed;
- new property: a lone surrogate injected at any position of a hostile
  string — raw via stringify or spelled as a \uXXXX escape, with
  escapes/astral pairs/control characters nearby — is always rejected
  cleanly (parse raises, valid is false, never an abort);
- the deterministic surrogate pins now assert rejection for raw,
  escaped, reversed-pair, and mixed raw/escaped-half spellings, while
  well-formed pairs (raw or split across two escapes) still parse.

Note: "zero literals preserve the sign of zero" requires the parse(-0)
fix from #4061 (based on main) and fails until that lands in this
branch's history; all other tests are green on wasm-gc, js, and native.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread json/lex_number.mbt
let signed = if scan.negative { -v } else { v }
return { value: signed.to_double(), repr: None }
let v = scan.mantissa.reinterpret_as_int64().to_double()
let value = if scan.negative { -v } else { v }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FTR: here v used to be int where -v (when v is 0) does not make sense

@bobzhang
bobzhang merged commit e4a037e into main Aug 15, 2026
19 checks passed
@bobzhang
bobzhang deleted the agent/fix-json-negative-zero branch August 15, 2026 03:05
bobzhang added a commit that referenced this pull request Aug 15, 2026
…e policy

The parser now keeps strings Unicode well-formed (#4056): unpaired
surrogates — raw or as \uXXXX escapes — are rejected with a ParseError
instead of being passed through. Update the adversarial suite to match:

- the roundtrip and fully-escaped generators produce only Unicode
  scalar values (astral pairs still included), and the AdvString
  shrinker drops whole characters so candidates stay well-formed;
- new property: a lone surrogate injected at any position of a hostile
  string — raw via stringify or spelled as a \uXXXX escape, with
  escapes/astral pairs/control characters nearby — is always rejected
  cleanly (parse raises, valid is false, never an abort);
- the deterministic surrogate pins now assert rejection for raw,
  escaped, reversed-pair, and mixed raw/escaped-half spellings, while
  well-formed pairs (raw or split across two escapes) still parse.

Note: "zero literals preserve the sign of zero" requires the parse(-0)
fix from #4061 (based on main) and fails until that lands in this
branch's history; all other tests are green on wasm-gc, js, and native.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

json: parse("-0") loses the sign of negative zero

2 participants