From bd53e57b8e862def71b2558d4e35f106de41217f Mon Sep 17 00:00:00 2001 From: Weaselbot Date: Mon, 29 Jun 2026 13:57:58 -0400 Subject: [PATCH] Avoid nullptr subtraction when flushing scalars at EOF When a scalar ends exactly at a chunk boundary, Parser3::parse resets dataBegin and writeBuf to the new buf at the start of every call. On the EOF call buf is null, so both pointers become null. The final flushNumber/flushString then computed len as buf - dataBegin, i.e. nullptr - nullptr, which is undefined behaviour in C++. Compute the flush length safely: if dataBegin is null (or, for raw mode, buf is null), treat the length as zero. Use an empty string literal as a non-null data pointer for the zero-length, done=true callback so callers still receive the completion signal. Add a regression test covering a number that fills its chunk exactly and is finalized by an EOF call. Fixes #40 --- src/parser3.h | 35 +++++++++++++++++++++-------------- src/test.cpp | 18 ++++++++++++++++++ 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/parser3.h b/src/parser3.h index 639a05f..91f7b70 100644 --- a/src/parser3.h +++ b/src/parser3.h @@ -83,26 +83,33 @@ struct Parser3 { [[nodiscard]] WeaselJsonStatus parse(char *buf, int len); void flushNumber(bool done, char *buf) { - int len = buf - dataBegin; - assert(len >= 0); - if (done || len > 0) { - callbacks->on_number_data(userdata, dataBegin, len, done); - } - } - - void flushString(bool done, char *buf) { - int len; - if (!(flags & WeaselJsonRaw)) { - len = writeBuf - dataBegin; - } else { + int len = 0; + if (dataBegin != nullptr && buf != nullptr) { len = buf - dataBegin; } assert(len >= 0); if (done || len > 0) { + callbacks->on_number_data(userdata, dataBegin ? dataBegin : "", len, + done); + } + } + + void flushString(bool done, char *buf) { + int len = 0; + if (dataBegin != nullptr) { + if (!(flags & WeaselJsonRaw)) { + len = writeBuf - dataBegin; + } else if (buf != nullptr) { + len = buf - dataBegin; + } + } + assert(len >= 0); + if (done || len > 0) { + const char *data = dataBegin ? dataBegin : ""; if (inKey) { - callbacks->on_key_data(userdata, dataBegin, len, done); + callbacks->on_key_data(userdata, data, len, done); } else { - callbacks->on_string_data(userdata, dataBegin, len, done); + callbacks->on_string_data(userdata, data, len, done); } } } diff --git a/src/test.cpp b/src/test.cpp index eec6669..792cafa 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -303,6 +303,24 @@ TEST_CASE("reset clears inKey and transient state") { WeaselJsonParser_destroy(parser); } +TEST_CASE("scalar ending at chunk boundary is finalized at EOF") { + // A number whose digits exactly fill the first chunk must not invoke + // undefined behaviour on the EOF call, and must still signal completion. + auto c = serializeCallbacks(); + SerializeState state; + auto *parser = WeaselJsonParser_create(1024, &c, &state, 0); + REQUIRE(parser != nullptr); + + std::string chunk = "123"; + REQUIRE(WeaselJsonParser_parse(parser, chunk.data(), chunk.size()) == + WeaselJson_AGAIN); + REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) == WeaselJson_OK); + + CHECK(state.result == "(123)"); + + WeaselJsonParser_destroy(parser); +} + void doTestUnescapingUtf8(std::string const &escaped, std::string const &expected, int stride, int flags) { CAPTURE(escaped);