fix(json): preserve the sign of -0 in the integer fast path - #4061
Merged
Conversation
Collaborator
Coverage Report for CI Build 6067Coverage decreased (-0.001%) to 90.665%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - 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
force-pushed
the
agent/fix-json-negative-zero
branch
from
August 15, 2026 02:55
15d2ac4 to
b93a00e
Compare
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>
bobzhang
commented
Aug 15, 2026
| 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 } |
Contributor
Author
There was a problem hiding this comment.
FTR: here v used to be int where -v (when v is 0) does not make sense
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4053
parse("-0")returned +0.0 whileparse("-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 andJSON.parse. The bug is invisible toJsonequality because-0.0 == 0.0underDoublecomparison.Root cause: the safe-integer fast path in
lex_number_end(json/lex_number.mbt) negated the mantissa as anInt64(-0L == 0L) before converting toDouble. Fixed by negating after the conversion — exact for the whole safe-integer range.Deterministic regression tests in
json/lex_number_test.mbtcover 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/jsongreen on wasm-gc, js, and native; no.mbtichanges.🤖 Generated with Claude Code