Skip to content

Reject a leading zero after the minus sign in JSON.parse - #2322

Open
0z5a wants to merge 2 commits into
meta:static_hfrom
0z5a:fix/json-negative-leading-zero
Open

0z5a wants to merge 2 commits into
meta:static_hfrom
0z5a:fix/json-negative-leading-zero

Conversation

@0z5a

@0z5a 0z5a commented Nov 13, 2026 •

Copy link
Copy Markdown

Summary

Fixes #2321

JSON.parse("-01") returned -1 and JSON.parse("-00") returned -0 instead of throwing a SyntaxError. The leading-zero check in JSONLexer::scanNumber read numRef[0] and numRef[1]. numRef includes the minus sign, so the check never fired for a negative number. The JSON number grammar does not allow a leading zero in the integer part (ECMA-404, RFC 8259 section 6). JSON.parse("015") already throws, and V8 throws for -01 too.

The check now looks at the first digit of the integer part, right after the optional minus sign. The error message is the same as for 015 (Unexpected character in number: 1). The check is in the same function as the one that b616636 added for a minus sign without digits.

Test Plan

  • Added cases to test/hermes/json.js: -015, -00 and [-01.5] must throw, and -0, -0.5 and [-10] must still parse. 015, -015, -00, -0, -0.5 and -10 are also checked with a non-ASCII character in the input, which makes JSON.parse use the UTF-16 lexer. On static_h @ cf2ddd2 the test fails at json.js:65 (CHECK-NEXT: expected string not found in input, because parse("-015") printed nothing). With this change it passes on all three RUN lines (hermes, hermes running the HBC file, shermes -exec).
  • The repro script from the issue (15 inputs) now prints exactly what node v24.14.1 prints.
  • The whole test/hermes lit directory with this change: 660 passed, 3 expected failures, 45 unsupported, no unexpected failures.
  • test262 built-ins/JSON and staging/sm/JSON through utils/test_runner.py: 178 passed, 0 failed, both before and after the change.
  • HermesVMRuntimeTests --gtest_filter='*JSON*': 6/6 passed.
  • clang-format 20.1.8 reports no changes for JSONLexer.cpp.
  • Release build with Apple clang 21 on macOS arm64 (Apple M-series). The JSI UTF-8 parse path uses the same lexer code, but no test feeds it a number with a leading zero.
  • Review round: the check now runs while scanning (0c87619). The lit directory, test262, gtest and repro runs above were repeated on 0c87619 with the same results. Without the lexer change, -015 and -00 also parse on the UTF-16 path. The branch is on static_h @ cf2ddd2; the one newer static_h commit does not touch JSON code.

This change was drafted with Claude Code (claude-opus-5-5); the test results above are from the runs described.

The leading-zero check in JSONLexer::scanNumber looked at the first two
characters of the number, including the minus sign, so it never fired
for a negative number: JSON.parse("-01") returned -1 and
JSON.parse("-00") returned -0. The JSON number grammar (ECMA-404,
RFC 8259) does not allow a leading zero in the integer part, and
JSON.parse("015") already throws.

Skip the minus sign before checking the integer part.
@meta-cla meta-cla Bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Nov 13, 2026
@ljharb

ljharb commented Nov 13, 2026

Copy link
Copy Markdown

Be sure that JSON.parse('-0.2') and JSON.parse('-0') still work, though.

@0z5a

0z5a commented Nov 13, 2026

Copy link
Copy Markdown
Author

Thanks, both still work. The added test covers them: 1 / JSON.parse("-0") prints -Infinity and JSON.parse("-0.5") prints -0.5. On this branch I also checked -0.2, -0.0, -0e1, -0E-2 and -0.5e+3; the results match node v24.

Comment thread test/hermes/json.js
}
parse("015");
//CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character in number: 1
parse("-015");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please also add test for unicode.

// If there is a leading minus sign, there must be a digit following it.
if (LLVM_UNLIKELY(!hasChar() || *iter_.cur < '0' || *iter_.cur > '9')) {
return error("No digits after minus sign in JSON number");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This seems simpler.

if (*iter_.cur == '0') {
    ++iter_.cur;
    if (LLVM_UNLIKELY(hasChar() && *iter_.cur >= '0' && *iter_.cur <= '9'))
      return errorWithChar(u"Unexpected character in number: ", *iter_.cur);
  }

As suggested in review, check the first digit of the integer part while
scanning instead of slicing the captured number afterwards. Add json.js
cases with a non-ASCII character in the input, which makes JSON.parse use
the UTF-16 lexer.
@0z5a

0z5a commented Nov 13, 2026

Copy link
Copy Markdown
Author

Thanks, both done in 0c87619:

  • The leading-zero check is now your version, right after the minus-sign check, and the check on numRef after the scan is gone.
  • json.js now also runs 015, -015, -00 and -0 / -0.5 / -10 with a non-ASCII character in the input, which makes JSON.parse use the UTF-16 lexer. Without the fix, -015 and -00 parse there too.

json.js passes on all three RUN lines, the whole test/hermes lit directory has no unexpected failures, test262 built-ins/JSON and staging/sm/JSON pass (178, 0 failed), and so do the JSON gtests. I updated the description.

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

CLA Signed Do not delete this pull request or issue due to inactivity.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSON.parse accepts a leading zero after a minus sign: JSON.parse("-01") returns -1

3 participants