Files
libakerror/docs/uncaught-errors.md
Andrew Kesterson 5695061130
Some checks failed
libakerror CI Build / cmake_build (push) Successful in 2m47s
libakerror CI Build / coverage (push) Successful in 2m48s
libakerror CI Build / thread_sanitizer (push) Failing after 2m49s
libakerror CI Build / mutation_test (push) Successful in 39m56s
Split the README reference material into docs/
The README was 794 lines: the summary, the design rationale, the whole macro
reference, the threading contract, the build internals and the exit-status
specification in one file. It is now 178 lines -- summary, installation,
quickstart, and an index -- and the reference material lives in docs/, one file
per topic: architecture, usage, status-codes, uncaught-errors, exit-status,
thread-safety, building.

The prose moved as written. Inbound references followed it: UPGRADING.md,
TODO.md, include/akerror.tmpl.h and tests/err_threads_handoff.c now name the
docs/ file that owns the text they cite, and AGENTS.md says where new
documentation goes so the README does not grow back.

Five factual errors fixed in the moved text:

- Both NULL-pointer examples inverted their test. FAIL_ZERO_* fails when the
  expression is zero, so `(somePointer == NULL)` failed on a *valid* pointer.
  They now read `(somePointer != NULL)`.
- AKERROR_NOIGNORE, four times including the #define, is AKERR_NOIGNORE.
- FINISH_NORExbTURN is FINISH_NORETURN.
- "functiions" is "functions".
- The architecture link pointed at include/akerror.h, which is generated and
  not in the tree; it points at include/akerror.tmpl.h.

The quickstart is new text. It compiles under -Wall -Wextra -Werror and was run
through all three of its paths: handled usage error exits 0, unhandled IO error
prints a trace and exits with the status, success exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:56:07 -04:00

3.0 KiB

Uncaught errors

Misbehaving methods

Any function which returns akerr_ErrorContext * and completes successfully MUST call SUCCEED_RETURN(errctx). Failure to do this may result in an invalid akerr_ErrorContext * being returned, which will cause an AKERR_BEHAVIOR error to be triggered from your code.

Ensuring that all error codes are captured

Any function which returns akerr_ErrorContext * should also be marked with AKERR_NOIGNORE.

akerr_ErrorContext AKERR_NOIGNORE *f(...);

This will cause a compile-time error if the return value of such a function is not used. "Used" here means assigned to a variable - it does not necessarily mean that the value is checked. However assuming that such functions are called inside of ATTEMPT { ... } blocks, it is safe to assume that such returns will be caught with CATCH(...); therefore this error is a generally effective safeguard against careless coding where errors are not checked.

Beware that AKERR_NOIGNORE is not a failsafe - it implements the warn_unused_result mechanic. By design users may explicitly ignore an error code from a function marked with warn_unused_result by explicitly casting the return to void.

#define AKERR_NOIGNORE __attribute__((warn_unused_result))

Stack Traces

Whenever an error is captured using the FAIL_* or CATCH methods, and is unhandled such that it manages to propagate all the way to the top of the caller stack without being managed, the last FINISH macro to touch the error will trigger a stack trace and kill the program.

Consider the tests/err_trace.c program which intentionally triggers this behavior. It produces output like this:

tests/err_trace.c:func2:7: 1 (Null Pointer Error) : This is a failure in func2
tests/err_trace.c:func2:10
tests/err_trace.c:func1:18: Detected error 0 from array (refcount 1)
tests/err_trace.c:func1:18
tests/err_trace.c:func1:21
tests/err_trace.c:main:30: Detected error 0 from array (refcount 1)
tests/err_trace.c:main:30
tests/err_trace.c:main:33: Unhandled Error 1 (Null Pointer Error): This is a failure in func2

From bottom to top, we have:

  • The last line printed is the FINISH macro call that triggered the stacktrace.
  • Above that, the CATCH() inside of main() which caught the exception from func1() but did not handle it
  • Above that, a statement that the error was detected in the CATCH() statement at the same line
  • Above that, the FINISH() macro in the func1 method which detected the presence of an unhandled error and returned it up the calling stack
  • Above that, the CATCH() macro in the func1 method which caught the error coming out of func2()
  • Above that, a statement that the error was detected in the CATCH() statement at the same line
  • Above that, the FINISH() macro in func2() which detected an unhandled error and passed it out of the function
  • Above that, a reference to the line where the FAIL() macro set the error code and provided the message which is printed here