Raise errors from the status registry instead of returning codes
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

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>
This commit is contained in:
2026-07-30 20:58:47 -04:00
parent ba2430bfa1
commit 64da04e83b
13 changed files with 741 additions and 380 deletions

View File

@@ -39,6 +39,25 @@
#define AKERR_NOT_IMPLEMENTED (AKERR_LAST_ERRNO_VALUE + 16) /** A method was called that is defined but not currently implemented */
#define AKERR_BADEXC (AKERR_LAST_ERRNO_VALUE + 17) /** The libakerr library was given an akerr_ErrorContext to parse that did not come from AKERR_ARRAY_ERROR (likely an uninitialized pointer) */
/*
* Registry failures. These are ordinary status codes, not a private return
* enumeration: akerr_reserve_status_range() and akerr_register_status_name()
* return akerr_ErrorContext * like everything else in this library, so a refused
* reservation can be CATCH-ed, HANDLE-d, PASS-ed, or left unhandled to produce a
* stack trace and stop the program. Success returns NULL.
*/
#define AKERR_STATUS_RANGE_OVERLAP (AKERR_LAST_ERRNO_VALUE + 18) /** Some part of the range is already owned by someone else */
#define AKERR_STATUS_RANGE_FULL (AKERR_LAST_ERRNO_VALUE + 19) /** No reservation slots remain (see AKERR_MAX_RESERVED_STATUS_RANGES) */
#define AKERR_STATUS_RANGE_INVALID (AKERR_LAST_ERRNO_VALUE + 20) /** Bad count, bad owner string, or the range overflows int */
#define AKERR_STATUS_NAME_UNRESERVED (AKERR_LAST_ERRNO_VALUE + 21) /** No owner has reserved a range containing this status */
#define AKERR_STATUS_NAME_FOREIGN (AKERR_LAST_ERRNO_VALUE + 22) /** The status lies in a range reserved by a different owner */
#define AKERR_STATUS_NAME_FULL (AKERR_LAST_ERRNO_VALUE + 23) /** The name registry is full (raise AKERR_STATUS_NAME_SLOTS) */
#define AKERR_STATUS_NAME_INVALID (AKERR_LAST_ERRNO_VALUE + 24) /** NULL/empty/over-long owner, or a NULL name */
/* The last status the library defines for itself. Everything from
* AKERR_LAST_ERRNO_VALUE + 1 through here must have a registered name. */
#define AKERR_LAST_LIBRARY_STATUS AKERR_STATUS_NAME_INVALID
/*
* Status values 0 through 255 are reserved by libakerror at akerr_init() time:
* the host's errno values plus the AKERR_* codes above. Consumers allocate from
@@ -49,19 +68,6 @@
#define AKERR_RESERVED_STATUS_COUNT 256
#define AKERR_FIRST_CONSUMER_STATUS AKERR_RESERVED_STATUS_COUNT
/* Results of akerr_reserve_status_range(). */
#define AKERR_STATUS_RANGE_OK 0 /** The range is now reserved by the caller */
#define AKERR_STATUS_RANGE_OVERLAP 1 /** Some part of the range is already owned by someone else */
#define AKERR_STATUS_RANGE_FULL 2 /** No reservation slots remain (see AKERR_MAX_RESERVED_STATUS_RANGES) */
#define AKERR_STATUS_RANGE_INVALID 3 /** Bad count, bad owner string, or the range overflows int */
/* Results of akerr_register_status_name(). */
#define AKERR_STATUS_NAME_OK 0 /** The name is registered */
#define AKERR_STATUS_NAME_UNRESERVED 1 /** No owner has reserved a range containing this status */
#define AKERR_STATUS_NAME_FOREIGN 2 /** The status lies in a range reserved by a different owner */
#define AKERR_STATUS_NAME_FULL 3 /** The name registry is full (raise AKERR_STATUS_NAME_SLOTS) */
#define AKERR_STATUS_NAME_INVALID 4 /** NULL/empty/over-long owner, or a NULL name */
/*
* The library reserves status values 0 through 255 for itself (see akerr_init),
* which must contain every AKERR_* code above. AKERR_LAST_ERRNO_VALUE is
@@ -69,7 +75,7 @@
* unusually large errno space these codes could escape the band and collide
* with consumer codes allocated at 256. Fail the build instead.
*/
typedef char akerr_assert_codes_within_reserved_band[(AKERR_BADEXC < 256) ? 1 : -1];
typedef char akerr_assert_codes_within_reserved_band[(AKERR_LAST_LIBRARY_STATUS < 256) ? 1 : -1];
#define AKERR_MAX_ARRAY_ERROR 128
@@ -105,32 +111,43 @@ akerr_ErrorContext AKERR_NOIGNORE *akerr_next_error();
* Look up (name == NULL) or register (name != NULL) the display name for a
* status. Registration succeeds only if some owner has reserved a range
* containing `status`; prefer akerr_register_status_name(), which also checks
* that the range belongs to you and reports why a registration was refused.
* that the range belongs to you and raises an error saying why a registration
* was refused. This entry point cannot return an error context, so a refusal
* here is reported through akerr_log_method and reads back as "Unknown Error".
* Never returns NULL -- an unregistered status reads back as "Unknown Error".
*/
char *akerr_name_for_status(int status, char *name);
/*
* Register a display name for a status you own. Returns AKERR_STATUS_NAME_OK,
* or one of the AKERR_STATUS_NAME_* codes above; every refusal is also logged
* through akerr_log_method. `owner` must match the owner string passed to
* akerr_reserve_status_range() for the range containing `status`.
* Register a display name for a status you own. `owner` must match the owner
* string passed to akerr_reserve_status_range() for the range containing
* `status`. Returns NULL on success, or an error context whose status is one of
* the AKERR_STATUS_NAME_* codes above -- CATCH it, HANDLE it, or let it
* propagate.
*/
int akerr_register_status_name(const char *owner, int status, const char *name);
akerr_ErrorContext AKERR_NOIGNORE *akerr_register_status_name(const char *owner, int status, const char *name);
/*
* Claim `count` status values starting at `first_status` for `owner`. Repeating
* an identical reservation for the same owner is a no-op; any other collision
* is refused and logged. Returns one of the AKERR_STATUS_RANGE_* codes; treat
* anything other than AKERR_STATUS_RANGE_OK as an initialization failure.
* an identical reservation for the same owner is a no-op; any other collision is
* refused. Returns NULL on success, or an error context whose status is one of
* the AKERR_STATUS_RANGE_* codes above. Treat any error as an initialization
* failure: either handle it or let it propagate out of your init function.
*/
int akerr_reserve_status_range(int first_status, int count, const char *owner);
akerr_ErrorContext AKERR_NOIGNORE *akerr_reserve_status_range(int first_status, int count, const char *owner);
void akerr_init();
void akerr_default_handler_unhandled_error(akerr_ErrorContext *ptr);
void akerr_default_logger(const char *f, ...);
int akerr_valid_error_address(akerr_ErrorContext *ptr);
/* defined in src/errno.c which is built dynamically at build time from system errno definitions */
void akerr_init_errno(void);
/*
* Internal. Names a status in the library's own reserved band on behalf of
* akerr_init() and the generated errno table, which have no caller to raise
* into: a failure here is logged and terminates the program. Not part of the
* consumer API -- use akerr_register_status_name(), which raises instead.
*/
void __akerr_name_library_status(int status, const char *name);
#define LOG_ERROR_WITH_MESSAGE(__err_context, __err_message) \
akerr_log_method("%s%s:%s:%d: %s %d (%s): %s", (char *)&__err_context->stacktracebuf, (char *)__FILE__, (char *)__func__, __LINE__, __err_message, __err_context->status, akerr_name_for_status(__err_context->status, NULL), __err_context->message); \