Both found while auditing the pool for the cross-thread transfer guarantee. AGENTS.md said the two exit() calls that bypass akerr_exit() are "both in src/error.c". ENSURE_ERROR_READY's pool-exhaustion abort is in include/akerror.tmpl.h. The count of abort sites is load-bearing -- it is the argument against any API that could fail on pool exhaustion -- so pointing at the wrong file makes it hard to check. akerr_valid_error_address() returns 1 for NULL, which is right for VALID() (NULL is how a function reports success) and a trap for anything reading it as "is this a pool slot". Say so where the function is defined. Comments only; no behavior change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6.4 KiB
Repository Guidelines
Project Structure & Module Organization
This is a small C library built with CMake. Core implementation lives in
src/error.c. src/lock.h is private: it selects the threading backend
(pthread or none) and is not installed. The public header is generated at build
time from include/akerror.tmpl.h by scripts/generrno.sh, which also
generates src/errno.c under the build directory and stamps in whether the
build is thread safe. CMake package and pkg-config templates are in cmake/ and
akerror.pc.in. Tests are one-file C programs in tests/; shared test helpers
live beside them, such as tests/err_capture.h and tests/err_threads.h.
Build, Test, and Development Commands
Use an out-of-tree build:
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure
cmake -S . -B build configures the build and generates akerror.h.
cmake --build build compiles libakerror and test executables. ctest
runs the registered unit tests. To emit CI-style results, use:
ctest --test-dir build --output-on-failure --output-junit "$(pwd)/ctest-junit.xml"
The suite includes thread tests. The run that proves there is no data race under them is ThreadSanitizer:
scripts/thread_test.sh
which configures build/tsan with -DAKERR_SANITIZE=thread, builds the library
and the tests with it, and runs CTest with ASLR disabled (TSan aborts with an
"unexpected memory mapping" on kernels with vm.mmap_rnd_bits above 28).
AKERR_SANITIZE takes any sanitizer list, so -DAKERR_SANITIZE=address,undefined
works the same way. CTest gives each test halt_on_error=1, so a report fails
the test rather than being printed and passed over — running a sanitized test
binary by hand does not inherit that. Set it yourself
(TSAN_OPTIONS=halt_on_error=1 ./build/tsan/test_err_threads_pool) or the
binary can print a race and still exit 0.
Mutation testing is available through:
cmake --build build --target mutation
scripts/mutation_test.py --target src/error.c --threshold 65
Code coverage is available through:
cmake --build build --target coverage
scripts/coverage.py --threshold 90 --branch-threshold 50
scripts/coverage.py configures its own instrumented build tree (default
build/coverage, -DAKERR_COVERAGE=ON), runs the CTest suite there, and
reports gcov line/branch coverage per library source. Thresholds gate each
file as well as the total. Coverage measures the library's own sources only --
src/error.c, src/lock.h and the generated src/errno.c; the public header's
macros expand at their call sites, so mutation testing is what checks those.
To build without threads (no locking, no thread-local storage, thread tests not
registered), configure with -DAKERR_THREADS=none. auto is the default and
fails the configure rather than falling back.
Coding Style & Naming Conventions
Use C99-compatible C and follow the surrounding style. Functions and types use
the akerr_ prefix; macros and constants use AKERR_ or all-caps macro names
such as PREPARE_ERROR. Keep generated-code changes in templates or generator
scripts, not in build outputs. Preserve concise comments for invariants,
macro constraints, and non-obvious error lifecycle behavior.
Locking. One recursive lock (akerr_state_lock, see src/lock.h) covers
both the error pool and the status registry. A function named *_locked is
called with that lock already held; a function without the suffix takes it.
Never take it in a body written with the FAIL_*_RETURN macros — those return
from the middle of the function and would skip the unlock. Split it instead: the
locked body does the work, and a thin wrapper takes the lock, calls it, and
releases it on the single return path. Do not call consumer code
(akerr_log_method, akerr_handler_unhandled_error) while holding it.
Exiting. Never call exit() with a status value. Call akerr_exit(), which
owns the one mapping from an akerr status to an exit code: an exit status is a
byte, and every consumer status starts at 256, so passing a status through
exit() silently truncates it — status 256 exits 0 and reports success. The
only exits that bypass it are the two that have no status to map:
ENSURE_ERROR_READY's pool-exhaustion abort in include/akerror.tmpl.h, and the
NULL-context case in akerr_default_handler_unhandled_error() in src/error.c. This rule applies to test
programs too, except where the test's whole point is to observe the raw
truncation.
Testing Guidelines
Add tests as tests/err_<behavior>.c. Register each new test in the
AKERR_TESTS list in CMakeLists.txt; tests that intentionally abort must
also be listed in AKERR_WILL_FAIL_TESTS. Prefer focused executable tests that
return zero on success and use existing helpers such as AKERR_CHECK. Run the
CTest suite before submitting changes, and run mutation testing and coverage
when changing core control-flow, reference counting, stack-trace, or handler
behavior.
Tests that drive the library from several threads go in the AKERR_THREAD_SAFE
branch of the AKERR_TESTS list — an -DAKERR_THREADS=none build has no
threading to test — and use tests/err_threads.h, which runs a body on
AKERR_TEST_THREADS threads that meet at a barrier first.
Count failures per thread with AKERR_TCHECK rather than returning early: a
thread that abandons its work leaves the others holding pool slots and turns one
failure into a cascade. Anything the test shares between its own threads must go
through __atomic builtins — a race in the test is still a race, and
ThreadSanitizer cannot tell you whose it is. The capturing logger in
err_capture.h is single-threaded (shared buffer, shared length); use
akerr_thread_logger instead. Run scripts/thread_test.sh when changing
anything that touches the pool, the registry, initialization, or the lock.
Commit & Pull Request Guidelines
Recent commits use short, imperative, sentence-case subjects, for example
Fix refcount leak and stack-trace buffer overflow. Keep commits focused and
describe the observable behavior changed. Pull requests should include a brief
summary, tests run, and any compatibility impact for public macros, generated
headers, installation paths, or CMake/pkg-config consumers.
Agent-Specific Instructions
Do not overwrite uncommitted user changes. Avoid editing generated files in
build/; update include/akerror.tmpl.h, src/error.c, CMake files, tests,
or scripts instead.