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>
This commit is contained in:
2026-08-02 22:01:27 -04:00
parent d6a1cd8ca8
commit 55d986c631
27 changed files with 96 additions and 94 deletions

View File

@@ -18,7 +18,7 @@
* 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,
* 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))

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
* 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
* 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
* 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
* 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.
*
* 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
* node's parent about it, and finding the parent by walking from the root again
* 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
* rather better -- worth having when the keys are short and share a prefix,
* 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.
*
* 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
* 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.
@@ -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
* 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
* 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
* 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 --
* 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)
{
@@ -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.
* 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
* 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
* unconditionally, so the old FAIL_ZERO_RETURN(e, memset(...), ...) and
* (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.
*/
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
* this wrapper used to hand both straight through unexamined -- reachable from
* 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(
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
* 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
* 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
@@ -405,7 +405,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream)
* Formatted output.
*
* 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
* 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
* 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
* 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
* 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
@@ -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
* error-handling wrapper around an unbounded write is precisely the sharp edge
* 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)
@@ -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
* anyway would mean a failure branch nothing can reach and a free() beneath
* it that nothing can execute -- dead code that reads as though there were a
* failure mode to catch, which is exactly what TODO.md 2.2.11 recorded
* failure mode to catch, which is exactly what was recorded
* against the old aksl_memset and aksl_memcpy and what removing those was
* for. The invariant is stated here instead, where it can be read.
*/
@@ -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(3) has no error channel at all. A library whose entire value proposition
* 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
* 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
* 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.
*
* 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;
* 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
@@ -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
* 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
* 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)
{
@@ -921,7 +921,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(const char *str, size_t len
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)
{
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
* 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
* list of two or more nodes -- see TODO.md 2.1.1.
* list of two or more nodes.
*/
while ( fast != NULL && fast->next != NULL ) {
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
* 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.
*/
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
* 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)
{
@@ -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
* 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
* and never actually did (TODO.md 2.2.8).
* and never actually did.
*/
typedef struct TreeQueueEntry {
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
* 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
* 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
* 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
* 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(
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 --
* and AKSL_TREE_SEARCH_VISIT, which the header documented but nothing
* implemented -- fell straight through to success having visited nothing
* (TODO.md 2.2.9).
* See UPGRADING.md.
*/
switch ( searchmode ) {
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
* 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 ) {
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,
* 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
* 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
* 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
* 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.
*