The bullet said breaking at tree[3] in pre-order should assert 4 visits. Pre-order is 0, 1, 3, 4, 2, 5, 6, so the correct count is 3 -- which is what tests/test_tree_iterate_break.c already asserts. The TODO was written before that test existed and had the number wrong, not the test. Also point the bullet at the existing pre-order test, and give the in-order and post-order traversals with their break points so the remaining work does not need the count re-derived. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
33 KiB
TODO
Working notes for libakstdlib — the akerror-wrapped libc surface.
Scope of the current library (include/akstdlib.h, src/stdlib.c): 16 libc wrappers
(fopen, fread, fwrite, fclose, malloc, free, memset, memcpy, printf,
fprintf, sprintf, atoi, atol, atoll, atof, realpath), one hash helper
(aksl_strhash_djb2), a doubly-linked list (append/pop/iterate) and a binary tree
iterator (aksl_tree_iterate).
Items marked [CONFIRMED] were reproduced by building the library and running a probe program against it, not just read off the source.
1. Unit tests that should be written
1.0 Test-harness prerequisites — DONE (branch feature/test-harness)
ctest is green: 5/5, no Not Run entries. Everything in §1.1–1.9 can now be written
against tests/aksl_capture.h and added to the AKSL_TESTS list.
-
The current tests cannot fail.
tests/test_linkedlist.casserted nothing at all; it printed node names and returned0unconditionally, so any of the list bugs in §2.1 would pass it. Rewritten as 15 assertion-based cases covering the append, iterate and pop behaviour that is correct today. -
tests/test_tree.cwas red.ctestreportedtree (Failed), exit 136, becauseparms.stepswas never reset between the three searches, so the second assertion compared an accumulated14against7. Each search now builds its own tree and params. The file records that its step counts do not actually distinguish the three traversal orders — real order assertions remain §1.8. -
Adopt libakerror's test conventions.
tests/aksl_capture.hprovidesAKSL_CHECK()(anNDEBUG-proof assert),AKSL_CHECK_STATUS()/AKSL_CHECK_OK()which run an akerror-returning call, assert on its status and release the context,AKSL_CHECK_MSG_CONTAINS(), a capturingakerr_log_method,aksl_slots_in_use()for pool-leak checks, and anAKSL_RUN()driver that additionally fails any test which leaks an error-pool slot. -
One test source per behaviour, registered with CTest via a list variable.
tests/test_<name>.c→ executabletest_<name>→ CTest test<name>, driven byAKSL_TESTSinCMakeLists.txt. -
Add a
WILL_FAILcategory. Two lists:AKSL_WILL_FAIL_TESTSfor tests that abort by design (empty for now) andAKSL_KNOWN_FAILING_TESTSfor the confirmed defects in §2.1 — see the note at the head of §2.1. -
Fix the four permanently-failing submodule tests.
deps/libakerroris addedEXCLUDE_FROM_ALL, so akerror'sadd_testentries registered but its test binaries were never built, andctestreportederr_catch,err_cleanup,err_trace,err_improper_closureas Not Run → failed on every run. Neither the pinned commit nor upstreammainguards that registration, CMake has no way to un-register a test, andset_tests_propertiescannot reach across directory scopes — soadd_testandset_tests_propertiesare shadowed for the duration of theadd_subdirectorycall. The dependency has its own CI; this suite now contains only this project's tests. -
Wire in a memory-error build.
cmake -S . -B build-asan -DAKSL_SANITIZE=ONadds-fsanitize=address,undefined -fno-sanitize-recover=allto the library, the tests and the vendored libakerror. Currently clean, and it is the intended way to test the items in §2 that only misbehave under instrumentation (uninitialised%sinaksl_realpath, unboundedvsprintf, missingva_end). -
Port
scripts/mutation_test.pyfrom libakerror. Retargeted atsrc/stdlib.candinclude/akstdlib.h(178 mutants) and exposed ascmake --build build --target mutation. CI runs the narrowersrc/stdlib.cset (173 mutants) as its ownmutation_testjob with--threshold 40and a JUnit report. Current score: 46.8% — 81 killed, 92 survived. The survivors are concentrated exactly where §1.1–1.9 have yet to be written:| surviving mutants | function | |---|---| | 10 | `aksl_tree_iterate` | | 6 each | `aksl_fread`, `aksl_fwrite`, `aksl_fprintf`, `aksl_sprintf` | | 5 | `aksl_printf` | | 4 each | `aksl_memcpy`, `aksl_realpath`, `aksl_strhash_djb2`, `aksl_list_append` | | 3 each | `aksl_malloc`, `aksl_free`, `aksl_memset`, `aksl_fopen`, `aksl_fclose`, `aksl_atoi`, `aksl_atol`, `aksl_atoll`, `aksl_atof` | | 1 | `aksl_list_iterate` | The threshold is a regression ratchet, not a quality bar; raise it as sections below land. Each surviving mutant is a concrete missing test — `mutation-junit.xml` lists them with `file:line` and the exact edit. -
Cap every test with a CTest
TIMEOUT. Found while measuring the above: a mutant that deletesfast = fast->next->nextturnsaksl_list_iterateinto an infinite loop, soctestwaited forever, the mutation harness killedctestat its own 120 s timeout, and the orphaned test binary kept spinning at 100% CPU while holding the pipe open — the run wedged and had to be killed by hand.TIMEOUT 30on every test makesctestreap its own child, so those mutants are now killed in 30 s and leave nothing behind. It also guards the real suite against the same class of bug. -
CI could not have run any of this.
actions/checkout@v4was not fetching submodules, soadd_subdirectory(deps/libakerror)had nothing to descend into and the configure step failed before ever reaching the tests. Addedsubmodules: recursive, and swappedcmake --build build --target testforctest --output-on-failureso a failure is diagnosable from the job log. The deeper problem — CI installinglibakerror@mainwhile the build actually compiles the pinned submodule — is §2.3.
1.1 Memory wrappers
aksl_malloc— happy path returns non-NULL and writes throughdst.aksl_malloc(n, NULL)→AKERR_NULLPOINTER, message content asserted.aksl_malloc(0, &p)— pin down the contract. Today the result depends on whether the platform'smalloc(0)returns NULL; if it does, the wrapper reportserrno, which may be0. See §2.2.1.aksl_malloc(SIZE_MAX, &p)→ allocation failure surfacesENOMEM, and*dstis left NULL rather than garbage.aksl_free(NULL)→AKERR_NULLPOINTER(assert the documented behaviour, sincefree(NULL)is legal C and callers will be surprised).aksl_freehappy path, and a malloc→free round trip under ASan.aksl_memset— happy path fills the buffer;n == 0is a no-op;s == NULL→AKERR_NULLPOINTER.aksl_memcpy— happy path copies;d == NULLands == NULLeach →AKERR_NULLPOINTER;n == 0with valid pointers succeeds.aksl_memcpywith overlapping regions — decide and test whether this is rejected (AKERR_VALUE) or documented as UB likememcpy.
1.2 File I/O
aksl_fopenhappy path on a temp file;*fpis written.aksl_fopen("/nonexistent/path", "r", &fp)→ENOENTpropagated as the status, with the pathname in the message.aksl_fopenon a mode-denied path (e.g./proc/1/mem, or achmod 000temp file) →EACCES.aksl_fopen(path, mode, NULL)→AKERR_NULLPOINTER.aksl_fopen(NULL, "r", &fp)andaksl_fopen(path, NULL, &fp)→ currently unchecked, see §2.2.2. Test once the guards exist.aksl_freadfull read; short read at EOF →AKERR_EOF; read from a write-only stream →AKERR_IO;fp == NULL→AKERR_NULLPOINTER;ptr == NULL→ should beAKERR_NULLPOINTER(see §2.2.3).aksl_freadpartial read that is neither EOF nor error — assert whatever the fixed contract is; today this silently returns success and the caller cannot tell how many members were read.aksl_fwritehappy path; write to a read-only stream →AKERR_IO; write to a full device (/dev/full) →ENOSPC/AKERR_IO;fp == NULL→AKERR_NULLPOINTER.aksl_fclosehappy path;NULL→AKERR_NULLPOINTER; double-close is caught or documented as UB.aksl_fcloseon a stream whose buffered flush fails (/dev/full) → non-zerofclosesurfaceserrno.- Round-trip test:
fopen→fwrite→fclose→fopen→fread→ compare bytes.
1.3 Formatted output
aksl_printf/aksl_fprintf/aksl_sprintfhappy paths, asserting both the byte count written throughcountand the produced text.- Each of the three with every pointer argument NULL in turn →
AKERR_NULLPOINTER. aksl_fprintfto a closed / read-only stream → error path, and confirm*countis not left holding-1as if it were a valid length.aksl_sprintfwith a format that overflows the destination — currently unbounded (§2.2.4). Test theaksl_snprintfreplacement once it exists.- Regression test for the missing
va_end(§2.1.4) — a test that calls each variadic wrapper many times in a loop, run under valgrind/ASan. - Format-string/arg mismatch is caught at compile time once
__attribute__((format(printf, ...)))is added (§2.2.5) — a negative compile test.
1.4 String → number
aksl_atoi/atol/atoll/atofhappy paths, including negative values and leading whitespace.- NULL
nptrand NULLdestfor each →AKERR_NULLPOINTER. - Non-numeric input (
"not a number") — [CONFIRMED] currently returns success with*dest == 0. Test theAKERR_VALUEbehaviour once §2.1.5 is fixed. - Overflow (
"99999999999999999999") — [CONFIRMED] currently returns success with a garbage value (-1on this box). Test forERANGE. - Empty string,
" ","12abc"(trailing junk),"0x10","inf"/"nan"foratof. LONG_MIN/LONG_MAX/LLONG_MIN/LLONG_MAXboundary strings round-trip exactly.
1.5 aksl_realpath
- Happy path on an existing file and on a symlink chain; result matches
realpath(3). - Non-existent path →
ENOENT. - A path component that is not a directory →
ENOTDIR. - Symlink loop →
ELOOP. path == NULL→AKERR_NULLPOINTER.resolved_path == NULL— currently unchecked and leaks (§2.1.6). Test once fixed.- A failure case where
resolved_pathis an uninitialised buffer — this is the crash case in §2.1.6; run it under ASan/MSan.
1.6 aksl_strhash_djb2
- Known-answer vectors:
djb2("")== 5381; a handful of fixed strings with their pre-computed 32-bit values. len == 0returns 5381 regardless ofstrcontents.- NULL
strand NULLhashval→AKERR_NULLPOINTER. - High-bit bytes (
"\xff\xfe") — pins down the sign-extension bug in §2.2.6; the expected value must be theunsigned charone. - Embedded NUL bytes are hashed (the function is length-driven, not NUL-driven).
- Same input → same output across two calls (no hidden state).
1.7 Linked list
aksl_list_appendbuilds a correct chain of N nodes — [CONFIRMED BROKEN], see §2.1.1. Appendingn1..n4ton0yields the chainn0 -> n4;n1,n2,n3are silently dropped. This is the single most important test to add.appendsetsobj->prevto the real tail andobj->nextto NULL.appendonto an empty (single, zeroed) node.appendNULL list / NULL obj →AKERR_NULLPOINTER.appendonto a list containing a cycle →AKERR_CIRCULAR_REFERENCE, for a self-loop, a 2-node cycle, and a cycle that does not include the head.appenda node that is already in the list (aliasing) — define and test the contract.aksl_list_iteratevisits every node exactly once, starting at the head — [CONFIRMED BROKEN], see §2.1.2: it starts iterating from the midpoint left behind by the cycle detector, so the head is never visited. Assert both the visit count and the visit order.iterateover a single-node list visits exactly one node.iterateNULL list / NULL iter →AKERR_NULLPOINTER.iterateover a cyclic list →AKERR_CIRCULAR_REFERENCE(self-loop, 2-node, tail-to-middle).iteratewhere the callback raisesAKERR_ITERATOR_BREAK→ iteration stops at that node, the wrapper returns success, and the visit count proves the early exit.iteratewhere the callback raises some other error → that error propagates out unchanged, with the callback's message intact.aksl_list_popon a middle node relinksprev/nextcorrectly and clears the popped node's pointers.popon the head node, on the tail node, and on a single-node list.pop(NULL)→AKERR_NULLPOINTER.poptheniterate— the list is still traversable and the popped node is gone.- Pool-accounting test: after a long sequence of list operations including failures,
akerr_slots_in_use() == 0.
1.8 Tree
- Visit order assertions, not just counts, for
DFS_PREORDER,DFS_INORDERandDFS_POSTORDERover the 7-node tree — record the visited node pointers into an array and compare against the expected sequence. The current test only counts steps, which cannot distinguish the three orders (all three visit 7 nodes). AKERR_ITERATOR_BREAKaborts the whole traversal — [CONFIRMED BROKEN], see §2.1.3. The pre-order case exists astests/test_tree_iterate_break.c: pre-order visits 0, 1, 3, so breaking attree[3]must stop the walk at 3 visits, and today it runs on to all 7. Add the equivalent for in-order (3, 1, 4, 0, 5, 2, 6 → breaking attree[4]gives 3) and post-order (3, 4, 1, 5, 6, 2, 0 → breaking attree[5]gives 4).AKSL_TREE_SEARCH_BFSandAKSL_TREE_SEARCH_BFS_RIGHT→AKERR_NOT_IMPLEMENTEDtoday; replace with real order assertions once implemented (§3 / §2.2.9).- Unknown
searchmode(e.g.99) — [CONFIRMED] currently returns success having visited nothing. Should beAKERR_VALUE. AKSL_TREE_SEARCH_VISIT(defined in the header,5) — [CONFIRMED] falls into the same silent-success hole. Either implement it or reject it.root == NULL/iter == NULL→AKERR_NULLPOINTER.- Single-node tree (no children) visits exactly once, in all three orders.
- Left-only and right-only degenerate chains.
- Callback raising a non-
ITERATOR_BREAKerror propagates out of the recursion with the original status and message. - Custom
lalloc/lfreeare actually invoked — currently they are stored and never called (§2.2.8), so this test will fail until BFS lands. - Deep/degenerate tree (e.g. 100k-node left chain) — documents the recursion-depth limit (§2.2.7).
- A tree containing a cycle (child pointing back at an ancestor) — currently infinite
recursion; test for
AKERR_CIRCULAR_REFERENCEonce guarded.
1.9 Cross-cutting
- Error-pool accounting: for every wrapper, a test that drives the failure path
AKERR_MAX_ARRAY_ERROR + 10times and asserts the pool does not leak (akerr_slots_in_use() == 0after each handled error). - Stack-trace content: assert that the file/function/line recorded by each wrapper's
FAILpoints atsrc/stdlib.cand the right function name. AKERR_NOIGNOREis effective: a negative compile test where a wrapper's return is discarded and-Werror=unused-resultfires.- Thread safety: libakerror's
AKERR_ARRAY_ERRORis a process-global array with no locking. Iflibakstdlibis meant to be callable from threads, add a concurrent smoke test under TSan — and if it is not, say so in the README.
2. Corner cases in existing functionality that should be resolved
2.1 Confirmed defects (reproduced against the built library)
The first three now have a failing test apiece, registered in AKSL_KNOWN_FAILING_TESTS
and therefore marked WILL_FAIL so the suite stays green while the gap stays visible:
tests/test_list_append_chain.c, tests/test_list_iterate_head.c and
tests/test_tree_iterate_break.c. When one of these defects is fixed, CTest reports that
test as failed with unexpectedly passed — that is the cue to move it into AKSL_TESTS.
-
aksl_list_appenddoes not find the tail — it silently truncates the list.src/stdlib.c:194. The function conflates Floyd cycle detection with tail-finding:tailis set toslowbeforeslowadvances, so it tracks the node behind the midpoint, not the tail. Appendingn1,n2,n3,n4ton0produces the chainn0 -> n4;n1–n3are unlinked and lost. The fix is to keep the cycle check but walk a separate cursor to the real tail (while (tail->next) tail = tail->next;), or run Floyd first and then walk to the end. -
aksl_list_iterateskips the first half of the list.src/stdlib.c:324. After the cycle-detection loop,slowis left at the list midpoint, and the visiting loop then starts fromslowinstead of fromlist. The head node is never passed to the callback. Fix: iterate fromlist, not fromslow. -
AKERR_ITERATOR_BREAKdoes not stop a tree traversal.src/stdlib.c:251. The recursive frame in which the callback raises the break handles it in its ownPROCESS/HANDLE(e, AKERR_ITERATOR_BREAK)block and returns success; the parent'sPASStherefore sees no error and continues on to the sibling subtree. Breaking attree[3]in pre-order still visits all 7 nodes. Fix by splitting the recursion: an internal helper that propagatesAKERR_ITERATOR_BREAKunhandled, and a public entry point that swallows it exactly once at the top.tests/test_tree.ccurrently passes only because the search target is the last node in all three orders. -
va_endis never called.src/stdlib.c:96,:109,:122each callva_startwith no matchingva_endon any path — happy or error. This is undefined behaviour per the C standard and leaks register-save state on some ABIs. -
aksl_atoi/atol/atoll/atofcannot report a conversion failure.src/stdlib.c:135–169.atoi("not a number")returns success with0;atoi("99999999999999999999")returns success with a wrapped value. Since the entire point of this library is turning silent libc failures into error contexts, these should be reimplemented overstrtol/strtoll/strtodwitherrno = 0before the call, anendptrcheck for "no digits consumed" and "trailing junk", and a range check — raisingAKERR_VALUEandERANGErespectively. Keep theatoi-compatible names but document the stricter contract, or addaksl_strtol-family wrappers alongside. -
aksl_realpathmishandlesresolved_path.src/stdlib.c:171.resolved_pathis never NULL-checked.realpath(path, NULL)is valid and mallocs a buffer, but the wrapper discardsresult, so the caller gets nothing and the buffer leaks.- On failure the message formats
resolved_pathwith%swhilerealpathleaves it unspecified — for the normal caller who passed an uninitialised stack buffer, the error path itself reads uninitialised memory and can crash. - There is no way to express the buffer's size, so callers must know to supply
PATH_MAXbytes. Consideraksl_realpath(const char *path, char *buf, size_t buflen), or an allocating variant that returns the malloc'd pointer through an out-param.
2.2 Latent issues and API contract gaps
-
errnois used as an error status where it may be stale.aksl_mallocreportserrnowhenmallocreturns NULL, butmalloc(0)may legitimately return NULL without settingerrno, producing aFAILwithstatus == 0— an error context that everyDETECT/CATCHwill read as success while still holding a pool slot. Guard everyerrno-sourced status with a fallback (errno ? errno : AKERR_IO) and seterrno = 0before the call being wrapped. -
aksl_fopendoes not validatepathnameormode.fopen(NULL, ...)is UB. AddAKERR_NULLPOINTERguards. -
aksl_fread/aksl_fwritelose the transfer count and hide short transfers.ptris never NULL-checked in either function.- Neither has an out-param for the number of members actually transferred, so a caller who
gets
AKERR_EOFcannot tell how much data arrived. Addsize_t *nmemb_out. - If
nmemr != nmembbut neitherfeofnorferroris set, both functions fall through toSUCCEED_RETURN— a short transfer reported as complete success. aksl_fwrite's error message reads"Error reading file"(src/stdlib.c:83), and checkingfeof()on a write path is meaningless.
-
aksl_sprintfwrapsvsprintf, which is unbounded. There is no way for a caller to bound the destination. Addaksl_snprintf/aksl_vsnprintfand consider deprecating thesprintfwrapper, or reject it outright — an "error-handling" wrapper around an unbounded write is a sharp edge the library exists to remove. -
No
formatattribute on the variadic wrappers. Adding__attribute__((format(printf, 2, 3)))(and the equivalents) restores the compile-time format/argument checking that callers lose by going through the wrapper. -
aksl_strhash_djb2sign-extends bytes ≥ 0x80.src/stdlib.c:181iterates achar *, which is signed on x86/ARM Linux, so high bytes contribute a sign-extended negative value and the hash differs from the canonical djb2 — and differs across platforms wherecharis unsigned. Iterate aconst unsigned char *. Also takeconst char *in the signature so callers need not cast away constness. -
aksl_tree_iteraterecursion is unbounded and cycle-blind. A deep or degenerate tree overflows the stack, and a child pointer that loops back to an ancestor recurses forever. Add a depth limit (raisingAKERR_OUTOFBOUNDS) and/or a visited check raisingAKERR_CIRCULAR_REFERENCE, consistent with what the list functions already do. -
lalloc,lfreeandqueueare dead parameters.lalloc/lfreeare defaulted and then never called;queueis entirely unused (it is the only-Wunused-parameterwarning in the file). The doc comment even tells the caller to "pass NULL here", which is a sign the queue belongs in an internal helper rather than the public signature. Either implement BFS so they are used, or drop them from the public API until it is. -
Unknown
searchmodevalues return success. Theswitchinaksl_tree_iteratehas nodefault:;searchmode == 99and the header-definedAKSL_TREE_SEARCH_VISIT(5) both fall straight through toSUCCEED_RETURNhaving visited nothing. Adddefault: FAIL_RETURN(e, AKERR_VALUE, ...). -
AKSL_TREE_SEARCH_BFS/BFS_RIGHTare unimplemented (AKERR_NOT_IMPLEMENTED), andAKSL_TREE_SEARCH_VISITis declared in the header with a documented meaning but no implementation anywhere. -
aksl_memset's andaksl_memcpy's inner checks are dead code.memsetreturnssandmemcpyreturnsd, both unconditionally; neither can fail. TheFAIL_ZERO_RETURN(e, memset(...), errno, ...)and(memcpy(...) == d)checks can never fire and only obscure the intent. Also,memcpywith overlapping ranges is UB — either document that or dispatch tomemmove. -
aksl_list_popcannot tell the caller the new head. Popping the head leaves the caller's head pointer dangling at a now-detached node. Add an out-param for the new head, or aaksl_list_pop(aksl_ListNode **head, aksl_ListNode *node)form. -
aksl_freedoes not clear the caller's pointer, so double-free remains easy. Consider anaksl_freep(void **ptr)that frees and NULLs. -
No initialisers for the public structs. Every caller must remember to
memsetanaksl_ListNode/aksl_TreeNodeto zero before use (both existing tests do). Addaksl_list_node_init/aksl_tree_node_initorAKSL_LIST_NODE_INITmacros. -
aksl_TreeNode.parentis declared but never set or read by any library function. -
Header hygiene: no
extern "C" { }guard for C++ consumers; no version macros (AKSL_VERSION_MAJOR…);akstdlib.hpulls instdio.h/stdlib.h/string.h/stdint.hinto every consumer's namespace. -
Doxygen coverage is 2 functions out of 20. A
Doxyfileexists but onlyaksl_tree_iterateandaksl_list_iteratehave doc comments. Every public function needs@param/@throws/@return, especially the ones whose contract deviates from libc (aksl_free(NULL)is an error;aksl_atoiwill become strict).
2.3 Build, CI and repository
CMakeLists.txt:4-6setsCMAKE_CXX_FLAGSin a C-only project, so-g -ggdb -pgnever reach the C compiler. UseCMAKE_C_FLAGS— or better, drop-pgfrom the default build entirely (it is what produces the straygmon.outsitting untracked in the repo root) and letCMAKE_BUILD_TYPEcontrol debug info.- No warning flags. Add
-Wall -Wextra(and ideally-Werrorin CI). The only current warning is the unusedqueueparameter, so the cost of turning them on is low. src/stdlib.ctriggers "ISO C99 requires at least one argument for the..." on roughly 20 lines under-Wpedantic, becauseFAIL_*is called with a bare message and no varargs. Either always pass an argument, or add a zero-arg-safe form in libakerror.- The
deps/libakerrorsubmodule is pinned 11 commits behindmain(pinned at4fad0ce "Add gitea workflow"; upstream is at4212ff0). The pin predates the refcount-leak fix, the stack-trace buffer-overflow fix, theAKERR_MAX_ERR_VALUEcorrection and the format-string fix. Bump it. - CI does not build against the submodule it pins.
.gitea/workflows/ci.yamlcloneslibakerror@mainand installs it, while the build it then runs is top-level and so compilesdeps/libakerrorat the pinned commit — two different libakerror versions depending on where you build, and the installed one is never actually linked. Pick one. (The submodule is at least present in CI now: §1.0 addedsubmodules: recursiveto the checkout, without which configure failed outright.) ctestwas red (treefailing plus four Not Run submodule tests) — fixed in §1.0. The build badge inREADME.mdmeans something again.- Untracked build litter in the repo root:
build/,gmon.out,CMakeLists.txt-akstdlib, and a dozen*~editor backups. Add a.gitignore(libakerror has one; this repo does not). README.mdis a build badge and nothing else. It needs at minimum: what the library is, the akerror wrapper contract, the list of wrapped functions, and the deviations from libc semantics.- Indentation is inconsistent —
src/stdlib.cmixes hard tabs and 4-space indents within the same functions. libakerror standardised on Stroustrup style (commite5f7616); apply the same here, ideally with a checked-in.clang-format.
3. libc functions not yet wrapped
Ordered roughly by how much a caller of this library would miss them. Each entry means "add
an aksl_-prefixed wrapper that turns the documented failure modes into an
akerr_ErrorContext".
3.1 High priority — gaps in areas the library already covers
Memory (stdlib.h, string.h)
calloc,realloc,reallocarray—realloc's "returns NULL and the old pointer is still valid" trap is exactly what this library should be hiding.aligned_alloc,posix_memalignmemmove,memcmp,memchr
Bounded string formatting (stdio.h)
snprintf,vsnprintf— needed to make §2.2.4 fixable.vprintf,vfprintf,vsprintf— theva_listforms, so consumers can build their own variadic wrappers on top.asprintf/vasprintf(GNU) as an allocating alternative.
String → number (stdlib.h)
strtol,strtoll,strtoul,strtoull,strtod,strtof,strtold— the correct foundation for §2.1.5.
Strings (string.h)
strlen,strnlenstrcpy,strncpy,strcat,strncat— with truncation reported as an error rather than silently accepted, which is the whole value proposition here.strdup,strndupstrcmp,strncmp,strcasecmp,strncasecmp,strcollstrchr,strrchr,strstr,strcasestr,strpbrk,strspn,strcspnstrtok_r,strsepstrerror_r
Stream I/O (stdio.h)
fseek,ftell,rewind,fgetpos,fsetpos,fseeko,ftellofflushfgets,fputs,fgetc/getc/getchar,fputc/putc/putchar,ungetcgetline,getdelimfreopen,fdopen,filenosetvbuf,setbufclearerr,feof,ferror— thin, but worth exposing so callers never touch rawFILE *internals.sscanf,fscanf,scanf— the return-value semantics (items matched vsEOF) are a classic silent-failure source.remove,rename,tmpfile,mkstemp,mkdtempperror— or an akerror-native equivalent.
3.2 POSIX file and process API (likely the next major surface)
unistd.h / fcntl.h
open,close,read,write,pread,pwrite,lseekreadv,writevdup,dup2,pipe,fcntlfsync,fdatasync,truncate,ftruncateunlink,link,symlink,readlink,rmdir,mkdiraccess,faccessat,chmod,fchmod,chown,fchown,umaskchdir,fchdir,getcwdisatty,ttyname_rsysconf,pathconfsleep,usleep,nanosleep
sys/stat.h
stat,fstat,lstat,fstatatstatvfs,fstatvfs
dirent.h
opendir,fdopendir,readdir,readdir_r,closedir,rewinddir,scandir
Process control
fork,execve/execvp/execlfamily,waitpid,waitposix_spawnsystem,popen,pclosegetpid,getppid,getuid,geteuid,setuid,setgidatexit,exit,_exit,abort— mostly to give akerror a hook on shutdown.getenv,setenv,unsetenv,putenv,clearenv
sys/mman.h
mmap,munmap,mprotect,msync,madvise
3.3 Time
time,clock_gettime,clock_getres,gettimeofdaylocaltime_r,gmtime_r,mktime,timegm,difftimestrftime,strptimeclock,times
3.4 Sorting, searching and misc stdlib.h
qsort,qsort_r,bsearch— comparator errors currently have nowhere to go; an akerror-aware comparator signature would be a genuine improvement.abs,labs,llabs,div,ldiv,lldivrand,srand,random,srandom,getrandom/arc4random
3.5 Lower priority / larger projects
- Sockets:
socket,bind,listen,accept,connect,send/sendto/sendmsg,recv/recvfrom/recvmsg,shutdown,setsockopt/getsockopt,getaddrinfo/freeaddrinfo/gai_strerror,inet_ntop/inet_pton - Multiplexing:
select,poll,ppoll,epoll_create1/epoll_ctl/epoll_wait - Signals:
sigaction,sigprocmask,sigemptyset/sigaddset,kill,raise,signalfd - Threads:
pthread_create/join/detach,pthread_mutex_*,pthread_cond_*,pthread_rwlock_*,sem_*— blocked on resolving the thread-safety question in §1.9 (libakerror's error pool is an unlocked process-global array). - Dynamic loading:
dlopen,dlsym,dlclose,dlerror - Locale / wide chars:
setlocale,mbstowcs,wcstombs,iconv_* - Math: the
math.hfunctions that seterrno/raise FP exceptions (sqrt,log,pow,acos, …) — probably better served by a dedicatedlibakmath.
3.6 Non-libc additions the current data structures imply
Not libc wrappers, but the existing list/tree API is visibly incomplete:
- List:
prepend,insert_after/insert_before,length,find,reverse,concat,free_all, reverse iteration, and a head/tail-tracking container type soappendis O(1) instead of O(n). - Tree:
insert,remove,find,height,count,free_all, and the BFS traversal thelalloc/lfree/queueparameters were designed for. - Hash map built on
aksl_strhash_djb2, plus additional hashes (FNV-1a, xxHash) and a NUL-terminatedaksl_strhash_djb2_strconvenience form. - Growable buffer / string-builder type, to make the
snprintfandstrcatwrappers pleasant to use.