3 Commits

Author SHA1 Message Date
be725f8cf2 Merge pull request 'Document libc wrapper contract' (#30) from 9 into main
All checks were successful
libakstdlib CI Build / cmake_build (push) Successful in 2m53s
libakstdlib CI Build / sanitizers (push) Successful in 3m3s
libakstdlib CI Build / coverage (push) Successful in 2m45s
libakstdlib CI Build / mutation_test (push) Successful in 11m59s
Reviewed-on: #30
2026-08-03 06:53:05 -04:00
58f426abce Document libc wrapper contract
All checks were successful
libakstdlib CI Build / coverage (push) Successful in 2m47s
libakstdlib CI Build / sanitizers (push) Successful in 2m57s
libakstdlib CI Build / cmake_build (push) Successful in 3m15s
libakstdlib CI Build / mutation_test (push) Successful in 12m35s
Co-authored-by: Andrew Kesterson <andrew@aklabs.net>
2026-08-03 06:38:21 -04:00
55d986c631 Drop the TODO.md section numbers from 26 files
All checks were successful
libakstdlib CI Build / cmake_build (push) Successful in 5m53s
libakstdlib CI Build / sanitizers (push) Successful in 2m55s
libakstdlib CI Build / coverage (push) Successful in 2m48s
libakstdlib CI Build / mutation_test (push) Successful in 12m39s
Eighty-five comments cited section numbers -- 1.1, 2.2.6, 3.6 -- from a
numbering the file had already abandoned before the move to the tracker. They
label completed work, so the pointer was the only wrong part.

The citation is removed and the sentence kept, which is what issue #27
recommended: these are labels, not references, and a label carrying a
version-dependent pointer goes stale again at the next reorganisation. Where a
pointer earns its place it names what actually holds the content now --
UPGRADING.md for the confirmed defects, libakerror #15 for the target
namespacing, issue #7 for the mutation survivors.

README.md and akstdlib.h sent readers to TODO.md for 'what is still open';
they name the tracker.

Verified: cmake --build build && ctest --test-dir build, 19/19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-08-02 22:01:27 -04:00
28 changed files with 105 additions and 94 deletions

View File

@@ -21,7 +21,7 @@ jobs:
# different libakerrors depending on where you built: the install went to # different libakerrors depending on where you built: the install went to
# /usr/local, while the build below is top-level and so compiles # /usr/local, while the build below is top-level and so compiles
# deps/libakerror at the pinned commit via add_subdirectory -- the # deps/libakerror at the pinned commit via add_subdirectory -- the
# installed one was never actually linked against. TODO.md 2.3. # installed one was never actually linked against.
# #
# The submodule wins. It is the version this repository pins, tests and # The submodule wins. It is the version this repository pins, tests and
# ships against, and a CI that tests a different one is testing something # ships against, and a CI that tests a different one is testing something
@@ -155,7 +155,7 @@ jobs:
# new surface being argument validation whose mutants are frequently # new surface being argument validation whose mutants are frequently
# equivalent. `errno = 0` deleted from a wrapper whose libc call always # equivalent. `errno = 0` deleted from a wrapper whose libc call always
# sets errno cannot be distinguished by any test that could be written. # sets errno cannot be distinguished by any test that could be written.
# The survivors worth acting on are named in TODO.md; raise the gate as # The survivors worth acting on are named in issue #7; raise the gate as
# they become assertions. # they become assertions.
- name: mutation testing - name: mutation testing
run: | run: |

View File

@@ -76,6 +76,10 @@ return convention and the `PREPARE_ERROR` / `FAIL_*` / `SUCCEED_RETURN` pattern.
Four conventions hold across the whole library, and a new wrapper that breaks one Four conventions hold across the whole library, and a new wrapper that breaks one
of them is wrong even if it compiles and passes: of them is wrong even if it compiles and passes:
- **Preserve the libc contract unless there is a compelling, documented reason
not to.** libc behaviour is the standard to meet. This library changes only
the error transport (to `akerror`) and, where libc returns a value, the result
shape (through a caller-provided destination pointer).
- **A NULL out-param is a caller error**, not "don't care". - **A NULL out-param is a caller error**, not "don't care".
- **Finding nothing is success** -- searching functions write NULL or zero and - **Finding nothing is success** -- searching functions write NULL or zero and
return NULL. return NULL.
@@ -93,6 +97,11 @@ The build is `-Wall -Wextra` and CI adds `-Werror`. `-Wpedantic` is deliberately
off: libakerror's `FAIL_*` macros trip "ISO C99 requires at least one argument off: libakerror's `FAIL_*` macros trip "ISO C99 requires at least one argument
for the ..." on their own expansion, not on anything at the call site. for the ..." on their own expansion, not on anything at the call site.
**Do not wrap a libc function that cannot fail and provides no failure or
operation-status result.** There is no `akerror` context to carry. A value such
as `umask()`'s previous mask is not an operation-status result, so `umask()` is
not a wrapper candidate.
## Testing Guidelines ## Testing Guidelines
Add a new test by creating `tests/test_mything.c` and adding `mything` to the Add a new test by creating `tests/test_mything.c` and adding `mything` to the

View File

@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.10)
# akstdlibConfigVersion.cmake. Nothing else should spell a version number. # akstdlibConfigVersion.cmake. Nothing else should spell a version number.
# #
# 0.2.0, and the minor bump is an ABI break on purpose. Fixing the confirmed # 0.2.0, and the minor bump is an ABI break on purpose. Fixing the confirmed
# defects in TODO.md 2.1 changed documented behaviour and, in five places, # defects listed in UPGRADING.md changed documented behaviour and, in five places,
# signatures: # signatures:
# #
# aksl_realpath takes the destination's length; aksl_realpath_alloc is new # aksl_realpath takes the destination's length; aksl_realpath_alloc is new
@@ -54,7 +54,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
endif() endif()
# Warnings. The only warning in the tree when these went on was the unused # Warnings. The only warning in the tree when these went on was the unused
# `queue` parameter of aksl_tree_iterate, which TODO.md 2.2.8 had already # `queue` parameter of aksl_tree_iterate, which UPGRADING.md had already
# recorded as a dead parameter -- so the cost of turning them on was one # recorded as a dead parameter -- so the cost of turning them on was one
# already-known defect, and the cost of leaving them off was every future one. # already-known defect, and the cost of leaving them off was every future one.
# #
@@ -63,7 +63,7 @@ endif()
# requires at least one argument for the ...", which is a complaint about the # requires at least one argument for the ...", which is a complaint about the
# macro's shape rather than about anything at this call site. Every FAIL_* in # macro's shape rather than about anything at this call site. Every FAIL_* in
# src/stdlib.c passes at least one argument regardless -- see the note in # src/stdlib.c passes at least one argument regardless -- see the note in
# TODO.md 2.3 -- so the code is pedantic-clean; it is the expansion that is not. # libakerror issue #15 -- so the code is pedantic-clean; it is the expansion that is not.
if(CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$") if(CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
add_compile_options(-Wall -Wextra) add_compile_options(-Wall -Wextra)
# CI turns this on. Locally it is off, because a warning that stops the build # CI turns this on. Locally it is off, because a warning that stops the build
@@ -173,7 +173,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
# configure at all. Rename the dependency's on the way past rather than # configure at all. Rename the dependency's on the way past rather than
# dropping it: its coverage script drives its own instrumented build tree, so # dropping it: its coverage script drives its own instrumented build tree, so
# `cmake --build build-coverage --target akerror_coverage` still does the right # `cmake --build build-coverage --target akerror_coverage` still does the right
# thing. Remove this once the dependency namespaces it upstream -- see TODO.md. # thing. Remove this once the dependency namespaces it upstream -- libakerror issue #15.
function(add_custom_target _name) function(add_custom_target _name)
if(AKSL_SUPPRESS_ADD_TEST AND _name STREQUAL "coverage") if(AKSL_SUPPRESS_ADD_TEST AND _name STREQUAL "coverage")
_add_custom_target(akerror_coverage ${ARGN}) _add_custom_target(akerror_coverage ${ARGN})
@@ -329,7 +329,7 @@ set(AKSL_WILL_FAIL_TESTS
# Empty, and that is the news. It held four entries -- convert_strict, # Empty, and that is the news. It held four entries -- convert_strict,
# list_append_chain, list_iterate_head and tree_iterate_break -- one for each # list_append_chain, list_iterate_head and tree_iterate_break -- one for each
# confirmed defect in TODO.md 2.1. All four are fixed, so each of those files # confirmed defect listed in UPGRADING.md. All four are fixed, so each of those files
# was folded back into the test for the thing it was testing (tests/ # was folded back into the test for the thing it was testing (tests/
# test_convert.c, tests/test_linkedlist.c and tests/test_tree.c) where it now # test_convert.c, tests/test_linkedlist.c and tests/test_tree.c) where it now
# has to keep passing rather than merely keep failing visibly. # has to keep passing rather than merely keep failing visibly.
@@ -350,7 +350,7 @@ foreach(_test IN LISTS AKSL_TESTS AKSL_WILL_FAIL_TESTS AKSL_KNOWN_FAILING_TESTS)
list(APPEND AKSL_TEST_TARGETS test_${_test}) list(APPEND AKSL_TEST_TARGETS test_${_test})
endforeach() endforeach()
# Negative compile tests -- TODO.md 1.3 and 1.9. # Negative compile tests: the format attributes and AKERR_NOIGNORE.
# #
# Two properties of this library are enforced by the compiler and by nothing # Two properties of this library are enforced by the compiler and by nothing
# else: AKERR_NOIGNORE makes discarding a returned error context an error, and # else: AKERR_NOIGNORE makes discarding a returned error context an error, and

View File

@@ -8,7 +8,8 @@ through [libakerror](https://source.starfort.tech/andrew/libakerror)'s
and `errno`. It also provides data structures built on the same convention. and `errno`. It also provides data structures built on the same convention.
Every entry point returns `akerr_ErrorContext *` and is marked `AKERR_NOIGNORE`. Every entry point returns `akerr_ErrorContext *` and is marked `AKERR_NOIGNORE`.
See `TODO.md` for the current state of the library and `UPGRADING.md` if you are Outstanding work is in [the issue tracker](https://source.starfort.tech/andrew/libakstdlib/issues).
See `TODO.md` for where the library stands and what is deliberately not wrapped, and `UPGRADING.md` if you are
coming from 0.1.0, which this release breaks. coming from 0.1.0, which this release breaks.
## What it wraps ## What it wraps

View File

@@ -26,7 +26,8 @@
* takes a slot from it on any failure path. See README.md. * takes a slot from it on any failure path. See README.md.
* *
* @see README.md for the deviations from libc semantics, UPGRADING.md if you are * @see README.md for the deviations from libc semantics, UPGRADING.md if you are
* coming from 0.1.0, and TODO.md for what is still open. * coming from 0.1.0. Outstanding work is in the issue tracker;
* TODO.md is the record of where the library stands.
*/ */
#ifndef _AKSTDLIB_H_ #ifndef _AKSTDLIB_H_
@@ -69,7 +70,7 @@
* *
* It used to pull in stdlib.h and string.h as well, which nothing here needs and * It used to pull in stdlib.h and string.h as well, which nothing here needs and
* which every consumer then got whether it wanted them or not. stddef.h in place * which every consumer then got whether it wanted them or not. stddef.h in place
* of stdlib.h is the same size_t at a fraction of the namespace. TODO.md 2.2.16. * of stdlib.h is the same size_t at a fraction of the namespace.
*/ */
#include <stdarg.h> #include <stdarg.h>
#include <stddef.h> #include <stddef.h>
@@ -85,7 +86,7 @@ extern "C" {
/* /*
* Restore the compile-time format/argument checking that callers otherwise lose * Restore the compile-time format/argument checking that callers otherwise lose
* by going through a variadic wrapper: without it, printf("%d", "str") is caught * by going through a variadic wrapper: without it, printf("%d", "str") is caught
* and aksl_printf(&n, "%d", "str") is not. TODO.md 2.2.5. * and aksl_printf(&n, "%d", "str") is not.
* *
* tests/negative/format_mismatch.c is a compile that must fail, which is what * tests/negative/format_mismatch.c is a compile that must fail, which is what
* proves these are still attached. * proves these are still attached.

View File

@@ -18,7 +18,7 @@
* the context still holds a pool slot: an error that is invisible and leaks at * the context still holds a pool slot: an error that is invisible and leaks at
* the same time. Every errno-sourced status in this library goes through here, * the same time. Every errno-sourced status in this library goes through here,
* and every wrapped call clears errno first so the value read back is its own. * and every wrapped call clears errno first so the value read back is its own.
* TODO.md 2.2.1. * See UPGRADING.md.
*/ */
#define AKSL_ERRNO_OR(__fallback) (errno != 0 ? errno : (__fallback)) #define AKSL_ERRNO_OR(__fallback) (errno != 0 ? errno : (__fallback))

View File

@@ -1,5 +1,5 @@
/* /*
* Data structures -- TODO.md section 3.6. * Data structures.
* *
* Not libc wrappers. These are the parts of the list and tree API that were * Not libc wrappers. These are the parts of the list and tree API that were
* visibly missing, plus the two structures the first real consumer had to write * visibly missing, plus the two structures the first real consumer had to write
@@ -328,7 +328,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate_reverse(aksl_ListNode *tail
* aksl_list_append has to walk the whole list to find the tail, so building a * aksl_list_append has to walk the whole list to find the tail, so building a
* list of n nodes with it is O(n^2). That is fine for the handful of nodes the * list of n nodes with it is O(n^2). That is fine for the handful of nodes the
* bare-node API was written for and wrong for anything larger, which is what * bare-node API was written for and wrong for anything larger, which is what
* TODO.md 3.6 means by "a head/tail-tracking container type so append is O(1)". * the collections plan meant by "a head/tail-tracking container type so append is O(1)".
* *
* The container holds the length as well, so aksl_list_length stops being a * The container holds the length as well, so aksl_list_length stops being a
* walk. It owns no memory -- the nodes are still the caller's -- so there is no * walk. It owns no memory -- the nodes are still the caller's -- so there is no
@@ -440,7 +440,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_clear(aksl_List *list, aksl_FreeFun
* through an out-param and may raise, like every other callback here. * through an out-param and may raise, like every other callback here.
* *
* These are the functions that set and read aksl_TreeNode.parent, which was * These are the functions that set and read aksl_TreeNode.parent, which was
* declared and then never touched by anything in the library (TODO.md 2.2.15). * declared and then never touched by anything in the library.
* aksl_tree_remove needs it: relinking a node's replacement means telling that * aksl_tree_remove needs it: relinking a node's replacement means telling that
* node's parent about it, and finding the parent by walking from the root again * node's parent about it, and finding the parent by walking from the root again
* would turn a removal into a second search. * would turn a removal into a second search.
@@ -691,7 +691,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_free_all(aksl_TreeNode **root, aksl
/* ====================================================================== */ /* ====================================================================== */
/* /*
* FNV-1a, the other half of TODO.md 3.6's hash request. It differs from djb2 in * FNV-1a, the other half of the collections plan's hash request. It differs from djb2 in
* XOR-then-multiply rather than multiply-then-add, which mixes the low bits * XOR-then-multiply rather than multiply-then-add, which mixes the low bits
* rather better -- worth having when the keys are short and share a prefix, * rather better -- worth having when the keys are short and share a prefix,
* which is exactly what identifiers in a symbol table look like. * which is exactly what identifiers in a symbol table look like.
@@ -730,7 +730,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_fnv1a_str(const char *str, uint3
/* /*
* Fixed-capacity, open-addressed, linear-probing, string-keyed. * Fixed-capacity, open-addressed, linear-probing, string-keyed.
* *
* The shape is akbasic's src/symtab.c, which TODO.md 3.6 says is "worth lifting * The shape is akbasic's src/symtab.c, which the collections plan called "worth lifting
* more or less verbatim": the caller supplies the slot array, the map refuses * more or less verbatim": the caller supplies the slot array, the map refuses
* rather than resizes when full, and the keys are copied into fixed-size slots * rather than resizes when full, and the keys are copied into fixed-size slots
* so the map owns them and a caller cannot outlive its own key strings. * so the map owns them and a caller cannot outlive its own key strings.
@@ -939,7 +939,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_iterate(aksl_HashMap *map,
* *
* The bounded formatting wrappers are the right answer when the destination is * The bounded formatting wrappers are the right answer when the destination is
* a fixed buffer, and no answer at all when the output length is not known in * a fixed buffer, and no answer at all when the output length is not known in
* advance -- which is why TODO.md 3.6 asks for this to "make the snprintf and * advance -- which is why the collections plan asked for this to "make the snprintf and
* strcat wrappers pleasant to use". Building a diagnostic, a serialised record * strcat wrappers pleasant to use". Building a diagnostic, a serialised record
* or a generated line means appending to something that grows. * or a generated line means appending to something that grows.
* *

View File

@@ -99,7 +99,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_calloc(size_t nmemb, size_t size, void *
* block valid*, so the near-universal `p = realloc(p, n)` leaks the original * block valid*, so the near-universal `p = realloc(p, n)` leaks the original
* every time it fails. Here the old pointer goes in and out through the same * every time it fails. Here the old pointer goes in and out through the same
* out-param, and is left untouched -- still valid, still the caller's to free -- * out-param, and is left untouched -- still valid, still the caller's to free --
* whenever an error is raised. TODO.md 3.1. * whenever an error is raised.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *aksl_realloc(void **ptr, size_t size) akerr_ErrorContext AKERR_NOIGNORE *aksl_realloc(void **ptr, size_t size)
{ {
@@ -196,7 +196,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_free(void *ptr)
/* /*
* Frees and clears in one step, so the pointer cannot be used or freed twice. * Frees and clears in one step, so the pointer cannot be used or freed twice.
* TODO.md 2.2.13 -- aksl_free leaves the caller holding a dangling pointer, and * aksl_free leaves the caller holding a dangling pointer, and
* "remember to NULL it afterwards" is exactly the discipline this library is * "remember to NULL it afterwards" is exactly the discipline this library is
* supposed to make unnecessary rather than merely possible. * supposed to make unnecessary rather than merely possible.
*/ */
@@ -214,7 +214,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_freep(void **ptr)
* memset(3) and memcpy(3) cannot fail. They return their destination pointer * memset(3) and memcpy(3) cannot fail. They return their destination pointer
* unconditionally, so the old FAIL_ZERO_RETURN(e, memset(...), ...) and * unconditionally, so the old FAIL_ZERO_RETURN(e, memset(...), ...) and
* (memcpy(...) == d) checks were dead code that read as though there were a * (memcpy(...) == d) checks were dead code that read as though there were a
* failure mode to catch -- TODO.md 2.2.11. What is worth checking is the * failure mode to catch. What is worth checking is the
* arguments, which is all that is checked now. * arguments, which is all that is checked now.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *aksl_memset(void *s, int c, size_t n) akerr_ErrorContext AKERR_NOIGNORE *aksl_memset(void *s, int c, size_t n)
@@ -289,7 +289,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_memchr(const void *s, int c, size_t n, v
* pathname and mode are checked. fopen(NULL, ...) is undefined behaviour, and * pathname and mode are checked. fopen(NULL, ...) is undefined behaviour, and
* this wrapper used to hand both straight through unexamined -- reachable from * this wrapper used to hand both straight through unexamined -- reachable from
* user input in practice, which is why akbasic validates the filename itself * user input in practice, which is why akbasic validates the filename itself
* before calling DLOAD/DSAVE with a comment pointing at TODO.md 2.2.2. * before calling DLOAD/DSAVE with a comment pointing at.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen( akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(
const char *pathname, const char *pathname,
@@ -312,7 +312,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(
/* /*
* fread and fwrite both report how much they actually transferred, through a * fread and fwrite both report how much they actually transferred, through a
* required out-param. Three things were wrong with the old pair (TODO.md 2.2.3), * required out-param. Three things were wrong with the old pair,
* and the count is the fix for the worst of them: a caller who got AKERR_EOF had * and the count is the fix for the worst of them: a caller who got AKERR_EOF had
* no way to find out how much data had arrived before the stream ran out, which * no way to find out how much data had arrived before the stream ran out, which
* makes the EOF status almost useless for the partial-read case it exists to * makes the EOF status almost useless for the partial-read case it exists to
@@ -405,7 +405,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream)
* Formatted output. * Formatted output.
* *
* The va_list forms below do the work and the variadic forms are thin wrappers * The va_list forms below do the work and the variadic forms are thin wrappers
* over them, which is both what §3.1 of TODO.md asked for -- so consumers can * over them, which is both what the wrapper contract asked for -- so consumers can
* build their own variadic wrappers -- and what makes the va_end rule below * build their own variadic wrappers -- and what makes the va_end rule below
* checkable in one place instead of three. * checkable in one place instead of three.
* *
@@ -415,7 +415,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream)
* one. Omitting it is undefined behaviour per the standard and leaks * one. Omitting it is undefined behaviour per the standard and leaks
* 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. TODO.md 2.1.4. * exactly what made it worth fixing before something did.
* - *count is written on every path. It used to be left holding vprintf's -1 * - *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 * 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 * a negative byte count out of a function that had already failed. It is now
@@ -426,7 +426,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream)
* 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
* this library exists to remove. aksl_snprintf replaces it, and treats * this library exists to remove. aksl_snprintf replaces it, and treats
* truncation as the failure it is rather than as a short success. TODO.md 2.2.4. * truncation as the failure it is rather than as a short success.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *aksl_vprintf(int *count, const char *restrict format, va_list args) akerr_ErrorContext AKERR_NOIGNORE *aksl_vprintf(int *count, const char *restrict format, va_list args)
@@ -561,7 +561,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_vasprintf(int *count, char **dest, const
* same arguments a few lines up, so the write cannot truncate. Guarding it * 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 * 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 * 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 * failure mode to catch, which is exactly what was recorded
* against the old aksl_memset and aksl_memcpy and what removing those was * 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. * for. The invariant is stated here instead, where it can be read.
*/ */
@@ -590,10 +590,10 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_asprintf(int *count, char **dest, const
* atoi("99999999999999999999") handed back success and a wrapped value, because * atoi("99999999999999999999") handed back success and a wrapped value, because
* atoi(3) has no error channel at all. A library whose entire value proposition * atoi(3) has no error channel at all. A library whose entire value proposition
* is turning silent libc failures into error contexts cannot ship that. * is turning silent libc failures into error contexts cannot ship that.
* TODO.md 2.1.5. * See UPGRADING.md.
* *
* The strto* wrappers below are the real implementation and the ato* wrappers * The strto* wrappers below are the real implementation and the ato* wrappers
* are three-line calls into them, which is what TODO.md 3.1 asked for on its own * are three-line calls into them, which is what the wrapper contract asked for on its own
* account -- akbasic had to hand-write ~60 lines of exactly this (its * account -- akbasic had to hand-write ~60 lines of exactly this (its
* src/convert.c) because the library would not do it. * src/convert.c) because the library would not do it.
* *
@@ -846,7 +846,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_atof(const char *nptr, double *dest)
* realpath(3), with the destination buffer made expressible. * realpath(3), with the destination buffer made expressible.
* *
* The old two-argument form had three separate problems, all of them reachable * The old two-argument form had three separate problems, all of them reachable
* from ordinary use (TODO.md 2.1.6): resolved_path was never NULL-checked, so * from ordinary use: resolved_path was never NULL-checked, so
* realpath(path, NULL) allocated a buffer the wrapper then discarded and leaked; * realpath(path, NULL) allocated a buffer the wrapper then discarded and leaked;
* there was no way for a caller to say how big the buffer was, so everyone had * there was no way for a caller to say how big the buffer was, so everyone had
* to know to supply PATH_MAX bytes; and the failure path formatted resolved_path * to know to supply PATH_MAX bytes; and the failure path formatted resolved_path
@@ -904,7 +904,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_realpath_alloc(const char *restrict path
* negative value: the hash differed from canonical djb2, and -- worse -- differed * negative value: the hash differed from canonical djb2, and -- worse -- differed
* between platforms depending on the signedness of char. Benign for the 7-bit * between platforms depending on the signedness of char. Benign for the 7-bit
* ASCII identifiers akbasic hashes, and quietly wrong for the first caller to * ASCII identifiers akbasic hashes, and quietly wrong for the first caller to
* key a table on a filename or a UTF-8 string literal. TODO.md 2.2.6. * key a table on a filename or a UTF-8 string literal.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(const char *str, size_t len, uint32_t *hashval) akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(const char *str, size_t len, uint32_t *hashval)
{ {
@@ -921,7 +921,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(const char *str, size_t len
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
/* The NUL-terminated convenience form TODO.md 3.6 asked for. */ /* The NUL-terminated convenience form the collections plan asked for. */
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2_str(const char *str, uint32_t *hashval) akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2_str(const char *str, uint32_t *hashval)
{ {
PREPARE_ERROR(e); PREPARE_ERROR(e);
@@ -941,7 +941,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_append(aksl_ListNode *list, aksl_Li
* Two separate walks, deliberately. The Floyd pass answers "is this list * Two separate walks, deliberately. The Floyd pass answers "is this list
* finite"; it says nothing about where the tail is, because `slow` stops at * finite"; it says nothing about where the tail is, because `slow` stops at
* the midpoint. Conflating the two is what made this function truncate every * the midpoint. Conflating the two is what made this function truncate every
* list of two or more nodes -- see TODO.md 2.1.1. * list of two or more nodes.
*/ */
while ( fast != NULL && fast->next != NULL ) { while ( fast != NULL && fast->next != NULL ) {
slow = slow->next; slow = slow->next;
@@ -973,7 +973,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_append(aksl_ListNode *list, aksl_Li
/* /*
* `head` is required, not optional. Popping the head node used to leave the * `head` is required, not optional. Popping the head node used to leave the
* caller's own head pointer aimed at a node that is no longer in the list, and * caller's own head pointer aimed at a node that is no longer in the list, and
* there was no way for the caller to learn the new one -- TODO.md 2.2.12. Taking * there was no way for the caller to learn the new one. Taking
* the head by reference makes the correct call the only call that compiles. * the head by reference makes the correct call the only call that compiles.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_pop(aksl_ListNode **head, aksl_ListNode *node) akerr_ErrorContext AKERR_NOIGNORE *aksl_list_pop(aksl_ListNode **head, aksl_ListNode *node)
@@ -998,7 +998,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_pop(aksl_ListNode **head, aksl_List
/* /*
* Zeroing initialisers. Every caller previously had to remember to memset a node * Zeroing initialisers. Every caller previously had to remember to memset a node
* before its first use, because append and iterate both read next/prev -- and a * before its first use, because append and iterate both read next/prev -- and a
* stack-allocated node that skipped it walked into garbage. TODO.md 2.2.14. * stack-allocated node that skipped it walked into garbage.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_node_init(aksl_ListNode *node, void *data) akerr_ErrorContext AKERR_NOIGNORE *aksl_list_node_init(aksl_ListNode *node, void *data)
{ {
@@ -1027,7 +1027,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_node_init(aksl_TreeNode *node, void
* aksl_ListNode: the walk needs to carry each node's depth alongside it, which is * aksl_ListNode: the walk needs to carry each node's depth alongside it, which is
* how a cyclic tree is stopped without a visited-set. The caller's lalloc/lfree * how a cyclic tree is stopped without a visited-set. The caller's lalloc/lfree
* allocate and release these -- which is what they were always documented to do * allocate and release these -- which is what they were always documented to do
* and never actually did (TODO.md 2.2.8). * and never actually did.
*/ */
typedef struct TreeQueueEntry { typedef struct TreeQueueEntry {
struct TreeQueueEntry *next; struct TreeQueueEntry *next;
@@ -1165,12 +1165,12 @@ static akerr_ErrorContext AKERR_NOIGNORE *tree_bfs_walk(
* unwinds every frame up to the public entry point, which swallows it exactly * unwinds every frame up to the public entry point, which swallows it exactly
* once. In the old single-function form the frame that raised the break handled * once. In the old single-function form the frame that raised the break handled
* it and returned success, the parent's PASS therefore saw nothing wrong, and * it and returned success, the parent's PASS therefore saw nothing wrong, and
* the walk carried on into the sibling subtree -- TODO.md 2.1.3. * the walk carried on into the sibling subtree.
* *
* `path` is the chain of ancestors of `root` and `depth` its length. Checking * `path` is the chain of ancestors of `root` and `depth` its length. Checking
* each node against its own ancestry turns a tree that loops back on itself from * each node against its own ancestry turns a tree that loops back on itself from
* infinite recursion into AKERR_CIRCULAR_REFERENCE, and the depth cap catches the * infinite recursion into AKERR_CIRCULAR_REFERENCE, and the depth cap catches the
* degenerate chain that is merely too deep to recurse over (TODO.md 2.2.7). * degenerate chain that is merely too deep to recurse over.
*/ */
static akerr_ErrorContext AKERR_NOIGNORE *tree_dfs_walk( static akerr_ErrorContext AKERR_NOIGNORE *tree_dfs_walk(
aksl_TreeNode *root, aksl_TreeNode *root,
@@ -1266,7 +1266,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_iterate(
* assume it. The switch used to have no default at all, so searchmode 99 -- * assume it. The switch used to have no default at all, so searchmode 99 --
* and AKSL_TREE_SEARCH_VISIT, which the header documented but nothing * and AKSL_TREE_SEARCH_VISIT, which the header documented but nothing
* implemented -- fell straight through to success having visited nothing * implemented -- fell straight through to success having visited nothing
* (TODO.md 2.2.9). * See UPGRADING.md.
*/ */
switch ( searchmode ) { switch ( searchmode ) {
case AKSL_TREE_SEARCH_DFS_PREORDER: case AKSL_TREE_SEARCH_DFS_PREORDER:
@@ -1328,7 +1328,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate(aksl_ListNode *list, aksl_L
/* /*
* From `list`, not from `slow`. The Floyd pass above leaves `slow` at the * From `list`, not from `slow`. The Floyd pass above leaves `slow` at the
* midpoint, and starting the visit there skipped the whole first half of the * midpoint, and starting the visit there skipped the whole first half of the
* list including the head -- see TODO.md 2.1.2. * list including the head.
*/ */
while ( node != NULL ) { while ( node != NULL ) {
ATTEMPT { ATTEMPT {

View File

@@ -1,5 +1,5 @@
/* /*
* stdio.h wrappers beyond fopen/fread/fwrite/fclose -- TODO.md section 3.1. * stdio.h wrappers beyond fopen/fread/fwrite/fclose.
* *
* Positioning, flushing, character and line I/O, stream state, formatted input, * Positioning, flushing, character and line I/O, stream state, formatted input,
* and the file-level operations that go with them. * and the file-level operations that go with them.

View File

@@ -1,12 +1,12 @@
/* /*
* string.h wrappers -- TODO.md section 3.1. * string.h wrappers.
* *
* This is the section akbasic needed most and could not have: across its source * This is the section akbasic needed most and could not have: across its source
* it calls strlen 37 times, strcmp 16, strncpy 15 and strstr once, every one of * it calls strlen 37 times, strcmp 16, strncpy 15 and strstr once, every one of
* them raw because there was nothing here to call instead. Ten of those sites * them raw because there was nothing here to call instead. Ten of those sites
* are the same idiom written out by hand -- a length check, then strncpy, then * are the same idiom written out by hand -- a length check, then strncpy, then
* an explicit NUL -- which is exactly the "truncation reported as an error * an explicit NUL -- which is exactly the "truncation reported as an error
* rather than silently accepted" that TODO.md 3.1 asks for. * rather than silently accepted" that the wrapper contract asks for.
* *
* Two conventions run through the whole file. * Two conventions run through the whole file.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* NEGATIVE COMPILE TEST -- TODO.md sections 1.3 and 2.2.5. * NEGATIVE COMPILE TEST.
* *
* This file must NOT compile. It is built by the CTest entry * This file must NOT compile. It is built by the CTest entry
* `negative_format_mismatch` with -Werror, and that test is marked WILL_FAIL. * `negative_format_mismatch` with -Werror, and that test is marked WILL_FAIL.

View File

@@ -1,5 +1,5 @@
/* /*
* NEGATIVE COMPILE TEST -- TODO.md section 1.9. * NEGATIVE COMPILE TEST.
* *
* This file must NOT compile. It is built by the CTest entry `negative_noignore` * This file must NOT compile. It is built by the CTest entry `negative_noignore`
* with -Werror, and that test is marked WILL_FAIL, so a successful build is a * with -Werror, and that test is marked WILL_FAIL, so a successful build is a

View File

@@ -1,5 +1,5 @@
/* /*
* List and tree additions -- src/collections.c, TODO.md section 3.6. * List and tree additions -- src/collections.c.
* *
* The bare-node list functions, the tracked aksl_List container, and the binary * The bare-node list functions, the tracked aksl_List container, and the binary
* search tree. The hash map and string buffer have their own files. * search tree. The hash map and string buffer have their own files.
@@ -407,7 +407,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *free_that_fails_once(void *ptr)
* single walk. Only the first is kept and handed back; the rest have to be * single walk. Only the first is kept and handed back; the rest have to be
* released, or a walk over n nodes with a broken free would consume n pool slots * released, or a walk over n nodes with a broken free would consume n pool slots
* and exhaust the pool -- the failure mode the whole pool-accounting section of * and exhaust the pool -- the failure mode the whole pool-accounting section of
* TODO.md 1.9 exists to catch. * the cross-cutting wrapper contract exists to catch.
*/ */
static akerr_ErrorContext AKERR_NOIGNORE *free_that_always_fails(void *ptr) static akerr_ErrorContext AKERR_NOIGNORE *free_that_always_fails(void *ptr)
{ {
@@ -800,7 +800,7 @@ static int test_tree_insert_orders_the_leaves(void)
} }
/* /*
* TODO.md 2.2.15: aksl_TreeNode.parent was declared and never touched by * aksl_TreeNode.parent was declared and never touched by
* anything in the library. These are the functions that set it, and * anything in the library. These are the functions that set it, and
* aksl_tree_remove is the one that needs it. * aksl_tree_remove is the one that needs it.
*/ */

View File

@@ -1,7 +1,7 @@
/* /*
* String -> number wrappers: aksl_atoi / atol / atoll / atof. * String -> number wrappers: aksl_atoi / atol / atoll / atof.
* *
* TODO.md section 1.4, complete. The cases that used to live in * Numeric conversion, complete. The cases that used to live in
* tests/test_convert_strict.c -- registered as a known failure because the ato* * tests/test_convert_strict.c -- registered as a known failure because the ato*
* family had no error channel at all (2.1.5) -- are folded back in here now that * family had no error channel at all (2.1.5) -- are folded back in here now that
* they pass: non-numeric input, empty input, trailing junk and overflow are all * they pass: non-numeric input, empty input, trailing junk and overflow are all
@@ -95,7 +95,7 @@ static int test_trailing_junk_is_a_value_error(void)
/* /*
* "0x10" through the ato* forms is base 10, so parsing stops at the 'x' and the * "0x10" through the ato* forms is base 10, so parsing stops at the 'x' and the
* rest is trailing junk. TODO.md 1.4 left this open; the answer is that the * rest is trailing junk. The wrapper plan left this open; the answer is that the
* prefix-honouring parse is aksl_strtol(nptr, NULL, 0, &dest), and tests/ * prefix-honouring parse is aksl_strtol(nptr, NULL, 0, &dest), and tests/
* test_strto.c holds that half. * test_strto.c holds that half.
*/ */
@@ -149,7 +149,7 @@ static int test_atoi_narrows_to_int_range(void)
return 0; return 0;
} }
/* TODO.md 1.4: the type boundaries round-trip exactly rather than nearly. */ /* The type boundaries round-trip exactly rather than nearly. */
static int test_type_boundaries_round_trip(void) static int test_type_boundaries_round_trip(void)
{ {
char buf[64]; char buf[64];
@@ -229,7 +229,7 @@ static int test_atof_converts_and_rejects_null(void)
} }
/* /*
* TODO.md 1.4 left "inf" and "nan" open. They are accepted, because strtod(3) * The wrapper plan left "inf" and "nan" open. They are accepted, because strtod(3)
* accepts them and because they are exact round-trips rather than approximations * accepts them and because they are exact round-trips rather than approximations
* of something else -- a caller who did not want them has a domain check to make * of something else -- a caller who did not want them has a domain check to make
* that this library cannot make for it. * that this library cannot make for it.

View File

@@ -2,7 +2,7 @@
* 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.
* *
* TODO.md section 1.3, now complete. Each happy path asserts both halves of the * Formatted output, complete. Each happy path asserts both halves of the
* contract -- the byte count handed back through *count and the text that * contract -- the byte count handed back through *count and the text that
* actually landed somewhere -- and every pointer argument is checked for its * actually landed somewhere -- and every pointer argument is checked for its
* NULL guard. * NULL guard.
@@ -11,7 +11,7 @@
* 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
* wrappers, which tests/test_stream.c covers. * wrappers, which tests/test_stream.c covers.
* *
* aksl_sprintf is gone (TODO.md 2.2.4) and aksl_snprintf takes its place, so the * aksl_sprintf is gone and aksl_snprintf takes its place, so the
* destination-overflow case that could not previously be written is here: it is * destination-overflow case that could not previously be written is here: it is
* AKERR_OUTOFBOUNDS, not the short success snprintf(3) would have reported. * AKERR_OUTOFBOUNDS, not the short success snprintf(3) would have reported.
*/ */
@@ -139,7 +139,7 @@ static int test_fprintf_writes_to_stream(void)
/* /*
* vfprintf on a stream opened "r" fails outright, so the wrapper reports the * vfprintf on a stream opened "r" fails outright, so the wrapper reports the
* errno it saw (EBADF on glibc). *count is 0 afterwards, not vfprintf's -1: * errno it saw (EBADF on glibc). *count is 0 afterwards, not vfprintf's -1:
* TODO.md 1.3 recorded the negative count as a contract gap, and this is the * The wrapper plan recorded the negative count as a contract gap, and this is the
* assertion that closes it. * assertion that closes it.
*/ */
static int test_fprintf_to_read_only_stream_reports_errno(void) static int test_fprintf_to_read_only_stream_reports_errno(void)
@@ -270,7 +270,7 @@ static int test_asprintf_allocates_to_fit(void)
} }
/* /*
* The va_list forms are what the variadic ones are built on, and TODO.md 3.1 * The va_list forms are what the variadic ones are built on, and the wrapper contract
* wanted them exposed so consumers can write their own variadic wrappers. This * wanted them exposed so consumers can write their own variadic wrappers. This
* is a consumer doing exactly that. * is a consumer doing exactly that.
*/ */
@@ -304,7 +304,7 @@ static int test_va_list_forms_are_usable_from_outside(void)
} }
/* /*
* Regression cover for the missing va_end (TODO.md 2.1.4). Nothing here can * Regression cover for the missing va_end. Nothing here can
* assert on register-save state directly; the point is to run the variadic * assert on register-save state directly; the point is to run the variadic
* wrappers enough times, with enough arguments, that the sanitizer build has * wrappers enough times, with enough arguments, that the sanitizer build has
* something to trip over. * something to trip over.

View File

@@ -1,5 +1,5 @@
/* /*
* The fixed-capacity hash map and FNV-1a -- src/collections.c, TODO.md 3.6. * The fixed-capacity hash map and FNV-1a -- src/collections.c.
* *
* "The single most obviously-missing data structure in the library", by the * "The single most obviously-missing data structure in the library", by the
* TODO's own account: akbasic needed one three times over -- variables, * TODO's own account: akbasic needed one three times over -- variables,

View File

@@ -1,5 +1,5 @@
/* /*
* Linked list -- TODO.md section 1.7, complete. * Linked list.
* *
* The two confirmed list defects are fixed, so the tests that used to live in * The two confirmed list defects are fixed, so the tests that used to live in
* tests/test_list_append_chain.c and tests/test_list_iterate_head.c are folded * tests/test_list_append_chain.c and tests/test_list_iterate_head.c are folded
@@ -59,7 +59,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_ListNode *node, void
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
/* /*
* TODO.md 2.2.14: every caller used to have to remember to memset a node before * every caller used to have to remember to memset a node before
* its first use, and a stack node that skipped it walked straight into garbage. * its first use, and a stack node that skipped it walked straight into garbage.
*/ */
static int test_node_init_zeroes_the_links(void) static int test_node_init_zeroes_the_links(void)
@@ -103,7 +103,7 @@ static int test_append_single_node(void)
* The defect that made this library's list unusable: `tail` was assigned from * The defect that made this library's list unusable: `tail` was assigned from
* Floyd's `slow` cursor *before* slow advanced, so it tracked the node behind * Floyd's `slow` cursor *before* slow advanced, so it tracked the node behind
* the midpoint rather than the last node. Appending n1..n4 to n0 produced the * the midpoint rather than the last node. Appending n1..n4 to n0 produced the
* chain "n0 -> n4" and silently dropped n1, n2 and n3. TODO.md 2.1.1. * chain "n0 -> n4" and silently dropped n1, n2 and n3.
*/ */
static int test_append_builds_the_whole_chain(void) static int test_append_builds_the_whole_chain(void)
{ {
@@ -218,7 +218,7 @@ static int test_append_detects_cycle_below_the_head(void)
} }
/* /*
* TODO.md 1.7 asked for the aliasing contract to be defined. It is refusal: * The wrapper plan asked for the aliasing contract to be defined. It is refusal:
* relinking a node that is already in the list would orphan everything between * relinking a node that is already in the list would orphan everything between
* its old position and the tail, so the tail walk -- which happens anyway -- * its old position and the tail, so the tail walk -- which happens anyway --
* doubles as the check. * doubles as the check.
@@ -281,7 +281,7 @@ static int test_iterate_single_node(void)
* Every node, exactly once, in order, starting at the head. The cycle check * Every node, exactly once, in order, starting at the head. The cycle check
* leaves Floyd's `slow` cursor at the list midpoint, and the visiting loop used * leaves Floyd's `slow` cursor at the list midpoint, and the visiting loop used
* to start from there -- so the whole first half of the list, head included, was * to start from there -- so the whole first half of the list, head included, was
* never passed to the callback at all. TODO.md 2.1.2. * never passed to the callback at all.
*/ */
static int test_iterate_visits_every_node_from_the_head(void) static int test_iterate_visits_every_node_from_the_head(void)
{ {
@@ -470,7 +470,7 @@ static int test_pop_middle_node(void)
} }
/* /*
* TODO.md 2.2.12: popping the head used to leave the caller's own head pointer * popping the head used to leave the caller's own head pointer
* aimed at a node that was no longer in the list, with no way to learn the new * aimed at a node that was no longer in the list, with no way to learn the new
* one. That is what the head out-param is for, and this is the assertion. * one. That is what the head out-param is for, and this is the assertion.
*/ */
@@ -572,7 +572,7 @@ static int test_pop_then_iterate(void)
} }
/* /*
* TODO.md 1.7's pool-accounting case. AKSL_RUN already asserts that each test * The wrapper plan's pool-accounting case. AKSL_RUN already asserts that each test
* leaves the pool as it found it; this one drives enough failures in a row to * leaves the pool as it found it; this one drives enough failures in a row to
* exhaust the pool several times over, which is where a wrapper that raises an * exhaust the pool several times over, which is where a wrapper that raises an
* error and forgets to release it shows up as an outright exhaustion rather than * error and forgets to release it shows up as an outright exhaustion rather than

View File

@@ -1,8 +1,8 @@
/* /*
* Memory wrappers -- TODO.md section 1.1, now complete, plus the additions from * Memory wrappers, complete, plus the additions from
* section 3.1. * section 3.1.
* *
* The three cases 1.1 left open are all pinned here: malloc(0) is AKERR_VALUE * The three cases the wrapper plan left open are all pinned here: malloc(0) is AKERR_VALUE
* rather than whatever errno happened to hold when the platform's malloc(0) * rather than whatever errno happened to hold when the platform's malloc(0)
* returned NULL; an allocation the system cannot satisfy reports ENOMEM and * returned NULL; an allocation the system cannot satisfy reports ENOMEM and
* leaves *dst NULL rather than garbage; and overlapping memcpy is refused with * leaves *dst NULL rather than garbage; and overlapping memcpy is refused with
@@ -32,7 +32,7 @@ static int test_malloc_rejects_null_destination(void)
} }
/* /*
* TODO.md 1.1 asked for this contract to be pinned down. malloc(0) is allowed to * The wrapper plan 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 * 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 * 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 * 0, which every DETECT downstream reads as success while the context holds a
@@ -296,7 +296,7 @@ static int test_memcpy_zero_length_is_noop(void)
} }
/* /*
* TODO.md 1.1's open question, decided: overlap is AKERR_VALUE. memcpy(3) calls * The wrapper plan's open question, decided: overlap is AKERR_VALUE. memcpy(3) calls
* it undefined behaviour, which in practice means "works until the day a * 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 * compiler version or a length changes and it does not". Callers who mean to
* overlap want aksl_memmove, and the message says so. * overlap want aksl_memmove, and the message says so.

View File

@@ -1,12 +1,12 @@
/* /*
* aksl_realpath and aksl_realpath_alloc -- TODO.md section 1.5, now complete. * aksl_realpath and aksl_realpath_alloc.
* *
* The happy paths compare against realpath(3) itself rather than against a * The happy paths compare against realpath(3) itself rather than against a
* hard-coded string, because $TMPDIR may itself be a symlink (/tmp -> /private/tmp * hard-coded string, because $TMPDIR may itself be a symlink (/tmp -> /private/tmp
* and friends) and the resolved answer is what the platform says it is. * and friends) and the resolved answer is what the platform says it is.
* *
* The failure cases now pass an *uninitialised* resolved_path on purpose. That * The failure cases now pass an *uninitialised* resolved_path on purpose. That
* used to be the crash case (TODO.md 2.1.6): the wrapper's own error path * used to be the crash case: the wrapper's own error path
* formatted the buffer with %s while realpath(3) leaves its contents * formatted the buffer with %s while realpath(3) leaves its contents
* unspecified on failure, so the library read uninitialised memory while * unspecified on failure, so the library read uninitialised memory while
* reporting an error. The message names only the input path now, and this test * reporting an error. The message names only the input path now, and this test
@@ -122,7 +122,7 @@ static int test_rejects_null_arguments(void)
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath(NULL, resolved, sizeof(resolved)), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath(NULL, resolved, sizeof(resolved)),
AKERR_NULLPOINTER, "path="); AKERR_NULLPOINTER, "path=");
/* /*
* TODO.md 2.1.6: this used to be unchecked, and realpath(path, NULL) * this used to be unchecked, and realpath(path, NULL)
* allocated a buffer that the wrapper then discarded and leaked. * allocated a buffer that the wrapper then discarded and leaked.
*/ */
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath("/tmp", NULL, PATH_MAX), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath("/tmp", NULL, PATH_MAX),

View File

@@ -1,5 +1,5 @@
/* /*
* Cross-cutting properties every wrapper has to have -- TODO.md section 1.9. * Cross-cutting properties every wrapper has to have.
* *
* Two of them, and neither is about what any individual function computes: * Two of them, and neither is about what any individual function computes:
* *

View File

@@ -1,5 +1,5 @@
/* /*
* The growable string buffer -- src/collections.c, TODO.md 3.6. * The growable string buffer -- src/collections.c.
* *
* The bounded formatting wrappers are the right answer when the destination is * The bounded formatting wrappers are the right answer when the destination is
* a fixed buffer and no answer at all when the length is not known in advance. * a fixed buffer and no answer at all when the length is not known in advance.

View File

@@ -1,7 +1,7 @@
/* /*
* Stream wrappers: aksl_fopen / aksl_fread / aksl_fwrite / aksl_fclose. * Stream wrappers: aksl_fopen / aksl_fread / aksl_fwrite / aksl_fclose.
* *
* TODO.md section 1.2, now complete. The happy paths, the round trip, every * Stream wrappers, complete. The happy paths, the round trip, every
* NULL guard, both stream-error statuses, the transferred-member count that * NULL guard, both stream-error statuses, the transferred-member count that
* aksl_fread and aksl_fwrite report through nmemb_out, and the three cases that * aksl_fread and aksl_fwrite report through nmemb_out, and the three cases that
* needed a hostile file to produce: a mode-denied path (EACCES), a full device * needed a hostile file to produce: a mode-denied path (EACCES), a full device
@@ -81,7 +81,7 @@ static int test_fopen_rejects_null_arguments(void)
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, "r", NULL), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, "r", NULL),
AKERR_NULLPOINTER, "fp="); AKERR_NULLPOINTER, "fp=");
/* TODO.md 2.2.2: both of these used to go straight through to fopen(3). */ /* both of these used to go straight through to fopen(3). */
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(NULL, "r", &fp), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(NULL, "r", &fp),
AKERR_NULLPOINTER, "pathname="); AKERR_NULLPOINTER, "pathname=");
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, NULL, &fp), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, NULL, &fp),
@@ -144,7 +144,7 @@ static int test_fread_short_read_is_eof_and_reports_the_count(void)
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp)); AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, &moved), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, &moved),
AKERR_EOF, "EOF"); AKERR_EOF, "EOF");
/* TODO.md 2.2.3: this is what the caller could not previously find out. */ /* this is what the caller could not previously find out. */
AKSL_CHECK(moved == 4); AKSL_CHECK(moved == 4);
AKSL_CHECK_OK(aksl_fclose(fp)); AKSL_CHECK_OK(aksl_fclose(fp));
@@ -160,7 +160,7 @@ static int test_fread_short_read_is_eof_and_reports_the_count(void)
* *
* That is a change from the old wrapper, which read ferror() and reported * That is a change from the old wrapper, which read ferror() and reported
* AKERR_IO without ever consulting errno. Routing it through AKSL_ERRNO_OR * AKERR_IO without ever consulting errno. Routing it through AKSL_ERRNO_OR
* (TODO.md 2.2.1) keeps AKERR_IO as the fallback for the case where the stream * keeps AKERR_IO as the fallback for the case where the stream
* is in error and errno says nothing, and hands back the real reason otherwise. * is in error and errno says nothing, and hands back the real reason otherwise.
*/ */
static int test_fread_from_write_only_stream_reports_errno(void) static int test_fread_from_write_only_stream_reports_errno(void)
@@ -194,7 +194,7 @@ static int test_fread_rejects_null_arguments(void)
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), NULL, &moved), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), NULL, &moved),
AKERR_NULLPOINTER, "fp="); AKERR_NULLPOINTER, "fp=");
/* TODO.md 2.2.3: ptr was never checked in either direction. */ /* ptr was never checked in either direction. */
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(NULL, 1, sizeof(buf), fp, &moved), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(NULL, 1, sizeof(buf), fp, &moved),
AKERR_NULLPOINTER, "ptr="); AKERR_NULLPOINTER, "ptr=");
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, NULL), AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, NULL),

View File

@@ -1,5 +1,5 @@
/* /*
* Stream wrappers beyond open/read/write/close -- src/stream.c, TODO.md 3.1. * Stream wrappers beyond open/read/write/close -- src/stream.c.
* *
* tests/test_stream.c covers fopen/fread/fwrite/fclose. This one covers * tests/test_stream.c covers fopen/fread/fwrite/fclose. This one covers
* positioning, flushing, character and line I/O, stream state, and formatted * positioning, flushing, character and line I/O, stream state, and formatted

View File

@@ -1,5 +1,5 @@
/* /*
* aksl_strhash_djb2 -- TODO.md section 1.6. * aksl_strhash_djb2.
* *
* The expected values are the canonical djb2 ones: h = 5381, then * The expected values are the canonical djb2 ones: h = 5381, then
* h = h * 33 + byte for each of len bytes, truncated to 32 bits. They were * h = h * 33 + byte for each of len bytes, truncated to 32 bits. They were
@@ -7,7 +7,7 @@
* *
* Most vectors here are 7-bit ASCII, where signed and unsigned char agree and * Most vectors here are 7-bit ASCII, where signed and unsigned char agree and
* the test therefore says nothing either way about byte signedness. The high-bit * the test therefore says nothing either way about byte signedness. The high-bit
* vector is the one that pins TODO.md 2.2.6 down. * vector is the one that pins the sign-extension defect down.
*/ */
#include "aksl_capture.h" #include "aksl_capture.h"
@@ -47,7 +47,7 @@ static int test_known_answer_vectors(void)
} }
/* /*
* TODO.md 2.2.6, pinned. The cursor is an unsigned char * now, so bytes at or * Pinned. The cursor is an unsigned char * now, so bytes at or
* above 0x80 contribute their unsigned value. Iterating a plain char * on x86 or * above 0x80 contribute their unsigned value. Iterating a plain char * on x86 or
* ARM Linux made them negative, giving 5859874 here instead of 5868578 -- a hash * ARM Linux made them negative, giving 5859874 here instead of 5868578 -- a hash
* that disagreed with canonical djb2 and, worse, disagreed with itself across * that disagreed with canonical djb2 and, worse, disagreed with itself across
@@ -68,7 +68,7 @@ static int test_high_bit_bytes_are_unsigned(void)
return 0; return 0;
} }
/* The NUL-terminated convenience form (TODO.md 3.6) agrees with the length one. */ /* The NUL-terminated convenience form agrees with the length one. */
static int test_str_form_matches_the_length_form(void) static int test_str_form_matches_the_length_form(void)
{ {
const char *s = "libakstdlib"; const char *s = "libakstdlib";

View File

@@ -1,5 +1,5 @@
/* /*
* String wrappers -- src/string.c, TODO.md section 3.1. * String wrappers -- src/string.c.
* *
* The two contracts worth testing hardest are the ones that differ from libc: * The two contracts worth testing hardest are the ones that differ from libc:
* every copying function takes the destination size and treats truncation as an * every copying function takes the destination size and treats truncation as an

View File

@@ -1,5 +1,5 @@
/* /*
* The strto* family -- TODO.md section 3.1. * The strto* family.
* *
* These are the real implementation behind the ato* wrappers and the thing * These are the real implementation behind the ato* wrappers and the thing
* akbasic had to hand-write for itself (its src/convert.c, ~60 lines) because * akbasic had to hand-write for itself (its src/convert.c, ~60 lines) because

View File

@@ -1,5 +1,5 @@
/* /*
* Tree traversal -- TODO.md section 1.8, complete. * Tree traversal.
* *
* The old version of this file counted steps, which cannot tell the three * The old version of this file counted steps, which cannot tell the three
* depth-first orders apart because all three visit all seven nodes -- and could * depth-first orders apart because all three visit all seven nodes -- and could
@@ -183,7 +183,7 @@ static int test_dfs_is_an_alias_for_preorder(void)
/* /*
* BFS was AKERR_NOT_IMPLEMENTED, and the lalloc/lfree parameters that existed to * BFS was AKERR_NOT_IMPLEMENTED, and the lalloc/lfree parameters that existed to
* serve it were defaulted and then never called -- TODO.md 2.2.8 and 2.2.10. * serve it were defaulted and then never called. See UPGRADING.md.
* Both modes work now, and the allocator test below proves the queue is real. * Both modes work now, and the allocator test below proves the queue is real.
*/ */
static int test_bfs_visits_level_by_level(void) static int test_bfs_visits_level_by_level(void)
@@ -249,7 +249,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *counting_free(void *ptr)
} }
/* /*
* TODO.md 1.8: "Custom lalloc/lfree are actually invoked -- currently they are * The wrapper plan asked that "custom lalloc/lfree are actually invoked -- currently they are
* stored and never called". They are called now, once per node enqueued, and * stored and never called". They are called now, once per node enqueued, and
* every allocation is released. The depth-first modes allocate nothing at all, * every allocation is released. The depth-first modes allocate nothing at all,
* which is the other half of the contract. * which is the other half of the contract.
@@ -382,7 +382,7 @@ static int test_degenerate_chains(void)
} }
/* /*
* TODO.md 1.8 / 2.2.7: a chain deeper than the recursion can take. It used to * A chain deeper than the recursion can take. It used to
* overflow the stack; it is AKERR_OUTOFBOUNDS now, and the message names the * overflow the stack; it is AKERR_OUTOFBOUNDS now, and the message names the
* documented limit. Built one node past the cap so the failure is the cap itself * documented limit. Built one node past the cap so the failure is the cap itself
* and not some incidental shortfall. `static` because AKSL_TREE_MAX_DEPTH nodes * and not some incidental shortfall. `static` because AKSL_TREE_MAX_DEPTH nodes
@@ -488,7 +488,7 @@ static int test_cyclic_tree_is_caught(void)
* that raised the break handled it in its own PROCESS/HANDLE block and returned * that raised the break handled it in its own PROCESS/HANDLE block and returned
* success, so the parent frame's PASS saw nothing wrong and carried straight on * success, so the parent frame's PASS saw nothing wrong and carried straight on
* into the sibling subtree. All seven nodes were visited no matter where the * into the sibling subtree. All seven nodes were visited no matter where the
* break was raised. TODO.md 2.1.3. * break was raised.
* *
* One case per order, each breaking on a node that is *not* last in that order -- * One case per order, each breaking on a node that is *not* last in that order --
* which is precisely what the old test could not do, because it hid its target * which is precisely what the old test could not do, because it hid its target
@@ -579,7 +579,7 @@ static int test_null_arguments(void)
} }
/* /*
* TODO.md 2.2.9: the switch had no default, so an unrecognised mode -- and * the switch had no default, so an unrecognised mode -- and
* AKSL_TREE_SEARCH_VISIT, which the header documented but nothing implemented -- * AKSL_TREE_SEARCH_VISIT, which the header documented but nothing implemented --
* fell straight through to SUCCEED_RETURN having visited nothing at all. A * fell straight through to SUCCEED_RETURN having visited nothing at all. A
* traversal that silently did not happen, reported as success. * traversal that silently did not happen, reported as success.

View File

@@ -1,5 +1,5 @@
/* /*
* Version reporting -- TODO.md section 2.2.16. * Version reporting.
* *
* There are two versions in play and the whole point of this API is that they * There are two versions in play and the whole point of this API is that they
* are allowed to differ: * are allowed to differ: