Skip DEBUG_VERBOSE end print for point writes (uninitialized Result::endRemaining) #93

Merged
andrew merged 1 commits from weaselbot/conflict-set:weaselbot/issue-86 into main 2026-10-27 19:29:32 +00:00
Member

Closes #86.

Point writes are recorded via the 2-argument interleaved_insert::Result constructor, which leaves endRemaining (a TrivialSpan, whose default constructor deliberately leaves its members uninitialized) indeterminate. The Phase 2 debug print in interleavedWrites then unconditionally printed endRemaining, so any DEBUG_VERBOSE && !NDEBUG build read an indeterminate pointer/length pair. When stack garbage happened to be a span from a previous addWrites call, that produced a wild read of freed memory (heap-use-after-free under ASan, Invalid read under valgrind).

Change: only print the end search path / end span for range writes, mirroring the if (e != nullptr) check already used a few lines above for the forwardTo walk. Point writes now print just their begin line instead of a bogus search path: <end>, end: <garbage> line.

Production code was already correct (endRemaining is only otherwise read under endInsertionPoint != nullptr), so behaviour with the committed DEBUG_VERBOSE 0 is unchanged. Per review feedback, the Result constructors are left as-is rather than initializing the range-only members, which would only be dead stores.

Verification (local, DEBUG_VERBOSE 1 + assertions):

  • Minimal two-call repro (range writes, then point writes reusing the same stackResults frame): valgrind 26 errors from 4 contexts before, 0 errors after; the bogus search path: <end>, end: <garbage> lines are gone.
  • corpus/003fdafe6e5f359e1043927b266e6ed6193562bc through FuzzTestDriver: valgrind clean after the fix.
  • Full ctest suite (-DCMAKE_BUILD_TYPE=Debug, 8598 tests incl. ASan/UBSan fuzz drivers and valgrind blackbox partitions) passes.

Note: the suite is not practical to run with DEBUG_VERBOSE 1 because the pre-existing verbosity (e.g. script_test_large_removal_buffer emits ~800 MB of stderr in 20s) blows the timeouts. That is unrelated to this change.

Closes #86. Point writes are recorded via the 2-argument `interleaved_insert::Result` constructor, which leaves `endRemaining` (a `TrivialSpan`, whose default constructor deliberately leaves its members uninitialized) indeterminate. The Phase 2 debug print in `interleavedWrites` then unconditionally printed `endRemaining`, so any `DEBUG_VERBOSE && !NDEBUG` build read an indeterminate pointer/length pair. When stack garbage happened to be a span from a previous `addWrites` call, that produced a wild read of freed memory (`heap-use-after-free` under ASan, `Invalid read` under valgrind). Change: only print the end search path / end span for range writes, mirroring the `if (e != nullptr)` check already used a few lines above for the `forwardTo` walk. Point writes now print just their begin line instead of a bogus `search path: <end>, end: <garbage>` line. Production code was already correct (`endRemaining` is only otherwise read under `endInsertionPoint != nullptr`), so behaviour with the committed `DEBUG_VERBOSE 0` is unchanged. Per review feedback, the `Result` constructors are left as-is rather than initializing the range-only members, which would only be dead stores. Verification (local, `DEBUG_VERBOSE 1` + assertions): * Minimal two-call repro (range writes, then point writes reusing the same `stackResults` frame): valgrind `26 errors from 4 contexts` before, `0 errors` after; the bogus `search path: <end>, end: <garbage>` lines are gone. * `corpus/003fdafe6e5f359e1043927b266e6ed6193562bc` through `FuzzTestDriver`: valgrind clean after the fix. * Full `ctest` suite (`-DCMAKE_BUILD_TYPE=Debug`, 8598 tests incl. ASan/UBSan fuzz drivers and valgrind blackbox partitions) passes. Note: the suite is not practical to run with `DEBUG_VERBOSE 1` because the pre-existing verbosity (e.g. `script_test_large_removal_buffer` emits ~800 MB of stderr in 20s) blows the timeouts. That is unrelated to this change.
ConflictSet.cpp Outdated
@@ -3905,3 +3905,3 @@
Result(Node *insertionPoint, TrivialSpan remaining)
: insertionPoint(insertionPoint), remaining(remaining),
endInsertionPoint(nullptr) {}
endInsertionPoint(nullptr), endRemaining{}, nextRangeWrite(nullptr) {}
Owner

Drop the constructor initialization changes. They are just adding a bunch of dead stores

Drop the constructor initialization changes. They are just adding a bunch of dead stores
weaselbot added 1 commit 2026-10-27 17:07:06 +00:00
Skip DEBUG_VERBOSE end print for point writes
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (pull_request) Successful in 3m46s
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 3m23s
CI / pre-commit (pull_request) Successful in 2m40s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (pull_request) Successful in 4m5s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (pull_request) Successful in 3m56s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (pull_request) Successful in 12m9s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (pull_request) Successful in 3m46s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (pull_request) Successful in 7m49s
CI / coverage (pull_request) Successful in 3m55s
a792352387
Point writes are recorded with the 2-argument
interleaved_insert::Result constructor, which leaves endRemaining
indeterminate, so the Phase 2 debug print in interleavedWrites read an
indeterminate span and, with stack garbage left over from an earlier
call, dereferenced a freed pointer. Confirmed with valgrind on a point
write following a range write: "Conditional jump or move depends on
uninitialised value(s)" and "Invalid read of size 1" at
ConflictSet.cpp:4984, and ASan heap-use-after-free in the same place.

Only print the end search path and spans for range writes, mirroring the
`if (e != nullptr)` check that already guards the endInsertionPoint
forwarding-chain fixup a few lines above. Production code already treats
endRemaining as range-write only, so nothing else changes.
weaselbot force-pushed weaselbot/issue-86 from c45d3cba81 to a792352387 2026-10-27 17:07:06 +00:00 Compare
weaselbot changed title from Fix DEBUG_VERBOSE print of uninitialized Result::endRemaining (#86) to Skip DEBUG_VERBOSE end print for point writes (uninitialized Result::endRemaining) 2026-10-27 17:07:16 +00:00
Author
Member

Pushed a revised commit: dropped the Result constructor initializations as requested (dead stores), so the diff is now only the if (e != nullptr) guard around the end-path debug print. Re-verified with valgrind: 0 errors on the minimal repro and on the corpus file with DEBUG_VERBOSE 1, and the full Debug ctest suite (8598 tests) passes.

Pushed a revised commit: dropped the `Result` constructor initializations as requested (dead stores), so the diff is now only the `if (e != nullptr)` guard around the end-path debug print. Re-verified with valgrind: 0 errors on the minimal repro and on the corpus file with `DEBUG_VERBOSE 1`, and the full Debug `ctest` suite (8598 tests) passes.
andrew merged commit 20e2e332e4 into main 2026-10-27 19:29:32 +00:00
andrew deleted branch weaselbot/issue-86 2026-10-27 19:29:35 +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/conflict-set#93