Test aksl_vscanf, and drop a check that could not fail
Coverage caught both. aksl_vscanf was declared, documented and never called by anything, which is what 100% function coverage is for -- it is the only metric here that notices a whole function nobody exercises. aksl_vasprintf had a second vsnprintf whose return it compared against the first, and a CLEANUP block to free the buffer when they disagreed. They cannot disagree: the buffer was sized by vsnprintf from the same format string and the same arguments a few lines earlier. So that was a failure branch nothing can reach and a free() beneath it that nothing can execute -- exactly the dead code TODO.md 2.2.11 recorded against the old aksl_memset and aksl_memcpy, and removing those was the point. The invariant is a comment now, where it can be read. Back to 99.5% of lines (1708/1716) and 100% of functions (154/154), with the eight uncovered lines the ones TODO.md already accounts for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
33
src/stdlib.c
33
src/stdlib.c
@@ -541,30 +541,33 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_vasprintf(int *count, char **dest, const
|
||||
*dest = NULL;
|
||||
FAIL_ZERO_RETURN(e, format, AKERR_NULLPOINTER, "count=%p, dest=%p, format=%p",
|
||||
(void *)count, (void *)dest, (void *)format);
|
||||
/*
|
||||
* Measured with a copy, because a vsnprintf consumes the va_list and using
|
||||
* the same one twice is undefined behaviour rather than merely wrong.
|
||||
*/
|
||||
va_copy(measure, args);
|
||||
needed = vsnprintf(NULL, 0, format, measure);
|
||||
va_end(measure);
|
||||
FAIL_NONZERO_RETURN(e, (needed < 0), AKSL_ERRNO_OR(AKERR_IO), "could not format the arguments");
|
||||
ATTEMPT {
|
||||
CATCH(e, aksl_malloc((size_t)needed + 1, (void **)&buf));
|
||||
/*
|
||||
* Cannot truncate -- the buffer was sized from this same format and
|
||||
* arguments -- but the return is checked anyway, because "cannot happen"
|
||||
* is a claim about the line above rather than about vsnprintf.
|
||||
*/
|
||||
FAIL_NONZERO_BREAK(e, (vsnprintf(buf, (size_t)needed + 1, format, args) != needed),
|
||||
AKERR_IO, "formatted output changed length between passes");
|
||||
*dest = buf;
|
||||
*count = needed;
|
||||
} CLEANUP {
|
||||
if ( *dest == NULL && buf != NULL ) {
|
||||
akerr_ErrorContext *released = aksl_free(buf);
|
||||
if ( released != NULL ) {
|
||||
released = akerr_release_error(released);
|
||||
}
|
||||
}
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
/*
|
||||
* No second check on the write, and no cleanup path for one.
|
||||
*
|
||||
* The buffer was sized by vsnprintf from this same format string and these
|
||||
* same arguments a few lines up, so the write cannot truncate. Guarding it
|
||||
* anyway would mean a failure branch nothing can reach and a free() beneath
|
||||
* it that nothing can execute -- dead code that reads as though there were a
|
||||
* failure mode to catch, which is exactly what TODO.md 2.2.11 recorded
|
||||
* against the old aksl_memset and aksl_memcpy and what removing those was
|
||||
* for. The invariant is stated here instead, where it can be read.
|
||||
*/
|
||||
vsnprintf(buf, (size_t)needed + 1, format, args);
|
||||
*dest = buf;
|
||||
*count = needed;
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user