From 8835eabd85eb40a6bc1b25230e46a662f427aa00 Mon Sep 17 00:00:00 2001 From: 0z5a Date: Fri, 13 Nov 2026 18:54:08 +0100 Subject: [PATCH 1/2] Reject a leading zero after the minus sign in JSON numbers 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. --- lib/VM/JSLib/JSONLexer.cpp | 15 +++++++++------ test/hermes/json.js | 8 ++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/VM/JSLib/JSONLexer.cpp b/lib/VM/JSLib/JSONLexer.cpp index 66d6b9490b7..828a42a0d59 100644 --- a/lib/VM/JSLib/JSONLexer.cpp +++ b/lib/VM/JSLib/JSONLexer.cpp @@ -221,14 +221,17 @@ ExecutionStatus JSONLexer::scanNumber() { numRef = iter_.cur.endCapture(); } - // Check for illegal leading 0. - size_t len = numRef.size(); - assert(len > 0 && "scanNumber must be called on a number-looking char"); + // Check for illegal leading 0 in the integer part, which follows the + // optional minus sign. + assert( + numRef.size() > 0 && + "scanNumber must be called on a number-looking char"); + llvh::ArrayRef intRef = multiplier < 0 ? numRef.drop_front() : numRef; if (LLVM_UNLIKELY( - numRef[0] == '0' && len > 1 && numRef[1] >= '0' && - numRef[1] <= '9')) { + intRef.size() > 1 && intRef[0] == '0' && intRef[1] >= '0' && + intRef[1] <= '9')) { // The integer part cannot start with 0, unless it's 0. - return errorWithChar(u"Unexpected character in number: ", numRef[1]); + return errorWithChar(u"Unexpected character in number: ", intRef[1]); } if (isTrivialInteger) { diff --git a/test/hermes/json.js b/test/hermes/json.js index e5755693761..4287cf1d5d3 100644 --- a/test/hermes/json.js +++ b/test/hermes/json.js @@ -61,6 +61,14 @@ function parse(json) { } parse("015"); //CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character in number: 1 +parse("-015"); +//CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character in number: 1 +parse("-00"); +//CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character in number: 0 +parse("[-01.5]"); +//CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character in number: 1 +print(1 / JSON.parse("-0"), JSON.parse("-0.5"), JSON.parse("[-10]")[0]); +//CHECK-NEXT: -Infinity -0.5 -10 parse("+5"); //CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character: + parse("-"); From 0c87619894a5913921d1ac27d9b0240c8cbed133 Mon Sep 17 00:00:00 2001 From: 0z5a Date: Sat, 14 Nov 2026 00:07:14 +0100 Subject: [PATCH 2/2] Check for a leading zero right after the minus sign 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. --- lib/VM/JSLib/JSONLexer.cpp | 20 +++++++------------- test/hermes/json.js | 10 ++++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/VM/JSLib/JSONLexer.cpp b/lib/VM/JSLib/JSONLexer.cpp index 828a42a0d59..51189279679 100644 --- a/lib/VM/JSLib/JSONLexer.cpp +++ b/lib/VM/JSLib/JSONLexer.cpp @@ -195,6 +195,13 @@ ExecutionStatus JSONLexer::scanNumber() { } } + // The integer part cannot start with 0, unless it's 0. + 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); + } + bool isTrivialInteger = true; uint64_t integer = 0; constexpr uint64_t kFirstIntThatCanOverflow = (UINT64_MAX - 9) / 10; @@ -221,19 +228,6 @@ ExecutionStatus JSONLexer::scanNumber() { numRef = iter_.cur.endCapture(); } - // Check for illegal leading 0 in the integer part, which follows the - // optional minus sign. - assert( - numRef.size() > 0 && - "scanNumber must be called on a number-looking char"); - llvh::ArrayRef intRef = multiplier < 0 ? numRef.drop_front() : numRef; - if (LLVM_UNLIKELY( - intRef.size() > 1 && intRef[0] == '0' && intRef[1] >= '0' && - intRef[1] <= '9')) { - // The integer part cannot start with 0, unless it's 0. - return errorWithChar(u"Unexpected character in number: ", intRef[1]); - } - if (isTrivialInteger) { token_.setNumber((double)integer * multiplier); return ExecutionStatus::RETURNED; diff --git a/test/hermes/json.js b/test/hermes/json.js index 4287cf1d5d3..55b1363534d 100644 --- a/test/hermes/json.js +++ b/test/hermes/json.js @@ -69,6 +69,16 @@ parse("[-01.5]"); //CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character in number: 1 print(1 / JSON.parse("-0"), JSON.parse("-0.5"), JSON.parse("[-10]")[0]); //CHECK-NEXT: -Infinity -0.5 -10 +// A non-ASCII character in the input makes JSON.parse use the UTF-16 lexer. +parse("[\"\u4f60\", 015]"); +//CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character in number: 1 +parse("[\"\u4f60\", -015]"); +//CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character in number: 1 +parse("[\"\u4f60\", -00]"); +//CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character in number: 0 +var u = JSON.parse("[\"\u4f60\", -0, -0.5, -10]"); +print(1 / u[1], u[2], u[3]); +//CHECK-NEXT: -Infinity -0.5 -10 parse("+5"); //CHECK-NEXT: SyntaxError: JSON Parse error: Unexpected character: + parse("-");