make WeaselJson_OVERFLOW a terminal state like WeaselJson_REJECT #53

Merged
andrew merged 2 commits from weaselbot/weaseljson:weaselbot/issue-52 into main 2026-07-13 20:58:15 +00:00
Member

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.

## 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
src/parser3.h Outdated
@@ -1074,6 +1076,10 @@ inline WeaselJsonStatus Parser3::parse(char *buf, int len) {
return WeaselJson_REJECT;
}
if (this->overflowed) [[unlikely]] {
Owner

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

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
weaselbot added 1 commit 2026-07-13 20:07:43 +00:00
make WeaselJson_OVERFLOW a terminal state like WeaselJson_REJECT
CI / pre-commit (pull_request) Successful in 53s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 54s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 51s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m35s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m32s
5e462f1477
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
weaselbot force-pushed weaselbot/issue-52 from f0a338f164 to 5e462f1477 2026-07-13 20:07:43 +00:00 Compare
andrew added 1 commit 2026-07-13 20:50:31 +00:00
Don't list all terminal statuses for weaseljson
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 59s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 59s
CI / pre-commit (pull_request) Successful in 51s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m34s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m30s
42d37d1fd7
Also simplifies codegen slightly presumably. OK and AGAIN should be the only non-terminal statuses ever.
andrew scheduled this pull request to auto merge when all checks succeed 2026-07-13 20:50:46 +00:00
andrew merged commit 70bb33eeaf into main 2026-07-13 20:58:15 +00:00
andrew deleted branch weaselbot/issue-52 2026-07-13 20:58:15 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/weaseljson#53