Preserve required snprintf length on truncation
All checks were successful
libakstdlib CI Build / cmake_build (push) Successful in 2m55s
libakstdlib CI Build / sanitizers (push) Successful in 2m57s
libakstdlib CI Build / coverage (push) Successful in 2m46s
libakstdlib CI Build / mutation_test (push) Successful in 18m29s

This commit is contained in:
2026-08-03 18:25:43 -04:00
parent 0620370dd9
commit acb47a0d56
7 changed files with 64 additions and 40 deletions

View File

@@ -16,10 +16,11 @@
int main(void)
{
char buf[64];
int count = 0;
akerr_ErrorContext *raised = NULL;
/* %d against a string. This is the line that must not build. */
raised = aksl_snprintf(buf, sizeof(buf), "%d", "not an int");
raised = aksl_snprintf(&count, buf, sizeof(buf), "%d", "not an int");
return raised == NULL ? 0 : 1;
}

View File

@@ -40,9 +40,11 @@ static long read_file(const char *path, char *buf, size_t n)
static int test_snprintf_writes_text(void)
{
char buf[64];
int count = -1;
memset(buf, 0x00, sizeof(buf));
AKSL_CHECK_OK(aksl_snprintf(buf, sizeof(buf), "%s=%d", "x", 7));
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "%s=%d", "x", 7));
AKSL_CHECK(count == 3);
AKSL_CHECK(strcmp(buf, "x=7") == 0);
return 0;
}
@@ -50,8 +52,10 @@ static int test_snprintf_writes_text(void)
static int test_snprintf_empty_format_writes_nothing(void)
{
char buf[8] = { 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z' };
int count = -1;
AKSL_CHECK_OK(aksl_snprintf(buf, sizeof(buf), "%s", ""));
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "%s", ""));
AKSL_CHECK(count == 0);
AKSL_CHECK(buf[0] == '\0');
return 0;
}
@@ -65,11 +69,13 @@ static int test_snprintf_empty_format_writes_nothing(void)
static int test_snprintf_truncation_is_an_error(void)
{
char buf[8];
int count = -1;
memset(buf, 0x00, sizeof(buf));
AKSL_CHECK_STATUS_MSG_CONTAINS(
aksl_snprintf(buf, sizeof(buf), "%s", "far too long for eight bytes"),
aksl_snprintf(&count, buf, sizeof(buf), "%s", "far too long for eight bytes"),
AKERR_OUTOFBOUNDS, "truncated");
AKSL_CHECK(count == 28);
AKSL_CHECK(strcmp(buf, "far too") == 0);
return 0;
}
@@ -78,27 +84,31 @@ static int test_snprintf_truncation_is_an_error(void)
static int test_snprintf_boundary_is_exact(void)
{
char buf[8];
int count = -1;
/* 7 characters plus the NUL is exactly sizeof(buf). */
AKSL_CHECK_OK(aksl_snprintf(buf, sizeof(buf), "%s", "1234567"));
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "%s", "1234567"));
AKSL_CHECK(count == 7);
AKSL_CHECK(strcmp(buf, "1234567") == 0);
AKSL_CHECK_STATUS(aksl_snprintf(buf, sizeof(buf), "%s", "12345678"),
AKSL_CHECK_STATUS(aksl_snprintf(&count, buf, sizeof(buf), "%s", "12345678"),
AKERR_OUTOFBOUNDS);
AKSL_CHECK(count == 8);
return 0;
}
static int test_snprintf_rejects_null_arguments_and_zero_size(void)
{
char buf[8];
int count = 0;
memset(buf, 0x00, sizeof(buf));
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(NULL, 8, "x"),
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, NULL, 8, "x"),
AKERR_NULLPOINTER, "str=");
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(buf, sizeof(buf), NULL),
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, buf, sizeof(buf), NULL),
AKERR_NULLPOINTER, "format=");
/* size 0 leaves no room even for the terminator, so there is nothing to do. */
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(buf, 0, "x"),
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, buf, 0, "x"),
AKERR_VALUE, "size=0");
return 0;
}
@@ -260,29 +270,32 @@ static int test_asprintf_allocates_to_fit(void)
* wanted them exposed so consumers can write their own variadic wrappers. This
* is a consumer doing exactly that.
*/
static akerr_ErrorContext AKERR_NOIGNORE *consumer_wrapper(char *buf, size_t n,
static akerr_ErrorContext AKERR_NOIGNORE *consumer_wrapper(int *count, char *buf, size_t n,
const char *fmt, ...)
{
va_list args;
akerr_ErrorContext *raised = NULL;
va_start(args, fmt);
raised = aksl_vsnprintf(buf, n, fmt, args);
raised = aksl_vsnprintf(count, buf, n, fmt, args);
va_end(args);
return raised;
}
static int test_va_list_forms_are_usable_from_outside(void)
{
char buf[32];
char buf[32];
int count = -1;
memset(buf, 0x00, sizeof(buf));
AKSL_CHECK_OK(consumer_wrapper(buf, sizeof(buf), "%s/%d", "via", 3));
AKSL_CHECK_OK(consumer_wrapper(&count, buf, sizeof(buf), "%s/%d", "via", 3));
AKSL_CHECK(count == 5);
AKSL_CHECK(strcmp(buf, "via/3") == 0);
/* The error contract survives the extra layer intact. */
AKSL_CHECK_STATUS(consumer_wrapper(buf, 4, "%s", "too long"),
AKERR_OUTOFBOUNDS);
AKSL_CHECK_STATUS(consumer_wrapper(&count, buf, 4, "%s", "too long"),
AKERR_OUTOFBOUNDS);
AKSL_CHECK(count == 8);
return 0;
}
@@ -294,13 +307,15 @@ static int test_va_list_forms_are_usable_from_outside(void)
*/
static int test_variadic_wrappers_survive_repeated_calls(void)
{
char buf[128];
char buf[128];
int count = 0;
int i = 0;
for ( i = 0; i < 512; i++ ) {
AKSL_CHECK_OK(aksl_snprintf(buf, sizeof(buf), "%d %s %ld %c %f",
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "%d %s %ld %c %f",
i, "iteration", (long)i, 'x', (double)i));
AKSL_CHECK(strlen(buf) > 0);
AKSL_CHECK(count > 0);
AKSL_CHECK((size_t)count == strlen(buf));
}
return 0;
}

View File

@@ -109,13 +109,14 @@ static int test_stream_wrappers_do_not_leak_slots(void)
static int test_format_wrappers_do_not_leak_slots(void)
{
char buf[16];
int count = 0;
int i = 0;
for ( i = 0; i < ROUNDS; i++ ) {
AKSL_CHECK_STATUS(aksl_printf(NULL, "x"), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fprintf(NULL, stdout, "x"), AKERR_NULLPOINTER);
AKSL_CHECK_OK(aksl_snprintf(buf, sizeof(buf), "x"));
AKSL_CHECK_STATUS(aksl_snprintf(buf, 4, "%s", "far too long"),
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "x"));
AKSL_CHECK_STATUS(aksl_snprintf(&count, buf, 4, "%s", "far too long"),
AKERR_OUTOFBOUNDS);
AKSL_CHECK(aksl_slots_in_use() == 0);
}
@@ -293,7 +294,7 @@ static int test_errors_name_their_origin_in_stdlib(void)
AKSL_CHECK_STATUS(aksl_printf(NULL, "x"), AKERR_NULLPOINTER);
AKSL_CHECK(came_from("aksl_vprintf", "src/stdlib.c") == 0);
AKSL_CHECK_STATUS(aksl_snprintf(NULL, 8, "x"), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_snprintf(NULL, NULL, 8, "x"), AKERR_NULLPOINTER);
AKSL_CHECK(came_from("aksl_vsnprintf", "src/stdlib.c") == 0);
/* Likewise, the ato* forms are calls into the strto* ones. */