From f0a338f164c8eb83a0e606a8156afa5ea6e0077f Mon Sep 17 00:00:00 2001 From: Weaselbot Date: Mon, 13 Jul 2026 11:45:58 -0400 Subject: [PATCH] make WeaselJson_OVERFLOW a terminal state like WeaselJson_REJECT 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 --- src/parser3.h | 10 +++++++++ src/test.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/parser3.h b/src/parser3.h index 639a05f..338ac32 100644 --- a/src/parser3.h +++ b/src/parser3.h @@ -140,6 +140,7 @@ struct Parser3 { std::ignore = push({N_VALUE, N_WHITESPACE, T_EOF}); inKey = false; rejected = false; + overflowed = false; utf8Codepoint = 0; utf16Surrogate = 0; minCodepoint = 0; @@ -163,6 +164,7 @@ struct Parser3 { Utf8Dfa strDfa; bool inKey = false; bool rejected = false; + bool overflowed = false; #ifndef HAS_MUSTTAIL char *stashBufForTrampoline; @@ -1074,6 +1076,10 @@ inline WeaselJsonStatus Parser3::parse(char *buf, int len) { return WeaselJson_REJECT; } + if (this->overflowed) [[unlikely]] { + return WeaselJson_OVERFLOW; + } + if (len < 0) [[unlikely]] { this->rejected = true; return WeaselJson_REJECT; @@ -1087,6 +1093,8 @@ inline WeaselJsonStatus Parser3::parse(char *buf, int len) { symbolTables.continuations[top()](this, buf, buf + len); if (status == WeaselJson_REJECT) { this->rejected = true; + } else if (status == WeaselJson_OVERFLOW) { + this->overflowed = true; } return WeaselJsonStatus(status); #else @@ -1097,6 +1105,8 @@ inline WeaselJsonStatus Parser3::parse(char *buf, int len) { ; if (result == WeaselJson_REJECT) { this->rejected = true; + } else if (result == WeaselJson_OVERFLOW) { + this->overflowed = true; } return WeaselJsonStatus(result); #endif diff --git a/src/test.cpp b/src/test.cpp index d13d353..6fef3a7 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -202,6 +202,62 @@ TEST_CASE("parser3") { } } +TEST_CASE("overflow state is sticky") { + auto c = noopCallbacks(); + // stackSize 3 is exactly big enough to hold reset()'s bootstrap, but too + // small for nested arrays. Overflows must be terminal like rejects: a later + // end-of-data call must never report OK for an incomplete document. + auto *parser = WeaselJsonParser_create(3, &c, nullptr, 0); + REQUIRE(parser != nullptr); + + std::string doc = "[["; + REQUIRE(WeaselJsonParser_parse(parser, doc.data(), doc.size()) == + WeaselJson_OVERFLOW); + + // After overflow, the end-of-data call must not return OK. + REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) != WeaselJson_OK); + // It should keep reporting a terminal failure. + REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) != WeaselJson_OK); + + // Further data chunks must also stay terminal. + std::string more = "]]"; + REQUIRE(WeaselJsonParser_parse(parser, more.data(), more.size()) != + WeaselJson_OK); + + WeaselJsonParser_destroy(parser); +} + +TEST_CASE("overflow is sticky for nested objects") { + auto c = noopCallbacks(); + auto *parser = WeaselJsonParser_create(4, &c, nullptr, 0); + REQUIRE(parser != nullptr); + + std::string doc = "{\"a\":{ \"a\":"; + REQUIRE(WeaselJsonParser_parse(parser, doc.data(), doc.size()) == + WeaselJson_OVERFLOW); + REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) != WeaselJson_OK); + + WeaselJsonParser_destroy(parser); +} + +TEST_CASE("reset clears overflow state") { + auto c = noopCallbacks(); + auto *parser = WeaselJsonParser_create(3, &c, nullptr, 0); + REQUIRE(parser != nullptr); + + std::string doc = "[["; + REQUIRE(WeaselJsonParser_parse(parser, doc.data(), doc.size()) == + WeaselJson_OVERFLOW); + // After reset the parser should accept a minimal document again. + WeaselJsonParser_reset(parser); + std::string copy = "1"; + REQUIRE(WeaselJsonParser_parse(parser, copy.data(), copy.size()) == + WeaselJson_AGAIN); + REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) == WeaselJson_OK); + + WeaselJsonParser_destroy(parser); +} + TEST_CASE("rejected state is sticky") { auto c = noopCallbacks(); auto *parser = WeaselJsonParser_create(1024, &c, nullptr, 0);