EOF flush for scalars ending at chunk boundary subtracts null pointers #40

Closed
opened 2026-06-29 00:11:37 +00:00 by weaselbot · 1 comment
Member

When a scalar value (number, and theoretically string) ends exactly at a chunk boundary, the parser flushes it with done=false in that chunk. The next call with len==0 (EOF) then issues a final done=true flush to signal completion. At EOF, Parser3::parse sets this->dataBegin = this->writeBuf = buf, and because buf is null for an EOF call, both pointers become null. The final flush computes len = buf - dataBegin, i.e. nullptr - nullptr, which is undefined behavior in C++.

Affected code:

  • src/parser3.h Parser3::parse (around line 1030) always resets the write window at the start of every call:

    inline WeaselJsonStatus Parser3::parse(char *buf, int len) {
      this->dataBegin = this->writeBuf = buf;
      ...
    
  • src/parser3.h Parser3::flushNumber and Parser3::flushString compute len by subtracting dataBegin from buf:

    void flushNumber(bool done, char *buf) {
      int len = buf - dataBegin;
      ...
      callbacks->on_number_data(userdata, dataBegin, len, done);
    }
    
    void flushString(bool done, char *buf) {
      int len;
      if (!(flags & WeaselJsonRaw)) {
        len = writeBuf - dataBegin;
      } else {
        len = buf - dataBegin;
      }
      ...
      callbacks->on_string_data(userdata, dataBegin, len, done);
    }
    

Concrete reproduction for numbers:

auto c = noopCallbacks();
auto *parser = WeaselJsonParser_create(1024, &c, nullptr, 0);
std::string chunk = "123";
WeaselJsonParser_parse(parser, chunk.data(), chunk.size()); // flushes "123" with done=false, returns AGAIN
WeaselJsonParser_parse(parser, nullptr, 0);                 // EOF: flushNumber(true, nullptr) computes nullptr - nullptr
WeaselJsonParser_destroy(parser);

The same pointer subtraction is reached for strings if a string's content is flushed at a chunk boundary and the subsequent EOF path somehow reaches flushString, although the most common trigger is the number path shown above.

Expected behavior: the parser should not invoke undefined behavior when finalizing a scalar at EOF. It can either skip the final zero-length flush when dataBegin is null, or explicitly compute len = 0 without pointer subtraction.

When a scalar value (number, and theoretically string) ends exactly at a chunk boundary, the parser flushes it with `done=false` in that chunk. The next call with `len==0` (EOF) then issues a final `done=true` flush to signal completion. At EOF, `Parser3::parse` sets `this->dataBegin = this->writeBuf = buf`, and because `buf` is null for an EOF call, both pointers become null. The final flush computes `len = buf - dataBegin`, i.e. `nullptr - nullptr`, which is undefined behavior in C++. Affected code: - `src/parser3.h` `Parser3::parse` (around line 1030) always resets the write window at the start of every call: ```cpp inline WeaselJsonStatus Parser3::parse(char *buf, int len) { this->dataBegin = this->writeBuf = buf; ... ``` - `src/parser3.h` `Parser3::flushNumber` and `Parser3::flushString` compute `len` by subtracting `dataBegin` from `buf`: ```cpp void flushNumber(bool done, char *buf) { int len = buf - dataBegin; ... callbacks->on_number_data(userdata, dataBegin, len, done); } void flushString(bool done, char *buf) { int len; if (!(flags & WeaselJsonRaw)) { len = writeBuf - dataBegin; } else { len = buf - dataBegin; } ... callbacks->on_string_data(userdata, dataBegin, len, done); } ``` Concrete reproduction for numbers: ```cpp auto c = noopCallbacks(); auto *parser = WeaselJsonParser_create(1024, &c, nullptr, 0); std::string chunk = "123"; WeaselJsonParser_parse(parser, chunk.data(), chunk.size()); // flushes "123" with done=false, returns AGAIN WeaselJsonParser_parse(parser, nullptr, 0); // EOF: flushNumber(true, nullptr) computes nullptr - nullptr WeaselJsonParser_destroy(parser); ``` The same pointer subtraction is reached for strings if a string's content is flushed at a chunk boundary and the subsequent EOF path somehow reaches `flushString`, although the most common trigger is the number path shown above. Expected behavior: the parser should not invoke undefined behavior when finalizing a scalar at EOF. It can either skip the final zero-length flush when `dataBegin` is null, or explicitly compute `len = 0` without pointer subtraction.
weaselbot was assigned by andrew 2026-06-29 17:09:49 +00:00
Owner

It's not actually UB

It's not actually UB
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/weaseljson#40