Finding
json_decode caps a number token at 63 characters and keeps scanning, so the dropped tail is silently discarded and atof parses whatever prefix survived. The result is a valid-looking number of the wrong magnitude, with rc=0.
src/builtins.c:958:
#define JSON_NUM_PUSH() do { if (len < 63) numbuf[len++] = s[p]; p++; } while (0)
That's the if (count < CAP) { write }-with-no-else shape from the #490–#512 silent-tolerance train, in a builtin that sweep didn't reach.
Reproduction
a is json_decode of "{\"v\": 1.000000000000000000000000000000000000000000000000000000000000e50}"
print of ["padded 1e50 ->", a["v"]]
b is json_decode of "{\"v\": 1000000000000000000000000000000000000000000000000000000000000000000000}"
print of ["70-digit int ->", b["v"]]
print of ["the lexer parses the same literals as", 1e50, 1e69]
["padded 1e50 ->", 1] <- off by 50 orders of magnitude
["70-digit int ->", 1e+62] <- off by 7 orders of magnitude
["the lexer parses the same literals as", 1e+50, 1e+69]
The first case is the sharp one: the e50 exponent falls off the end of numbuf, so 1.000…000e50 decodes to 1. Both inputs are valid JSON, and the runtime's own lexer parses the identical literals correctly — so json_decode diverges from the language's own number oracle.
Invariant violated
json_decode must yield the correctly-rounded double for any grammatically valid JSON number, or set g_json_parse_err. It does neither.
The function's own header states the rule it breaks — from #495: "a malformed tail (1e, 1e+, 1.) sets g_json_parse_err instead of letting atof() guess silently". The rationalizing comment ("beyond double's ~24-char maximum the extra digits carry no value anyway") is false precisely when the truncated tail is the exponent or significant integer digits.
This matters because json_decode is the primary untrusted-input boundary. A producer emitting padded or full-precision numbers gets a silently wrong value.
Not load-bearing
Checked tests/test_json*.eigs, test_json_hard, test_json_roundtrip, test_json_large, test_corpus — none exercise a >63-char number and nothing asserts on truncation. Round-trip is unaffected (json_encode emits the compact form), which is why the suite is green.
Fix
Don't truncate. The scanner already validates the grammar, so strtod cannot guess: record the token span (start = *pos, end = p) and strtod directly from s + start, dropping numbuf entirely. Strictly more correct at every length.
(Setting g_json_parse_err at len >= 63 is the minimal alternative, but it rejects valid JSON — prefer the in-place strtod.)
Test to add
json_decode of "[1.<60 zeros>e50]" asserted against the literal 1e50, and the 70-digit integer against 1e69.
Finding
json_decodecaps a number token at 63 characters and keeps scanning, so the dropped tail is silently discarded andatofparses whatever prefix survived. The result is a valid-looking number of the wrong magnitude, with rc=0.src/builtins.c:958:That's the
if (count < CAP) { write }-with-no-elseshape from the #490–#512 silent-tolerance train, in a builtin that sweep didn't reach.Reproduction
The first case is the sharp one: the
e50exponent falls off the end ofnumbuf, so1.000…000e50decodes to1. Both inputs are valid JSON, and the runtime's own lexer parses the identical literals correctly — sojson_decodediverges from the language's own number oracle.Invariant violated
json_decodemust yield the correctly-rounded double for any grammatically valid JSON number, or setg_json_parse_err. It does neither.The function's own header states the rule it breaks — from #495: "a malformed tail (
1e,1e+,1.) setsg_json_parse_errinstead of lettingatof()guess silently". The rationalizing comment ("beyond double's ~24-char maximum the extra digits carry no value anyway") is false precisely when the truncated tail is the exponent or significant integer digits.This matters because
json_decodeis the primary untrusted-input boundary. A producer emitting padded or full-precision numbers gets a silently wrong value.Not load-bearing
Checked
tests/test_json*.eigs,test_json_hard,test_json_roundtrip,test_json_large,test_corpus— none exercise a >63-char number and nothing asserts on truncation. Round-trip is unaffected (json_encodeemits the compact form), which is why the suite is green.Fix
Don't truncate. The scanner already validates the grammar, so
strtodcannot guess: record the token span (start = *pos,end = p) andstrtoddirectly froms + start, droppingnumbufentirely. Strictly more correct at every length.(Setting
g_json_parse_erratlen >= 63is the minimal alternative, but it rejects valid JSON — prefer the in-placestrtod.)Test to add
json_decode of "[1.<60 zeros>e50]"asserted against the literal1e50, and the 70-digit integer against1e69.