Fix undefined behavior on empty input in strinc() and prefixRange() #60

Merged
andrew merged 1 commits from weaselbot/conflict-set:weaselbot/issue-59 into main 2026-06-29 18:26:05 +00:00
Member

Closes #59

strinc() in ConflictSet.cpp used std::string_view::size() (size_t) and subtracted 1 without first checking for an empty string. For the root node, getSearchPath() returns the empty string, so every debug correctness check underflowed size_t and relied on implementation-defined conversion to int.

prefixRange() in Bench.cpp had the same loop shape. Although TrivialSpan::size() returns int, on an empty (or all-\xff) key the function asserted and then continued executing, allocating a zero-length buffer and writing before its start.

Changes:

  • In strinc(), initialize index as signed int(str.size()) - 1 so the loop is skipped for empty input, and return ok = false cleanly.
  • In prefixRange(), initialize index the same way and call std::abort() after the assert so invalid input cannot fall through to an out-of-bounds write.
  • Replace C-style uint8_t casts with explicit static_casts.
Closes #59 `strinc()` in `ConflictSet.cpp` used `std::string_view::size()` (`size_t`) and subtracted `1` without first checking for an empty string. For the root node, `getSearchPath()` returns the empty string, so every debug correctness check underflowed `size_t` and relied on implementation-defined conversion to `int`. `prefixRange()` in `Bench.cpp` had the same loop shape. Although `TrivialSpan::size()` returns `int`, on an empty (or all-`\xff`) key the function asserted and then continued executing, allocating a zero-length buffer and writing before its start. Changes: - In `strinc()`, initialize `index` as signed `int(str.size()) - 1` so the loop is skipped for empty input, and return `ok = false` cleanly. - In `prefixRange()`, initialize `index` the same way and call `std::abort()` after the assert so invalid input cannot fall through to an out-of-bounds write. - Replace C-style `uint8_t` casts with explicit `static_cast`s.
weaselbot added 1 commit 2026-06-29 17:52:12 +00:00
Fix undefined behavior on empty input in strinc() and prefixRange()
CI / pre-commit (pull_request) Successful in 2m5s
CI / release (arm64, ubuntu-latest-arm64) (pull_request) Successful in 3m29s
CI / test (-DCMAKE_BUILD_TYPE=Debug, debug) (pull_request) Successful in 3m34s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (pull_request) Successful in 3m28s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (pull_request) Successful in 3m38s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (pull_request) Successful in 3m27s
CI / release (amd64, ubuntu-latest-amd64) (pull_request) Successful in 4m51s
CI / coverage (pull_request) Successful in 3m37s
63f9a139da
strinc() in ConflictSet.cpp used std::string_view::size() (size_t) and subtracted 1 without first checking for an empty string. For the root node, getSearchPath() returns the empty string, so every debug correctness check underflowed size_t and relied on implementation-defined conversion to signed int.

prefixRange() in Bench.cpp had the same loop shape. Although TrivialSpan::size() returns int, on an empty (or all-0xff) key the function then asserted and continued executing, allocating a zero-length buffer and writing before its start.

Changes:
- In strinc(), initialize index as signed int(str.size()) - 1 so the loop is skipped for empty input, and return ok=false cleanly.
- In prefixRange(), initialize index the same way and call std::abort() after the assert so invalid input cannot fall through to an out-of-bounds write.
- Replace C-style uint8_t casts with explicit static_casts.

Closes #59
andrew merged commit 9449190d02 into main 2026-06-29 18:26:05 +00:00
andrew deleted branch weaselbot/issue-59 2026-06-29 18:26:06 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/conflict-set#60