-
-
Notifications
You must be signed in to change notification settings - Fork 159
fix(json): throw instead of crashing on deeply nested JSON.parse input (#7792) #7816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| `JSON.parse` no longer crashes the process on deeply nested input. | ||
|
|
||
| A document nested a few tens of thousands of levels deep did not throw — it | ||
| killed the process with SIGSEGV and printed nothing at all, so a program had | ||
| no way to see what happened, let alone recover: | ||
|
|
||
| ``` | ||
| node → parses it | ||
| scriptc → throws a catchable RangeError | ||
| perry → SIGSEGV, exit 139, no output | ||
| ``` | ||
|
|
||
| Deeply nested JSON is a well-known shape for untrusted input, which is what | ||
| makes a crash the wrong answer even though such a document is unusual. | ||
|
|
||
| Two parsers read the text, and both descend one function call per nesting | ||
| level: the syntax-validation pass and Perry's own value parser. Deep enough | ||
| input exhausts the stack in whichever reaches it first. | ||
|
|
||
| `JSON.parse` now measures nesting depth first and throws a catchable | ||
| `RangeError` when it exceeds 1,000 levels. The measurement is a single | ||
| non-recursive scan of the text, which matters more than it sounds: a recursive | ||
| depth check would crash on exactly the documents it exists to reject. It also | ||
| runs *before* syntax validation, since that pass recurses too and would crash | ||
| first otherwise — which means the scan sees malformed input and has to cope | ||
| with it, so brackets inside strings do not count and a stray closing bracket | ||
| clamps at zero instead of underflowing. | ||
|
|
||
| The limit is 1,000 because it has to be safe on the *smallest* stack in the | ||
| process, not the largest. Perry parses JSON on worker threads as well as the | ||
| main thread, and a 2 MiB thread stack overflows far earlier than the main | ||
| thread's 8 MB does. A first attempt used 10,000, taken from a main-thread | ||
| measurement, and the unit test crashed the test harness at 9,999 levels — so | ||
| the number is what a small stack can carry, not what a big one can. It matches | ||
| the depth Python's parser settled on, and real documents are not close: JSON | ||
| nested past a hundred levels is already unusual. | ||
|
|
||
| This is a deliberate gap against Node, which parses far deeper because V8's | ||
| parser is iterative and consumes no stack per level. Closing it means making | ||
| Perry's parser iterative too, tracked separately. Until then a catchable error | ||
| is strictly better than a crash. | ||
|
|
||
| Refs #7792. |
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
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
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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Test the exact accepted maximum.
The test accepts depth
MAX_NESTING_DEPTH - 1and rejectsMAX_NESTING_DEPTH + 1. An implementation that rejects depthMAX_NESTING_DEPTHwould still pass. Assert that exactlyMAX_NESTING_DEPTHparses successfully.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents