WeaselJson_OVERFLOW was not a terminal state: after a parse step returned it, the pushdown stack was left corrupted (frames had been pop()ed before the failing push()), but — unlike WeaselJson_REJECT — overflow was not made sticky. A subsequent end-of-data call WeaselJsonParser_parse(parser, nullptr, 0) could then dispatch through the corrupted stack and return WeaselJson_OK, accepting an incomplete, invalid too-deeply-nested document as valid JSON.
Fix
Replace the separate rejected boolean with a single sticky status field, terminalStatus (a WeaselJsonStatus):
It is WeaselJson_OK while parsing is still live, and set to WeaselJson_REJECT or WeaselJson_OVERFLOW as soon as a parse step (or the len < 0 guard) returns that terminal status.
parse() short-circuits: if terminalStatus != WeaselJson_OK it returns that value directly, so the corrupted stack is never touched again and every subsequent call (including the len == 0 end-of-data call) keeps reporting the terminal failure.
reset() resets terminalStatus back to WeaselJson_OK, so a reused parser can accept a different document.
Using one sticky status (rather than separate rejected/overflowed flags) keeps the terminal-state handling in a single branch and makes it trivial to extend if further terminal statuses are added.
Tests
Added regression tests in src/test.cpp:
overflow state is sticky: reproduces the issue (nested arrays, stackSize=3) and verifies the EOF call and further data calls never return WeaselJson_OK.
overflow is sticky for nested objects: covers the nested-object shape (stackSize=4).
reset clears overflow state: confirms WeaselJsonParser_reset restores normal operation after an overflow.
All existing tests continue to pass.
## Summary
Fixes #52.
`WeaselJson_OVERFLOW` was not a terminal state: after a parse step returned it, the pushdown stack was left corrupted (frames had been `pop()`ed before the failing `push()`), but — unlike `WeaselJson_REJECT` — overflow was not made sticky. A subsequent end-of-data call `WeaselJsonParser_parse(parser, nullptr, 0)` could then dispatch through the corrupted stack and return `WeaselJson_OK`, accepting an incomplete, invalid too-deeply-nested document as valid JSON.
## Fix
Replace the separate `rejected` boolean with a single sticky status field, `terminalStatus` (a `WeaselJsonStatus`):
- It is `WeaselJson_OK` while parsing is still live, and set to `WeaselJson_REJECT` or `WeaselJson_OVERFLOW` as soon as a parse step (or the `len < 0` guard) returns that terminal status.
- `parse()` short-circuits: if `terminalStatus != WeaselJson_OK` it returns that value directly, so the corrupted stack is never touched again and every subsequent call (including the `len == 0` end-of-data call) keeps reporting the terminal failure.
- `reset()` resets `terminalStatus` back to `WeaselJson_OK`, so a reused parser can accept a different document.
Using one sticky status (rather than separate `rejected`/`overflowed` flags) keeps the terminal-state handling in a single branch and makes it trivial to extend if further terminal statuses are added.
## Tests
Added regression tests in `src/test.cpp`:
- `overflow state is sticky`: reproduces the issue (nested arrays, `stackSize=3`) and verifies the EOF call and further data calls never return `WeaselJson_OK`.
- `overflow is sticky for nested objects`: covers the nested-object shape (`stackSize=4`).
- `reset clears overflow state`: confirms `WeaselJsonParser_reset` restores normal operation after an overflow.
All existing tests continue to pass.
andrew
requested changes 2026-07-13 19:54:15 +00:00
When a parse step returned WeaselJson_OVERFLOW, the pushdown stack was
left corrupted (frames had been popped before the failing push), but the
overflow status was not made sticky the way WeaselJson_REJECT is. A
subsequent end-of-data call WeaselJsonParser_parse(parser, nullptr, 0)
could then dispatch through the corrupted stack and return WeaselJson_OK,
accepting an incomplete, invalid too-deeply-nested document as valid JSON.
Add an `overflowed` flag, set it whenever a continuation returns
WeaselJson_OVERFLOW, and short-circuit parse() to return
WeaselJson_OVERFLOW on every subsequent call. reset() clears the flag so a
reused parser can accept a different document. This mirrors the existing
stickiness handling for WeaselJson_REJECT.
Closes#52
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Fixes #52.
WeaselJson_OVERFLOWwas not a terminal state: after a parse step returned it, the pushdown stack was left corrupted (frames had beenpop()ed before the failingpush()), but — unlikeWeaselJson_REJECT— overflow was not made sticky. A subsequent end-of-data callWeaselJsonParser_parse(parser, nullptr, 0)could then dispatch through the corrupted stack and returnWeaselJson_OK, accepting an incomplete, invalid too-deeply-nested document as valid JSON.Fix
Replace the separate
rejectedboolean with a single sticky status field,terminalStatus(aWeaselJsonStatus):WeaselJson_OKwhile parsing is still live, and set toWeaselJson_REJECTorWeaselJson_OVERFLOWas soon as a parse step (or thelen < 0guard) returns that terminal status.parse()short-circuits: ifterminalStatus != WeaselJson_OKit returns that value directly, so the corrupted stack is never touched again and every subsequent call (including thelen == 0end-of-data call) keeps reporting the terminal failure.reset()resetsterminalStatusback toWeaselJson_OK, so a reused parser can accept a different document.Using one sticky status (rather than separate
rejected/overflowedflags) keeps the terminal-state handling in a single branch and makes it trivial to extend if further terminal statuses are added.Tests
Added regression tests in
src/test.cpp:overflow state is sticky: reproduces the issue (nested arrays,stackSize=3) and verifies the EOF call and further data calls never returnWeaselJson_OK.overflow is sticky for nested objects: covers the nested-object shape (stackSize=4).reset clears overflow state: confirmsWeaselJsonParser_resetrestores normal operation after an overflow.All existing tests continue to pass.
@@ -1074,6 +1076,10 @@ inline WeaselJsonStatus Parser3::parse(char *buf, int len) {return WeaselJson_REJECT;}if (this->overflowed) [[unlikely]] {We should just have one "sticky status" and if it's non-zero return that. That way we don't need separate branches for rejected and overflowed
f0a338f164to5e462f1477