2026-09-03
- (/loop) vsnprintf closes the list, and the break names
the cases carrying it
differential 286 -> 295 cases
Arrived by hand again - the eighteenth wakeup that did not fire.
The
last uncovered function needed a va_list built rather than
borrowed
vsnprintf was last because its arguments do not arrive
as arguments. The System V psABI puts the first six integer arguments in
a register save area and the rest on the stack, and orbistoun reads a
guest's four-field va_list exactly as that defines it.
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
with six slots and an overflow area, which is what a caller with seven
or more integer arguments produces.
The sweep is over argument count, and breaking it proved why
Every earlier formatting case had one argument, so none ever left the save area. These go one, two, six, seven, eight. Breaking the crossover:
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. That is both halves in one run: the crossover is genuinely exercised, and the smaller cases could never have caught it. An implementation reading only the save area renders six correctly and the seventh from whatever follows.
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.
What it cannot check, written in the function rather than assumed
The reference's 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 must render
the same text - but a bug appearing only at a non-zero starting offset
would not be caught. Said so beside the construction.
Where the differential stands
295 cases, and every libc function drivable from the
record format as it stands now has some. What remains needs the format
changed first, and both items are the same shape: the wide-character
family needs a wide text encoding, and telling a
strtok_r that delegates to strtok needs
two interleaved sequences where the record carries one
subject per case. D512 records that as a decision to take deliberately
rather than while adding a function.
State
cargo test --workspace green - 119 suites, 1998
tests, 0 failures. Differential 295 cases, all
agreeing. clippy --tests clean, fmt clean, identity scan
clean on both.
Nothing committed. The day holds worklogs 292-361 and D466-D512.
Next: the record-format decision, which is now the only thing standing between the differential and the wide-character family.