1 Commits

Author SHA1 Message Date
af5a4b861a Add directory stream wrappers
All checks were successful
libakstdlib CI Build / cmake_build (push) Successful in 2m56s
libakstdlib CI Build / sanitizers (push) Successful in 2m53s
libakstdlib CI Build / coverage (push) Successful in 2m44s
libakstdlib CI Build / mutation_test (push) Successful in 13m17s
2026-08-03 13:04:33 -04:00
8 changed files with 50 additions and 71 deletions

View File

@@ -36,7 +36,7 @@ that will surprise you:
| `aksl_malloc(0, &p)` | `AKERR_VALUE`. There is nothing useful to hand back, and `malloc(0)` returning NULL without setting `errno` is how an error with status `0` used to get raised. | | `aksl_malloc(0, &p)` | `AKERR_VALUE`. There is nothing useful to hand back, and `malloc(0)` returning NULL without setting `errno` is how an error with status `0` used to get raised. |
| `aksl_atoi` and friends | Report bad conversions. `atoi(3)` has no error channel at all: junk converts to `0` and overflow wraps. Base 10, whole string, `ERANGE` on overflow. | | `aksl_atoi` and friends | Report bad conversions. `atoi(3)` has no error channel at all: junk converts to `0` and overflow wraps. Base 10, whole string, `ERANGE` on overflow. |
| `aksl_strcpy` / `strncpy` / `strcat` / `strncat` | Take the destination's size, which the libc originals cannot be called safely without. Truncation is `AKERR_OUTOFBOUNDS` and writes nothing. `aksl_strncpy` always terminates and never NUL-pads. | | `aksl_strcpy` / `strncpy` / `strcat` / `strncat` | Take the destination's size, which the libc originals cannot be called safely without. Truncation is `AKERR_OUTOFBOUNDS` and writes nothing. `aksl_strncpy` always terminates and never NUL-pads. |
| `aksl_snprintf` | Truncation is `AKERR_OUTOFBOUNDS`, not a short success; `*count` receives the required length. There is no `aksl_sprintf`: an error-handling wrapper around an unbounded write is the sharp edge this library exists to remove. | | `aksl_snprintf` | Truncation is `AKERR_OUTOFBOUNDS`, not a short success. There is no `aksl_sprintf`: an error-handling wrapper around an unbounded write is the sharp edge this library exists to remove. |
| `aksl_memcpy` | Overlapping ranges are `AKERR_VALUE` rather than undefined behaviour. Use `aksl_memmove`. | | `aksl_memcpy` | Overlapping ranges are `AKERR_VALUE` rather than undefined behaviour. Use `aksl_memmove`. |
| `aksl_fread` / `aksl_fwrite` | Require a transferred-count out-param, and report a short transfer with no stream error as `AKERR_IO` rather than as success. | | `aksl_fread` / `aksl_fwrite` | Require a transferred-count out-param, and report a short transfer with no stream error as `AKERR_IO` rather than as success. |
| `aksl_sscanf` / `aksl_fscanf` | Take the number of conversions you expect. Comparing `scanf(3)`'s return against that by hand at every call site is the check everyone eventually forgets. | | `aksl_sscanf` / `aksl_fscanf` | Take the number of conversions you expect. Comparing `scanf(3)`'s return against that by hand at every call site is the check everyone eventually forgets. |

View File

@@ -86,7 +86,8 @@ aksl_sprintf(&count, buf, "%s=%d", key, value);
aksl_snprintf(&count, buf, sizeof(buf), "%s=%d", key, value); aksl_snprintf(&count, buf, sizeof(buf), "%s=%d", key, value);
``` ```
Truncation is `AKERR_OUTOFBOUNDS` rather than a short success, and `*count` receives the required length. Truncation is `AKERR_OUTOFBOUNDS` rather than a short success, and `*count` is
`0` on any failure rather than `vsprintf`'s `-1`.
**`aksl_realpath` takes the destination's length.** **`aksl_realpath` takes the destination's length.**

View File

@@ -444,9 +444,9 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_memchr(const void *s, int c, size_t n, v
/* ====================================================================== */ /* ====================================================================== */
/** @name Formatted output /** @name Formatted output
* *
* Bounded output is checked for truncation and reports an error when the result * `*count` is the byte count written excluding the terminating NUL, and is 0 on
* does not fit. `*count` receives the number of bytes written, or the complete * every failure path -- never vsnprintf's -1, and never the length the output
* output length when truncation occurs. * *would* have been.
* *
* There is no aksl_sprintf. It wrapped vsprintf, which cannot be bounded, and an * There is no aksl_sprintf. It wrapped vsprintf, which cannot be bounded, and an
* error-handling wrapper around an unbounded write is the sharp edge this * error-handling wrapper around an unbounded write is the sharp edge this
@@ -457,7 +457,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_memchr(const void *s, int c, size_t n, v
/** /**
* @brief printf(3) to stdout. * @brief printf(3) to stdout.
* @param[out] count Bytes written. Required. * @param[out] count Bytes written; 0 on failure. Required.
* @param[in] format printf format string. Required. Checked at compile time. * @param[in] format printf format string. Required. Checked at compile time.
* @throws AKERR_NULLPOINTER If count or format is NULL. * @throws AKERR_NULLPOINTER If count or format is NULL.
* @throws AKERR_IO Or the errno the C library saw, if the write fails. * @throws AKERR_IO Or the errno the C library saw, if the write fails.
@@ -467,7 +467,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_printf(int *count, const char *restrict
/** /**
* @brief fprintf(3) to a stream. * @brief fprintf(3) to a stream.
* @param[out] count Bytes written. Required. * @param[out] count Bytes written; 0 on failure. Required.
* @param[in] stream Destination stream. Required. * @param[in] stream Destination stream. Required.
* @param[in] format printf format string. Required. Checked at compile time. * @param[in] format printf format string. Required. Checked at compile time.
* @throws AKERR_NULLPOINTER If any pointer is NULL. * @throws AKERR_NULLPOINTER If any pointer is NULL.
@@ -483,11 +483,11 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fprintf(int *count, FILE *restrict strea
* written, leaving the caller to notice by comparing that against the buffer * written, leaving the caller to notice by comparing that against the buffer
* size -- the check this library exists to stop people forgetting. * size -- the check this library exists to stop people forgetting.
* *
* @param[out] count Bytes written, or the required length on truncation. Required. * @param[out] count Bytes written excluding the NUL; 0 on failure. Required.
* @param[out] str Destination buffer. Required. * @param[out] str Destination buffer. Required.
* @param[in] size Size of `str` including the terminator. Must be non-zero. * @param[in] size Size of `str` including the terminator. Must be non-zero.
* @param[in] format printf format string. Required. Checked at compile time. * @param[in] format printf format string. Required. Checked at compile time.
* @throws AKERR_NULLPOINTER If str or format is NULL. * @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_VALUE If size is 0. * @throws AKERR_VALUE If size is 0.
* @throws AKERR_OUTOFBOUNDS If the output does not fit, naming both lengths. * @throws AKERR_OUTOFBOUNDS If the output does not fit, naming both lengths.
* @return NULL on success, an error context otherwise. * @return NULL on success, an error context otherwise.
@@ -551,9 +551,9 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_vfprintf(int *count, FILE *restrict stre
/** /**
* @brief vsnprintf(3) into a bounded buffer. The va_list form of aksl_snprintf. * @brief vsnprintf(3) into a bounded buffer. The va_list form of aksl_snprintf.
* @param[out] count Bytes written excluding the NUL; 0 on failure. Required.
* @param[out] str Destination buffer. Required. * @param[out] str Destination buffer. Required.
* @param[in] size Size of `str` including the terminator. Must be non-zero. * @param[in] size Size of `str` including the terminator. Must be non-zero.
* @param[out] count Bytes written, or the required length on truncation. Required.
* @param[in] format printf format string. Required. * @param[in] format printf format string. Required.
* @param[in] args Arguments. The caller owns it and must va_end it. * @param[in] args Arguments. The caller owns it and must va_end it.
* @throws AKERR_NULLPOINTER If any pointer is NULL. * @throws AKERR_NULLPOINTER If any pointer is NULL.

View File

@@ -1,20 +1,10 @@
/* /* POSIX directory-stream wrappers. */
* POSIX directory-stream wrappers.
*
* opendir(3), fdopendir(3), readdir(3), closedir(3), and rewinddir(3) expose
* three different failure conventions between them. These wrappers turn all
* three into error contexts and make end-of-directory an explicit AKERR_EOF.
*/
#include <akstdlib.h> #include <akstdlib.h>
#include <errno.h> #include <errno.h>
#include "aksl_internal.h" #include "aksl_internal.h"
/*
* opendir(3) returns NULL for failure. Clear *dest first so a failed open
* cannot leave the caller holding a stale directory stream.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_opendir(const char *pathname, DIR **dest) akerr_ErrorContext AKERR_NOIGNORE *aksl_opendir(const char *pathname, DIR **dest)
{ {
PREPARE_ERROR(e); PREPARE_ERROR(e);
@@ -29,10 +19,6 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_opendir(const char *pathname, DIR **dest
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
/*
* fdopendir(3) takes ownership of fd only when it succeeds. On success the
* matching aksl_closedir call closes both the stream and its descriptor.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopendir(int fd, DIR **dest) akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopendir(int fd, DIR **dest)
{ {
PREPARE_ERROR(e); PREPARE_ERROR(e);
@@ -44,10 +30,6 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopendir(int fd, DIR **dest)
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
/*
* readdir(3) owns and may reuse its returned storage. Copy the entry into the
* caller's destination, and use errno to distinguish failure from exhaustion.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_readdir(DIR *dirp, struct dirent *dest) akerr_ErrorContext AKERR_NOIGNORE *aksl_readdir(DIR *dirp, struct dirent *dest)
{ {
struct dirent *entry = NULL; struct dirent *entry = NULL;
@@ -68,7 +50,6 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_readdir(DIR *dirp, struct dirent *dest)
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
/* closedir(3) reports its failure directly and invalidates dirp on success. */
akerr_ErrorContext AKERR_NOIGNORE *aksl_closedir(DIR *dirp) akerr_ErrorContext AKERR_NOIGNORE *aksl_closedir(DIR *dirp)
{ {
PREPARE_ERROR(e); PREPARE_ERROR(e);
@@ -79,10 +60,6 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_closedir(DIR *dirp)
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
/*
* rewinddir(3) has no failure return. Preserve that contract after rejecting
* a NULL stream, which would otherwise be undefined behaviour.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_rewinddir(DIR *dirp) akerr_ErrorContext AKERR_NOIGNORE *aksl_rewinddir(DIR *dirp)
{ {
PREPARE_ERROR(e); PREPARE_ERROR(e);

View File

@@ -416,11 +416,12 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream)
* register-save state on some ABIs. akbasic's text sink ran this UB on every * register-save state on some ABIs. akbasic's text sink ran this UB on every
* line of program output without anything visibly misbehaving, which is * line of program output without anything visibly misbehaving, which is
* exactly what made it worth fixing before something did. * exactly what made it worth fixing before something did.
* - *count is written on every path. It used to be left holding vprintf's -1
* after a failure, so a caller who read the length rather than the status got
* a negative byte count out of a function that had already failed. It is now
* 0 whenever an error is raised.
* - errno is cleared before the call and read back through AKSL_ERRNO_OR, so a * - errno is cleared before the call and read back through AKSL_ERRNO_OR, so a
* failure can never be reported with a stale -- or with a zero -- status. * failure can never be reported with a stale -- or with a zero -- status.
* - The bounded form returns the complete required length through *count even
* when truncation raises AKERR_OUTOFBOUNDS; callers use the error context for
* failure details, not the count as a success indicator.
* *
* aksl_sprintf is gone. It wrapped vsprintf, which cannot be bounded, and an * aksl_sprintf is gone. It wrapped vsprintf, which cannot be bounded, and an
* error-handling wrapper around an unbounded write is precisely the sharp edge * error-handling wrapper around an unbounded write is precisely the sharp edge
@@ -484,11 +485,12 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fprintf(int *count, FILE *restrict strea
* *would* have written and silently drops the rest, which is the single most * *would* have written and silently drops the rest, which is the single most
* common way a bounded write goes wrong unnoticed; a caller who wanted to know * common way a bounded write goes wrong unnoticed; a caller who wanted to know
* would have had to compare the return against the buffer size by hand, which is * would have had to compare the return against the buffer size by hand, which is
* the check this library exists to stop people forgetting. The bounded wrapper * the check this library exists to stop people forgetting. *count is the number
* reports truncation instead, and hands the required length back through *count. * of bytes written excluding the terminating NUL, and is 0 on any failure.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *aksl_vsnprintf(int *count, char *restrict str, size_t size, const char *restrict format, va_list args) akerr_ErrorContext AKERR_NOIGNORE *aksl_vsnprintf(int *count, char *restrict str, size_t size, const char *restrict format, va_list args)
{ {
int needed = 0;
PREPARE_ERROR(e); PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, count, AKERR_NULLPOINTER, "count=%p, str=%p, format=%p", (void *)count, (void *)str, (void *)format); FAIL_ZERO_RETURN(e, count, AKERR_NULLPOINTER, "count=%p, str=%p, format=%p", (void *)count, (void *)str, (void *)format);
*count = 0; *count = 0;
@@ -496,10 +498,11 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_vsnprintf(int *count, char *restrict str
FAIL_ZERO_RETURN(e, format, AKERR_NULLPOINTER, "count=%p, str=%p, format=%p", (void *)count, (void *)str, (void *)format); FAIL_ZERO_RETURN(e, format, AKERR_NULLPOINTER, "count=%p, str=%p, format=%p", (void *)count, (void *)str, (void *)format);
FAIL_ZERO_RETURN(e, size, AKERR_VALUE, "size=0 leaves no room even for the terminating NUL"); FAIL_ZERO_RETURN(e, size, AKERR_VALUE, "size=0 leaves no room even for the terminating NUL");
errno = 0; errno = 0;
*count = vsnprintf(str, size, format, args); needed = vsnprintf(str, size, format, args);
FAIL_NONZERO_RETURN(e, (*count < 0), AKSL_ERRNO_OR(AKERR_IO), "Output error"); FAIL_NONZERO_RETURN(e, (needed < 0), AKSL_ERRNO_OR(AKERR_IO), "Output error");
FAIL_NONZERO_RETURN(e, ((size_t)*count >= size), AKERR_OUTOFBOUNDS, FAIL_NONZERO_RETURN(e, ((size_t)needed >= size), AKERR_OUTOFBOUNDS,
"output truncated: %d bytes needed, %zu available", *count, size); "output truncated: %d bytes needed, %zu available", needed, size);
*count = needed;
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }

View File

@@ -23,21 +23,14 @@ static int test_open_errors_and_nulls(void)
DIR *dirp = (DIR *)1; DIR *dirp = (DIR *)1;
struct dirent entry; struct dirent entry;
/* opendir propagates missing-path and non-directory failures. */
AKSL_CHECK_STATUS(aksl_opendir("/nonexistent/aksl/dir", &dirp), ENOENT); AKSL_CHECK_STATUS(aksl_opendir("/nonexistent/aksl/dir", &dirp), ENOENT);
AKSL_CHECK(dirp == NULL); AKSL_CHECK(dirp == NULL);
AKSL_CHECK(aksl_temp_file(file, sizeof(file)) == 0); AKSL_CHECK(aksl_temp_file(file, sizeof(file)) == 0);
AKSL_CHECK_STATUS(aksl_opendir(file, &dirp), ENOTDIR); AKSL_CHECK_STATUS(aksl_opendir(file, &dirp), ENOTDIR);
/* Every pointer required by the wrapped operation rejects NULL. */
AKSL_CHECK_STATUS(aksl_opendir(NULL, &dirp), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_opendir(NULL, &dirp), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_opendir(".", NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_opendir(".", NULL), AKERR_NULLPOINTER);
/* fdopendir propagates an invalid descriptor and validates its out-param. */
AKSL_CHECK_STATUS(aksl_fdopendir(-1, &dirp), EBADF); AKSL_CHECK_STATUS(aksl_fdopendir(-1, &dirp), EBADF);
AKSL_CHECK_STATUS(aksl_fdopendir(0, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_fdopendir(0, NULL), AKERR_NULLPOINTER);
/* The remaining wrappers reject NULL streams and destinations. */
AKSL_CHECK_STATUS(aksl_readdir(NULL, &entry), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_readdir(NULL, &entry), AKERR_NULLPOINTER);
AKSL_CHECK_OK(aksl_opendir(".", &dirp)); AKSL_CHECK_OK(aksl_opendir(".", &dirp));
AKSL_CHECK_STATUS(aksl_readdir(dirp, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_readdir(dirp, NULL), AKERR_NULLPOINTER);

View File

@@ -2,8 +2,10 @@
* Formatted-output wrappers: aksl_printf / aksl_fprintf / aksl_snprintf and * Formatted-output wrappers: aksl_printf / aksl_fprintf / aksl_snprintf and
* their va_list forms. * their va_list forms.
* *
* Formatted output, complete. Each happy path asserts the text that actually * Formatted output, complete. Each happy path asserts both halves of the
* landed somewhere, and every pointer argument is checked for its NULL guard. * contract -- the byte count handed back through *count and the text that
* actually landed somewhere -- and every pointer argument is checked for its
* NULL guard.
* *
* Readback goes through plain libc rather than aksl_fread so that a failure here * Readback goes through plain libc rather than aksl_fread so that a failure here
* points at the formatted-output wrapper under test and not at the stream * points at the formatted-output wrapper under test and not at the stream
@@ -37,7 +39,7 @@ static long read_file(const char *path, char *buf, size_t n)
return (long)got; return (long)got;
} }
static int test_snprintf_writes_text(void) static int test_snprintf_writes_text_and_count(void)
{ {
char buf[64]; char buf[64];
int count = -1; int count = -1;
@@ -64,7 +66,7 @@ static int test_snprintf_empty_format_writes_nothing(void)
* The case that could not be written while the wrapper was aksl_sprintf: output * The case that could not be written while the wrapper was aksl_sprintf: output
* longer than the destination. snprintf(3) would truncate, NUL-terminate and * longer than the destination. snprintf(3) would truncate, NUL-terminate and
* report the length it *would* have written, leaving the caller to notice; here * report the length it *would* have written, leaving the caller to notice; here
* it is an error. * it is an error, and *count is 0 rather than the would-have-been length.
*/ */
static int test_snprintf_truncation_is_an_error(void) static int test_snprintf_truncation_is_an_error(void)
{ {
@@ -75,8 +77,7 @@ static int test_snprintf_truncation_is_an_error(void)
AKSL_CHECK_STATUS_MSG_CONTAINS( AKSL_CHECK_STATUS_MSG_CONTAINS(
aksl_snprintf(&count, 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"); AKERR_OUTOFBOUNDS, "truncated");
AKSL_CHECK(count == 28); AKSL_CHECK(count == 0);
AKSL_CHECK(strcmp(buf, "far too") == 0);
return 0; return 0;
} }
@@ -91,9 +92,10 @@ static int test_snprintf_boundary_is_exact(void)
AKSL_CHECK(count == 7); AKSL_CHECK(count == 7);
AKSL_CHECK(strcmp(buf, "1234567") == 0); AKSL_CHECK(strcmp(buf, "1234567") == 0);
count = -1;
AKSL_CHECK_STATUS(aksl_snprintf(&count, buf, sizeof(buf), "%s", "12345678"), AKSL_CHECK_STATUS(aksl_snprintf(&count, buf, sizeof(buf), "%s", "12345678"),
AKERR_OUTOFBOUNDS); AKERR_OUTOFBOUNDS);
AKSL_CHECK(count == 8); AKSL_CHECK(count == 0);
return 0; return 0;
} }
@@ -103,12 +105,14 @@ static int test_snprintf_rejects_null_arguments_and_zero_size(void)
int count = 0; int count = 0;
memset(buf, 0x00, sizeof(buf)); memset(buf, 0x00, sizeof(buf));
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, NULL, 8, "x"), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(NULL, buf, sizeof(buf), "x"),
AKERR_NULLPOINTER, "count=");
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, NULL, 8, "x"),
AKERR_NULLPOINTER, "str="); AKERR_NULLPOINTER, "str=");
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, buf, sizeof(buf), NULL), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, buf, sizeof(buf), NULL),
AKERR_NULLPOINTER, "format="); AKERR_NULLPOINTER, "format=");
/* size 0 leaves no room even for the terminator, so there is nothing to do. */ /* size 0 leaves no room even for the terminator, so there is nothing to do. */
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, buf, 0, "x"), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, buf, 0, "x"),
AKERR_VALUE, "size=0"); AKERR_VALUE, "size=0");
return 0; return 0;
} }
@@ -277,25 +281,25 @@ static akerr_ErrorContext AKERR_NOIGNORE *consumer_wrapper(int *count, char *buf
akerr_ErrorContext *raised = NULL; akerr_ErrorContext *raised = NULL;
va_start(args, fmt); va_start(args, fmt);
raised = aksl_vsnprintf(count, buf, n, fmt, args); raised = aksl_vsnprintf(count, buf, n, fmt, args);
va_end(args); va_end(args);
return raised; return raised;
} }
static int test_va_list_forms_are_usable_from_outside(void) static int test_va_list_forms_are_usable_from_outside(void)
{ {
char buf[32]; char buf[32];
int count = -1; int count = -1;
memset(buf, 0x00, sizeof(buf)); memset(buf, 0x00, sizeof(buf));
AKSL_CHECK_OK(consumer_wrapper(&count, 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(count == 5);
AKSL_CHECK(strcmp(buf, "via/3") == 0); AKSL_CHECK(strcmp(buf, "via/3") == 0);
/* The error contract survives the extra layer intact. */ /* The error contract survives the extra layer intact. */
AKSL_CHECK_STATUS(consumer_wrapper(&count, buf, 4, "%s", "too long"), AKSL_CHECK_STATUS(consumer_wrapper(&count, buf, 4, "%s", "too long"),
AKERR_OUTOFBOUNDS); AKERR_OUTOFBOUNDS);
AKSL_CHECK(count == 8); AKSL_CHECK(count == 0);
return 0; return 0;
} }
@@ -307,7 +311,7 @@ static int test_va_list_forms_are_usable_from_outside(void)
*/ */
static int test_variadic_wrappers_survive_repeated_calls(void) static int test_variadic_wrappers_survive_repeated_calls(void)
{ {
char buf[128]; char buf[128];
int count = 0; int count = 0;
int i = 0; int i = 0;
@@ -326,7 +330,7 @@ int main(void)
akerr_init(); akerr_init();
AKSL_RUN(failures, test_snprintf_writes_text); AKSL_RUN(failures, test_snprintf_writes_text_and_count);
AKSL_RUN(failures, test_snprintf_empty_format_writes_nothing); AKSL_RUN(failures, test_snprintf_empty_format_writes_nothing);
AKSL_RUN(failures, test_snprintf_truncation_is_an_error); AKSL_RUN(failures, test_snprintf_truncation_is_an_error);
AKSL_RUN(failures, test_snprintf_boundary_is_exact); AKSL_RUN(failures, test_snprintf_boundary_is_exact);

View File

@@ -115,7 +115,7 @@ static int test_format_wrappers_do_not_leak_slots(void)
for ( i = 0; i < ROUNDS; i++ ) { for ( i = 0; i < ROUNDS; i++ ) {
AKSL_CHECK_STATUS(aksl_printf(NULL, "x"), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_printf(NULL, "x"), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_fprintf(NULL, stdout, "x"), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_fprintf(NULL, stdout, "x"), AKERR_NULLPOINTER);
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "x")); AKSL_CHECK_STATUS(aksl_snprintf(NULL, buf, sizeof(buf), "x"), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_snprintf(&count, buf, 4, "%s", "far too long"), AKSL_CHECK_STATUS(aksl_snprintf(&count, buf, 4, "%s", "far too long"),
AKERR_OUTOFBOUNDS); AKERR_OUTOFBOUNDS);
AKSL_CHECK(aksl_slots_in_use() == 0); AKSL_CHECK(aksl_slots_in_use() == 0);
@@ -266,6 +266,7 @@ static int test_traversal_failures_do_not_leak_slots(void)
static int test_errors_name_their_origin_in_stdlib(void) static int test_errors_name_their_origin_in_stdlib(void)
{ {
void *ptr = NULL; void *ptr = NULL;
int count = 0;
char resolved[PATH_MAX]; char resolved[PATH_MAX];
uint32_t h = 0; uint32_t h = 0;
aksl_ListNode node; aksl_ListNode node;
@@ -294,7 +295,7 @@ static int test_errors_name_their_origin_in_stdlib(void)
AKSL_CHECK_STATUS(aksl_printf(NULL, "x"), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_printf(NULL, "x"), AKERR_NULLPOINTER);
AKSL_CHECK(came_from("aksl_vprintf", "src/stdlib.c") == 0); AKSL_CHECK(came_from("aksl_vprintf", "src/stdlib.c") == 0);
AKSL_CHECK_STATUS(aksl_snprintf(NULL, NULL, 8, "x"), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_snprintf(&count, NULL, 8, "x"), AKERR_NULLPOINTER);
AKSL_CHECK(came_from("aksl_vsnprintf", "src/stdlib.c") == 0); AKSL_CHECK(came_from("aksl_vsnprintf", "src/stdlib.c") == 0);
/* Likewise, the ato* forms are calls into the strto* ones. */ /* Likewise, the ato* forms are calls into the strto* ones. */