Skip to content

GROOVY-12329: JsonSlurper: bound the length of a JSON number token - #2856

Merged
paulk-asert merged 1 commit into
apache:masterfrom
paulk-asert:groovy12329
Sep 3, 2026
Merged

GROOVY-12329: JsonSlurper: bound the length of a JSON number token#2856
paulk-asert merged 1 commit into
apache:masterfrom
paulk-asert:groovy12329

Conversation

@paulk-asert

Copy link
Copy Markdown
Contributor

A digit run past the long range is converted with BigInteger, and one with a decimal point or exponent with BigDecimal. Both conversions are superlinear in the digit count and neither token was bounded anywhere in groovy-json, so a document of a few hundred KB cost seconds of CPU: a 400,000-digit number took about two seconds on every parser variant, and doubling the digits quadrupled the time.

A number token longer than maxNumberLength is now rejected with a JsonException before the conversion that would pay for it. The default of 1000 characters matches Jackson's StreamReadConstraints, so JSON is bounded consistently with the Jackson-backed YAML/TOML/CSV slurpers, and follows the shape GROOVY-12064 established for nesting depth: a per instance setter on JsonSlurper and JsonSlurperClassic, the groovy.json.maxNumberLength system property to override globally, and a value of 0 or less to disable the check. Rejecting a 400,000-digit number now takes single-digit milliseconds instead of two seconds.

All four JsonParserType variants and the classic parser funnel through their own conversion, so each enforces the bound where it reads the token. The INDEX_OVERLAY and LAX parsers defer conversion to first access, so they check while decoding rather than in NumberValue: a document over the limit is rejected by the parse that read it, not by a later read of the value.

This tightens what the parsers accept. A number longer than 1000 characters that parsed before is now rejected by default; callers that legitimately carry such values can raise or disable the limit.

A digit run past the long range is converted with BigInteger, and one
with a decimal point or exponent with BigDecimal. Both conversions are
superlinear in the digit count and neither token was bounded anywhere in
groovy-json, so a document of a few hundred KB cost seconds of CPU: a
400,000-digit number took about two seconds on every parser variant, and
doubling the digits quadrupled the time.

A number token longer than maxNumberLength is now rejected with a
JsonException before the conversion that would pay for it. The default
of 1000 characters matches Jackson's StreamReadConstraints, so JSON is
bounded consistently with the Jackson-backed YAML/TOML/CSV slurpers, and
follows the shape GROOVY-12064 established for nesting depth: a per
instance setter on JsonSlurper and JsonSlurperClassic, the
groovy.json.maxNumberLength system property to override globally, and a
value of 0 or less to disable the check. Rejecting a 400,000-digit
number now takes single-digit milliseconds instead of two seconds.

All four JsonParserType variants and the classic parser funnel through
their own conversion, so each enforces the bound where it reads the
token. The INDEX_OVERLAY and LAX parsers defer conversion to first
access, so they check while decoding rather than in NumberValue: a
document over the limit is rejected by the parse that read it, not by a
later read of the value.

This tightens what the parsers accept. A number longer than 1000
characters that parsed before is now rejected by default; callers that
legitimately carry such values can raise or disable the limit.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.28571% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.8032%. Comparing base (c251a83) to head (34080e7).

Files with missing lines Patch % Lines
...vy-json/src/main/java/groovy/json/JsonSlurper.java 83.3333% 1 Missing ⚠️
...rg/apache/groovy/json/internal/BaseJsonParser.java 83.3333% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##               master      #2856        +/-   ##
==================================================
+ Coverage     70.8002%   70.8032%   +0.0030%     
- Complexity      36979      36993        +14     
==================================================
  Files            1576       1576                
  Lines          134792     134823        +31     
  Branches        24954      24955         +1     
==================================================
+ Hits            95433      95459        +26     
- Misses          30756      30764         +8     
+ Partials         8603       8600         -3     
Files with missing lines Coverage Δ
.../src/main/java/groovy/json/JsonSlurperClassic.java 47.9290% <100.0000%> (+3.6252%) ⬆️
...a/org/apache/groovy/json/internal/CharScanner.java 76.5182% <100.0000%> (+0.8984%) ⬆️
...rg/apache/groovy/json/internal/JsonFastParser.java 77.6316% <100.0000%> (+0.1481%) ⬆️
...ache/groovy/json/internal/JsonParserCharArray.java 81.2866% <100.0000%> (ø)
...org/apache/groovy/json/internal/JsonParserLax.java 72.1312% <100.0000%> (+0.0917%) ⬆️
.../json/internal/JsonParserUsingCharacterSource.java 87.3016% <100.0000%> (+0.1016%) ⬆️
...vy-json/src/main/java/groovy/json/JsonSlurper.java 53.3333% <83.3333%> (+1.8182%) ⬆️
...rg/apache/groovy/json/internal/BaseJsonParser.java 75.2941% <83.3333%> (+0.6106%) ⬆️

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@testlens-app

testlens-app Bot commented Sep 2, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 34080e7
▶️ Tests: 115111 executed
⚪️ Checks: 31/31 completed


Learn more about TestLens at testlens.app/docs.

@paulk-asert
paulk-asert merged commit fab3f6b into apache:master Sep 3, 2026
32 checks passed
@paulk-asert
paulk-asert deleted the groovy12329 branch September 3, 2026 23:16
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.

2 participants