Skip to content

Make the test element type notice self move assignment - #73

Merged
martinus merged 1 commit into
mainfrom
fix-67-fuzz-observable-self-move
Jul 27, 2026
Merged

Make the test element type notice self move assignment#73
martinus merged 1 commit into
mainfrom
fix-67-fuzz-observable-self-move

Conversation

@martinus

Copy link
Copy Markdown
Owner

Fixes #67.

Counter::Obj::operator=(Obj&&) is a plain field copy, so moving an object onto itself preserves its value. That is why #65 and #66 survived: both were pure data corruption, test/fuzz/api.cpp generates exactly the inputs that trigger them, and it compares against std::vector after every operation — and still saw nothing.

Proof it works

With both fixes reverted:

--- Counter::Obj insert test (previously passed with the bug live) ---
ERROR at test/app/Counter.cpp(151): operator= self move assignment
test/unit/insert.cpp:112: FATAL ERROR: test case CRASHED: SIGABRT

--- fuzz corpus ---
1/1651  ERROR at test/app/Counter.cpp(151): operator= self move assignment

insert_input_iterator is a pre-existing test that passed while the bug was live. The fuzz corpus fails on its first of 1651 entries.

With the fixes in place the whole suite is clean, so no legitimate operation in the library self-move-assigns.

Why this and not a std::string fuzz lane

The issue suggested either. Detecting it in the element type turns out to be strictly stronger:

  • A std::string lane depends on implementation-defined behaviour. [container.reqmts] only promises "valid but unspecified" after a self-move; libstdc++ happening to leave the string empty is not a guarantee. A future stdlib or a different SSO threshold could silently restore the blind spot.
  • It only catches the bug if the damaged element survives to a comparison point. Several repeat_oneof branches tear down and rebuild the containers with assertEq commented out.
  • The element-type check fires at the point of the violation, regardless of whether the value happened to change or anyone looked afterwards.

It is also a 4-line change rather than duplicating the whole harness, every branch of which is wired to Counter::Obj bookkeeping (c.set(counts), counts.check_all_done()) with no std::string analogue.

Two deliberate deviations from the file's convention

The other invariant checks here sit inside #if COUNTER_ENABLE_UNORDERED_SET. This one does not: that macro guards two hash-set lookups per call, while this is a single pointer comparison worth having unconditionally. Counter::Obj is not used by any benchmark, so nothing measured is affected.

The message carries "self move assignment" rather than the bare __func__ the other sites use, because __func__ is operator= for both the copy and move overloads.

Known remaining gap

This catches self-move only. A double-move (same source moved into two destinations) would still pass, because Obj's move leaves the source untouched, so the duplicate looks valid. Marking the source moved-from and rejecting a second move would cover it — worth a follow-up, not done here.

Counter::Obj's move assignment is a plain field copy, so moving an object onto
itself preserves its value. That made #65 and #66 invisible: both were pure
data corruption, the fuzz harness generates exactly the inputs that trigger
them and compares against std::vector after every operation, and it still saw
nothing wrong.

Abort instead. A container moving an element onto itself means it passed an
overlapping range to std::move or std::move_backward, which neither allows, so
there is no legitimate case to preserve. Writing x = std::move(x) by hand is
fine and this check would be wrong on a general purpose type, but Obj exists to
be put into containers.

With the two fixes reverted, insert_input_iterator now aborts where it used to
pass, and the fuzz corpus fails on its first of 1651 entries.

Not gated on COUNTER_ENABLE_UNORDERED_SET like the other invariant checks here:
that macro guards two hash lookups per call, this is one pointer comparison and
is worth having unconditionally.

Closes #67

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@martinus
martinus merged commit 3971df8 into main Jul 27, 2026
6 checks passed
@martinus
martinus deleted the fix-67-fuzz-observable-self-move branch July 27, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fuzz harness cannot observe self-move-assignment, which hid two data corruption bugs

1 participant