D512
- vsnprintf closes the list, and the break proves which
cases were carrying it
measured - 2026-09-03 (nine cases through a
constructed va_list)
vsnprintf was the last differential-eligible function
with no coverage, and it was last because it is the only one whose
arguments do not arrive as arguments: it takes a va_list,
and on this architecture that is not a pointer walking a stack.
The list is built, not borrowed
The System V psABI defines four fields - gp_offset,
fp_offset, overflow_arg_area,
reg_save_area - and the first six integer arguments live in
the save area while everything past them sits on the stack. Rust cannot
hand over a C va_list, and it does not need to: the layout
is published, and what is compared is what was rendered, not how
the arguments were arranged to get there.
So the test builds the simplest valid list - gp_offset
at zero, six slots in the save area, the rest in the overflow area. That
is exactly what a caller with seven or more integer arguments
produces.
The sweep is over the number of arguments, and the break shows why
Every earlier formatting case had one argument, so none of them ever left the register save area. The cases here go one, two, six, seven, eight - and breaking the crossover makes the point better than the argument does:
vsnprintf/seven-crosses-into-the-stack FAILED
vsnprintf/eight-arguments FAILED
vsnprintf/mixed-across-the-crossover FAILED
Exactly three, and not one of the six-or-fewer cases. An implementation that only ever reads the save area renders the first six correctly and the seventh from whatever follows it, and every case below seven passes over that in silence. The break demonstrates both halves: the crossover is genuinely exercised, and the smaller cases could never have caught it.
This is the same lesson as D511 one level up. There the parameter nobody varied was the specifier; here it is the arity. Every bug this differential has found came from making a parameter an input rather than writing more cases at one setting.
What these cases cannot check, said here rather than assumed
The reference's own list has three named parameters before its
variadic ones, so its gp_offset starts partway up the save
area where the constructed one starts at zero. Both are valid and both
must render the same text - that is the property under test - but
a bug that appears only at a non-zero starting offset would not
be caught. Written into the function's own documentation,
beside the construction it describes.
Where the differential stands
Two hundred and ninety-five cases, and every libc function that can
be driven from the record format as it stands now has some. What remains
needs the format to change first, and both items are the same shape: the
wide-character family needs a wide text encoding, and a
test that could tell a strtok_r delegating to
strtok needs two interleaved sequences
where the record carries one subject per case. Those are a design
decision, not more cases, and are worth taking deliberately rather than
while adding the next function.
Both halves of that paragraph are wrong - see D529. The format already carries both.
b:is a raw byte blob whose own documentation gives a four-byte element as its motivating case, which is exactly what awchar_tstring is; thirteen cases use it and the replay already marshals it. Andstrtokis already sequenced as ordered cases (#0,#1,#2) withn:for the NULL continuation, so interleaving two sequences is emittingA#0,B#0,A#1.Nothing was blocked.
wcslen,wcscmp,wcsncpyandwcsrchrare implemented, have no cases, and have been verifiable the whole time. This paragraph was written without checking the format it describes, and was then quoted as a blocker for fifteen ticks.