2026-07-29 13:06:25 -04:00
|
|
|
/*
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
* Memory wrappers -- TODO.md section 1.1, now complete, plus the additions from
|
|
|
|
|
* section 3.1.
|
2026-07-29 13:06:25 -04:00
|
|
|
*
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
* The three cases 1.1 left open are all pinned here: malloc(0) is AKERR_VALUE
|
|
|
|
|
* rather than whatever errno happened to hold when the platform's malloc(0)
|
|
|
|
|
* returned NULL; an allocation the system cannot satisfy reports ENOMEM and
|
|
|
|
|
* leaves *dst NULL rather than garbage; and overlapping memcpy is refused with
|
|
|
|
|
* AKERR_VALUE rather than left as undefined behaviour that usually looks fine.
|
2026-07-29 13:06:25 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "aksl_capture.h"
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
#include <errno.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
2026-07-29 13:06:25 -04:00
|
|
|
static int test_malloc_writes_nonnull_pointer(void)
|
|
|
|
|
{
|
|
|
|
|
void *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_malloc(32, &ptr));
|
|
|
|
|
AKSL_CHECK(ptr != NULL);
|
|
|
|
|
AKSL_CHECK_OK(aksl_free(ptr));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_malloc_rejects_null_destination(void)
|
|
|
|
|
{
|
2026-07-29 15:47:17 -04:00
|
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_malloc(32, NULL),
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKERR_NULLPOINTER, "dst=");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* TODO.md 1.1 asked for this contract to be pinned down. malloc(0) is allowed to
|
|
|
|
|
* return either a unique pointer or NULL, and a NULL there is not a failure and
|
|
|
|
|
* need not set errno -- so the old wrapper could raise an error whose status was
|
|
|
|
|
* 0, which every DETECT downstream reads as success while the context holds a
|
|
|
|
|
* pool slot. A zero-size request is refused on its own terms instead.
|
|
|
|
|
*/
|
|
|
|
|
static int test_malloc_rejects_zero_size(void)
|
|
|
|
|
{
|
|
|
|
|
void *ptr = (void *)0x1;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_malloc(0, &ptr), AKERR_VALUE, "zero-size");
|
|
|
|
|
AKSL_CHECK(ptr == NULL);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* An allocation no system can satisfy. SIZE_MAX/2 rather than SIZE_MAX because
|
|
|
|
|
* some allocators reject the latter as an obvious overflow before ever
|
|
|
|
|
* consulting the system, and the case worth testing is the one where the request
|
|
|
|
|
* is plausible and the answer is still no.
|
|
|
|
|
*/
|
|
|
|
|
static int test_malloc_reports_out_of_memory(void)
|
|
|
|
|
{
|
|
|
|
|
void *ptr = (void *)0x1;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_malloc(SIZE_MAX / 2, &ptr), ENOMEM);
|
|
|
|
|
/* Never garbage on the failure path. */
|
|
|
|
|
AKSL_CHECK(ptr == NULL);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_calloc_zeroes_and_guards(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char *buf = NULL;
|
|
|
|
|
void *ptr = (void *)0x1;
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_calloc(16, sizeof(unsigned char), (void **)&buf));
|
|
|
|
|
AKSL_CHECK(buf != NULL);
|
|
|
|
|
for ( i = 0; i < 16; i++ ) {
|
|
|
|
|
AKSL_CHECK(buf[i] == 0);
|
|
|
|
|
}
|
|
|
|
|
AKSL_CHECK_OK(aksl_free(buf));
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_calloc(16, 1, NULL), AKERR_NULLPOINTER);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_calloc(0, 1, &ptr), AKERR_VALUE);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_calloc(1, 0, &ptr), AKERR_VALUE);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* realloc(3)'s trap: on failure it returns NULL and the original block is *still
|
|
|
|
|
* valid*, so `p = realloc(p, n)` leaks it. The wrapper takes the pointer by
|
|
|
|
|
* reference and leaves it untouched on failure, so the caller still holds a
|
|
|
|
|
* pointer it can free -- which is what this asserts by freeing it.
|
|
|
|
|
*/
|
|
|
|
|
static int test_realloc_grows_and_preserves_the_old_block_on_failure(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char *buf = NULL;
|
|
|
|
|
void *ptr = NULL;
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_malloc(8, (void **)&buf));
|
|
|
|
|
for ( i = 0; i < 8; i++ ) {
|
|
|
|
|
buf[i] = (unsigned char)i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ptr = buf;
|
|
|
|
|
AKSL_CHECK_OK(aksl_realloc(&ptr, 64));
|
|
|
|
|
buf = (unsigned char *)ptr;
|
|
|
|
|
AKSL_CHECK(buf != NULL);
|
|
|
|
|
/* The contents that fit are carried over. */
|
|
|
|
|
for ( i = 0; i < 8; i++ ) {
|
|
|
|
|
AKSL_CHECK(buf[i] == (unsigned char)i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* A request that cannot be met leaves ptr aimed at the original block. */
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_realloc(&ptr, SIZE_MAX / 2), ENOMEM);
|
|
|
|
|
AKSL_CHECK(ptr == (void *)buf);
|
|
|
|
|
for ( i = 0; i < 8; i++ ) {
|
|
|
|
|
AKSL_CHECK(buf[i] == (unsigned char)i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_free(ptr));
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_realloc(NULL, 8), AKERR_NULLPOINTER);
|
2026-07-29 13:06:25 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_free_rejects_null_pointer(void)
|
|
|
|
|
{
|
2026-07-29 15:47:17 -04:00
|
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_free(NULL),
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKERR_NULLPOINTER, "NULL");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* aksl_freep clears the caller's pointer, so a second free is caught. */
|
|
|
|
|
static int test_freep_clears_the_pointer(void)
|
|
|
|
|
{
|
|
|
|
|
void *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_malloc(16, &ptr));
|
|
|
|
|
AKSL_CHECK(ptr != NULL);
|
|
|
|
|
AKSL_CHECK_OK(aksl_freep(&ptr));
|
|
|
|
|
AKSL_CHECK(ptr == NULL);
|
|
|
|
|
/* The second attempt is now an error instead of a double free. */
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_freep(&ptr), AKERR_NULLPOINTER);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_freep(NULL), AKERR_NULLPOINTER);
|
2026-07-29 13:06:25 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_malloc_free_round_trip(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char *buf = NULL;
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_malloc(64, (void **)&buf));
|
|
|
|
|
AKSL_CHECK(buf != NULL);
|
|
|
|
|
for ( i = 0; i < 64; i++ ) {
|
|
|
|
|
buf[i] = (unsigned char)i;
|
|
|
|
|
}
|
|
|
|
|
for ( i = 0; i < 64; i++ ) {
|
|
|
|
|
AKSL_CHECK(buf[i] == (unsigned char)i);
|
|
|
|
|
}
|
|
|
|
|
AKSL_CHECK_OK(aksl_free(buf));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_memset_fills_buffer(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char buf[8];
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
|
|
memset((void *)buf, 0x00, sizeof(buf));
|
|
|
|
|
AKSL_CHECK_OK(aksl_memset(buf, 0x5a, sizeof(buf)));
|
|
|
|
|
for ( i = 0; i < sizeof(buf); i++ ) {
|
|
|
|
|
AKSL_CHECK(buf[i] == 0x5a);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_memset_zero_length_is_noop(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char buf[4] = { 1, 2, 3, 4 };
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_memset(buf, 0xff, 0));
|
|
|
|
|
AKSL_CHECK(buf[0] == 1);
|
|
|
|
|
AKSL_CHECK(buf[1] == 2);
|
|
|
|
|
AKSL_CHECK(buf[2] == 3);
|
|
|
|
|
AKSL_CHECK(buf[3] == 4);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_memset_rejects_null_buffer(void)
|
|
|
|
|
{
|
2026-07-29 15:47:17 -04:00
|
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_memset(NULL, 0x00, 4),
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKERR_NULLPOINTER, "s=");
|
2026-07-29 13:06:25 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_memcpy_copies_bytes(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char src[6] = { 0, 1, 2, 3, 4, 5 };
|
|
|
|
|
unsigned char dst[6] = { 9, 9, 9, 9, 9, 9 };
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_memcpy(dst, src, sizeof(src)));
|
|
|
|
|
for ( i = 0; i < sizeof(src); i++ ) {
|
|
|
|
|
AKSL_CHECK(dst[i] == src[i]);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_memcpy_zero_length_is_noop(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char src[3] = { 1, 2, 3 };
|
|
|
|
|
unsigned char dst[3] = { 4, 5, 6 };
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_memcpy(dst, src, 0));
|
|
|
|
|
AKSL_CHECK(dst[0] == 4);
|
|
|
|
|
AKSL_CHECK(dst[1] == 5);
|
|
|
|
|
AKSL_CHECK(dst[2] == 6);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
/*
|
|
|
|
|
* TODO.md 1.1's open question, decided: overlap is AKERR_VALUE. memcpy(3) calls
|
|
|
|
|
* it undefined behaviour, which in practice means "works until the day a
|
|
|
|
|
* compiler version or a length changes and it does not". Callers who mean to
|
|
|
|
|
* overlap want aksl_memmove, and the message says so.
|
|
|
|
|
*/
|
|
|
|
|
static int test_memcpy_rejects_overlapping_ranges(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char buf[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
|
|
|
|
|
|
|
|
|
/* Destination inside the source range. */
|
|
|
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_memcpy(&buf[2], &buf[0], 4),
|
|
|
|
|
AKERR_VALUE, "aksl_memmove");
|
|
|
|
|
/* Source inside the destination range. */
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_memcpy(&buf[0], &buf[2], 4), AKERR_VALUE);
|
|
|
|
|
/* Adjacent but not overlapping is fine. */
|
|
|
|
|
AKSL_CHECK_OK(aksl_memcpy(&buf[4], &buf[0], 4));
|
|
|
|
|
AKSL_CHECK(buf[4] == 0 && buf[5] == 1 && buf[6] == 2 && buf[7] == 3);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_memmove_handles_overlap(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char buf[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_memmove(&buf[2], &buf[0], 4));
|
|
|
|
|
AKSL_CHECK(buf[2] == 0 && buf[3] == 1 && buf[4] == 2 && buf[5] == 3);
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_memmove(NULL, buf, 1), AKERR_NULLPOINTER);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_memmove(buf, NULL, 1), AKERR_NULLPOINTER);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-29 13:06:25 -04:00
|
|
|
static int test_memcpy_rejects_null_destination(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char src[1] = { 1 };
|
|
|
|
|
|
2026-07-29 15:47:17 -04:00
|
|
|
AKSL_CHECK_STATUS(aksl_memcpy(NULL, src, sizeof(src)),
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKERR_NULLPOINTER);
|
2026-07-29 13:06:25 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_memcpy_rejects_null_source(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char dst[1] = { 0 };
|
|
|
|
|
|
2026-07-29 15:47:17 -04:00
|
|
|
AKSL_CHECK_STATUS(aksl_memcpy(dst, NULL, sizeof(dst)),
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKERR_NULLPOINTER);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* memcmp and memchr answer through an out-param, because the return value is
|
|
|
|
|
* already spoken for by the error context. A memchr that finds nothing is not an
|
|
|
|
|
* error: "absent" is an ordinary answer to that question, so *dest is NULL and
|
|
|
|
|
* the call succeeds.
|
|
|
|
|
*/
|
|
|
|
|
static int test_memcmp_reports_ordering(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char a[4] = { 1, 2, 3, 4 };
|
|
|
|
|
unsigned char b[4] = { 1, 2, 3, 4 };
|
|
|
|
|
unsigned char c[4] = { 1, 2, 9, 4 };
|
|
|
|
|
int result = 99;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_memcmp(a, b, sizeof(a), &result));
|
|
|
|
|
AKSL_CHECK(result == 0);
|
|
|
|
|
AKSL_CHECK_OK(aksl_memcmp(a, c, sizeof(a), &result));
|
|
|
|
|
AKSL_CHECK(result < 0);
|
|
|
|
|
AKSL_CHECK_OK(aksl_memcmp(c, a, sizeof(a), &result));
|
|
|
|
|
AKSL_CHECK(result > 0);
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_memcmp(NULL, b, 4, &result), AKERR_NULLPOINTER);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_memcmp(a, NULL, 4, &result), AKERR_NULLPOINTER);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_memcmp(a, b, 4, NULL), AKERR_NULLPOINTER);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_memchr_finds_or_reports_absence(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char buf[5] = { 'a', 'b', 'c', 'd', 'e' };
|
|
|
|
|
void *found = NULL;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_memchr(buf, 'c', sizeof(buf), &found));
|
|
|
|
|
AKSL_CHECK(found == (void *)&buf[2]);
|
|
|
|
|
|
|
|
|
|
/* Not found is a successful answer of "nowhere". */
|
|
|
|
|
AKSL_CHECK_OK(aksl_memchr(buf, 'z', sizeof(buf), &found));
|
|
|
|
|
AKSL_CHECK(found == NULL);
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_memchr(NULL, 'a', 1, &found), AKERR_NULLPOINTER);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_memchr(buf, 'a', 1, NULL), AKERR_NULLPOINTER);
|
2026-07-29 13:06:25 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(void)
|
|
|
|
|
{
|
|
|
|
|
int failures = 0;
|
|
|
|
|
|
|
|
|
|
akerr_init();
|
|
|
|
|
|
|
|
|
|
AKSL_RUN(failures, test_malloc_writes_nonnull_pointer);
|
|
|
|
|
AKSL_RUN(failures, test_malloc_rejects_null_destination);
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKSL_RUN(failures, test_malloc_rejects_zero_size);
|
|
|
|
|
AKSL_RUN(failures, test_malloc_reports_out_of_memory);
|
2026-07-29 13:06:25 -04:00
|
|
|
AKSL_RUN(failures, test_malloc_free_round_trip);
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKSL_RUN(failures, test_calloc_zeroes_and_guards);
|
|
|
|
|
AKSL_RUN(failures, test_realloc_grows_and_preserves_the_old_block_on_failure);
|
|
|
|
|
|
|
|
|
|
AKSL_RUN(failures, test_free_rejects_null_pointer);
|
|
|
|
|
AKSL_RUN(failures, test_freep_clears_the_pointer);
|
|
|
|
|
|
2026-07-29 13:06:25 -04:00
|
|
|
AKSL_RUN(failures, test_memset_fills_buffer);
|
|
|
|
|
AKSL_RUN(failures, test_memset_zero_length_is_noop);
|
|
|
|
|
AKSL_RUN(failures, test_memset_rejects_null_buffer);
|
|
|
|
|
|
|
|
|
|
AKSL_RUN(failures, test_memcpy_copies_bytes);
|
|
|
|
|
AKSL_RUN(failures, test_memcpy_zero_length_is_noop);
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKSL_RUN(failures, test_memcpy_rejects_overlapping_ranges);
|
2026-07-29 13:06:25 -04:00
|
|
|
AKSL_RUN(failures, test_memcpy_rejects_null_destination);
|
|
|
|
|
AKSL_RUN(failures, test_memcpy_rejects_null_source);
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKSL_RUN(failures, test_memmove_handles_overlap);
|
|
|
|
|
|
|
|
|
|
AKSL_RUN(failures, test_memcmp_reports_ordering);
|
|
|
|
|
AKSL_RUN(failures, test_memchr_finds_or_reports_absence);
|
2026-07-29 13:06:25 -04:00
|
|
|
|
|
|
|
|
AKSL_REPORT(failures);
|
|
|
|
|
}
|