Files
libakerror/tests/err_registry_init_order.c
Andrew Kesterson 64da04e83b
All checks were successful
libakerror CI Build / cmake_build (push) Successful in 2m47s
libakerror CI Build / coverage (push) Successful in 2m46s
libakerror CI Build / mutation_test (push) Successful in 14m56s
Raise errors from the status registry instead of returning codes
akerr_reserve_status_range() and akerr_register_status_name() returned
private int enumerations, which was the one place in the library where a
failure was not an akerr_ErrorContext *. They now return one like
everything else: NULL on success, and on refusal an error whose status is
a real code in the library's reserved band, so it can be CATCH-ed,
HANDLE-d, PASS-ed, or left to propagate into a stack trace. Both are
marked AKERR_NOIGNORE, so discarding the result warns at compile time.

AKERR_STATUS_RANGE_OK and AKERR_STATUS_NAME_OK are gone; the remaining
seven codes move into the AKERR_* offset span and get registered names.
AKERR_LAST_LIBRARY_STATUS replaces AKERR_BADEXC as the top of that span
in the reserved-band static assert and the exhaustiveness sweep.

The refusal detail that used to go straight to akerr_log_method now
travels in the error message, so a caller that handles the error decides
whether it is reported. The two-argument akerr_name_for_status() set path
is the exception: it returns a name and cannot raise, so it logs and
releases. akerr_init() likewise has no caller to raise into, so failing
to reserve its own band or name its own codes is logged and fatal --
that can only happen on a misconfigured build, and continuing would
degrade every later stack trace to "Unknown Error".

Move the 1.0.0 upgrade notice out of README.md into UPGRADING.md and
rewrite its return-code tables in terms of the statuses now raised.

Tests: ctest 29/29, coverage 97.5% line / 64.5% branch, mutation 77.5%
(was 77.6%; the new survivors are the fatal init path, which needs a
library built with an undersized name table -- TODO item 7).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 20:58:47 -04:00

56 lines
2.2 KiB
C

#include "akerror.h"
#include "err_capture.h"
#include <string.h>
/*
* A library may reserve its status range from its own init() before anything in
* the process has raised an error, i.e. before akerr_init() has run. That used
* to be silently destructive: akerr_init() clears the range and name tables, so
* whichever component first triggered it (via PREPARE_ERROR) wiped the earlier
* reservation, and the *next* component to claim the same range was told OK --
* producing exactly the undetected aliasing the registry exists to prevent.
*
* Every public registry entry point now calls akerr_init() itself, so the
* tables are only ever cleared before the first reservation, never after one.
*
* Note this test must not call akerr_init() or PREPARE_ERROR first -- the
* uninitialized entry is the whole point.
*/
int main(void)
{
akerr_capture_install();
/* Cold call: no akerr_init(), no PREPARE_ERROR anywhere yet. */
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(256, 16, "early-lib"));
AKERR_CHECK_SUCCEEDS(akerr_register_status_name("early-lib", 256, "Early Error"));
/* Something else now uses the library for the first time. */
akerr_init();
PREPARE_ERROR(e);
(void)e;
/* The early reservation and its name must both have survived. */
AKERR_CHECK(strcmp(akerr_name_for_status(256, NULL), "Early Error") == 0);
AKERR_CHECK_RAISES(akerr_reserve_status_range(256, 16, "late-lib"),
AKERR_STATUS_RANGE_OVERLAP);
AKERR_CHECK_MESSAGE_CONTAINS("early-lib");
AKERR_CHECK_RAISES(akerr_reserve_status_range(260, 2, "late-lib"),
AKERR_STATUS_RANGE_OVERLAP);
/* The library's own initialization still happened exactly once. */
AKERR_CHECK(strcmp(akerr_name_for_status(AKERR_NULLPOINTER, NULL),
"Null Pointer Error") == 0);
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(0, AKERR_RESERVED_STATUS_COUNT,
AKERR_LIBRARY_OWNER));
/* An identical repeat by the original owner is still idempotent. */
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(256, 16, "early-lib"));
/* Raising from a cold registry must not strand a pool slot either. */
AKERR_CHECK(akerr_slots_in_use() == 0);
fprintf(stderr, "err_registry_init_order ok\n");
return 0;
}