2026-09-04
- (/loop) strdup covered, and a missing terminator is only
caught by luck
differential 400 -> 408 cases
suites 125 tests 2013 clippy/fmt/identity clean
Twenty-first cron tick. strdup/strndup were
the last genuinely differentiable pair from the census. The more useful
half of this tick is what the break said about how well they
are covered.
The pointer is not compared; the contents are
An address is a fact about one process. What crosses is found-or-not,
then the copy through its terminator - the half of
strndup that is easy to lose. The discriminating case needs
a source longer than the bound, because a
strncpy-shaped implementation is correct whenever the
source fits.
Then the break did not behave
Removing the terminator and running three times:
run 1 strdup/embedded-high-byte strndup/bound-longer-than-source strndup/truncates
run 2 strdup/embedded-high-byte strdup/plain strndup/bound-longer-than-source
run 3 strdup/embedded-high-byte strdup/plain strndup/bound-longer-than-source strndup/truncates
Three, three and four - different sets. The byte after an unterminated copy is whatever the allocator last left there, and it is a zero often enough that the read finds a terminator nobody wrote.
So the claim was narrowed
The doc comment claimed the unterminated marker made
this deterministic. It does not. These cases verify the contents
of a copy that is terminated; termination itself is caught
probabilistically, and the comment now says so.
A single run showed four failures and would have been written up as "the cases catch it" - the confident wrong answer, inside a decision about a test.
Catching it properly needs the allocation poisoned before the call,
which is the reference's technique for buffers it owns and is
not available to a caller of strdup. Recorded as a
limit rather than worked around.
The rule this sharpens
"A guard is not finished until somebody has made it fail" has an unstated second half: a break that fires is not the same as a break that fires reliably. Where the failure depends on memory nobody wrote, run it more than once.
Decision: D535.