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>
This commit is contained in:
212
src/error.c
212
src/error.c
@@ -106,6 +106,30 @@ void akerr_default_logger(const char *fmt, ...)
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* The library naming its own codes. The registry entry points raise errors like
|
||||
* everything else in the library, but akerr_init() has no caller to raise into,
|
||||
* so this is where the buck stops: log the failure and take the unhandled-error
|
||||
* path, which terminates the program.
|
||||
*
|
||||
* This is fatal on purpose. The library cannot name its own status codes only
|
||||
* if the build is misconfigured -- a name table too small to hold even the
|
||||
* library's own entries, or a reservation that did not take -- and the
|
||||
* consequence of continuing is every later stack trace in the process printing
|
||||
* "Unknown Error" for a code the library defines. That is a startup defect, and
|
||||
* it is far cheaper to see it at init than to debug it from a degraded trace.
|
||||
*/
|
||||
void __akerr_name_library_status(int status, const char *name)
|
||||
{
|
||||
akerr_ErrorContext *errctx = akerr_register_status_name(AKERR_LIBRARY_OWNER,
|
||||
status, name);
|
||||
if ( errctx != NULL ) {
|
||||
LOG_ERROR_WITH_MESSAGE(errctx, "** libakerror could not name its own status **");
|
||||
akerr_handler_unhandled_error(errctx);
|
||||
RELEASE_ERROR(errctx);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Idempotent. `inited` is set before any work so that the registry calls below
|
||||
* -- and the public registry entry points, which all call akerr_init() so that
|
||||
@@ -138,27 +162,44 @@ void akerr_init()
|
||||
/* errno and AKERR_* values are the library-owned compatibility band.
|
||||
* This must precede every registration below: naming a status is only
|
||||
* permitted inside a reserved range. */
|
||||
(void)akerr_reserve_status_range(0, AKERR_RESERVED_STATUS_COUNT,
|
||||
AKERR_LIBRARY_OWNER);
|
||||
akerr_ErrorContext *reserve_err =
|
||||
akerr_reserve_status_range(0, AKERR_RESERVED_STATUS_COUNT,
|
||||
AKERR_LIBRARY_OWNER);
|
||||
if ( reserve_err != NULL ) {
|
||||
/* Fatal for the same reason as __akerr_name_library_status():
|
||||
* without this band the library owns nothing, so none of the names
|
||||
* below can register either. */
|
||||
LOG_ERROR_WITH_MESSAGE(reserve_err,
|
||||
"** libakerror could not reserve its own status band **");
|
||||
akerr_handler_unhandled_error(reserve_err);
|
||||
RELEASE_ERROR(reserve_err);
|
||||
}
|
||||
|
||||
/* Every AKERR_* code gets a name; tests/err_error_names.c asserts the
|
||||
* list is exhaustive so a new code cannot be added without one. */
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_NULLPOINTER, "Null Pointer Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_OUTOFBOUNDS, "Out Of Bounds Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_API, "API Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_ATTRIBUTE, "Attribute Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_TYPE, "Type Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_KEY, "Key Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_INDEX, "Index Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_FORMAT, "Format Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_IO, "Input Output Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_VALUE, "Value Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_RELATIONSHIP, "Relationship Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_EOF, "End Of File");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_CIRCULAR_REFERENCE, "Circular Reference Error");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_ITERATOR_BREAK, "Iterator Break");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_NOT_IMPLEMENTED, "Not Implemented");
|
||||
akerr_register_status_name(AKERR_LIBRARY_OWNER, AKERR_BADEXC, "Invalid akerr_ErrorContext");
|
||||
__akerr_name_library_status(AKERR_NULLPOINTER, "Null Pointer Error");
|
||||
__akerr_name_library_status(AKERR_OUTOFBOUNDS, "Out Of Bounds Error");
|
||||
__akerr_name_library_status(AKERR_API, "API Error");
|
||||
__akerr_name_library_status(AKERR_ATTRIBUTE, "Attribute Error");
|
||||
__akerr_name_library_status(AKERR_TYPE, "Type Error");
|
||||
__akerr_name_library_status(AKERR_KEY, "Key Error");
|
||||
__akerr_name_library_status(AKERR_INDEX, "Index Error");
|
||||
__akerr_name_library_status(AKERR_FORMAT, "Format Error");
|
||||
__akerr_name_library_status(AKERR_IO, "Input Output Error");
|
||||
__akerr_name_library_status(AKERR_VALUE, "Value Error");
|
||||
__akerr_name_library_status(AKERR_RELATIONSHIP, "Relationship Error");
|
||||
__akerr_name_library_status(AKERR_EOF, "End Of File");
|
||||
__akerr_name_library_status(AKERR_CIRCULAR_REFERENCE, "Circular Reference Error");
|
||||
__akerr_name_library_status(AKERR_ITERATOR_BREAK, "Iterator Break");
|
||||
__akerr_name_library_status(AKERR_NOT_IMPLEMENTED, "Not Implemented");
|
||||
__akerr_name_library_status(AKERR_BADEXC, "Invalid akerr_ErrorContext");
|
||||
__akerr_name_library_status(AKERR_STATUS_RANGE_OVERLAP, "Status Range Overlap");
|
||||
__akerr_name_library_status(AKERR_STATUS_RANGE_FULL, "Status Range Table Full");
|
||||
__akerr_name_library_status(AKERR_STATUS_RANGE_INVALID, "Invalid Status Range");
|
||||
__akerr_name_library_status(AKERR_STATUS_NAME_UNRESERVED, "Unreserved Status Name");
|
||||
__akerr_name_library_status(AKERR_STATUS_NAME_FOREIGN, "Foreign Status Name");
|
||||
__akerr_name_library_status(AKERR_STATUS_NAME_FULL, "Status Name Registry Full");
|
||||
__akerr_name_library_status(AKERR_STATUS_NAME_INVALID, "Invalid Status Name");
|
||||
#if (defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1) || (!defined(AKERR_USE_STDLIB))
|
||||
akerr_init_errno();
|
||||
#endif
|
||||
@@ -280,76 +321,91 @@ static akerr_StatusRange *akerr_range_for_status(int status)
|
||||
* Shared body of both registration entry points. A NULL owner means the caller
|
||||
* did not identify itself (the legacy two-argument akerr_name_for_status path):
|
||||
* the status must still lie inside *some* reservation, but we cannot check that
|
||||
* it is the caller's. Every refusal is logged -- a name that silently fails to
|
||||
* register degrades into "Unknown Error" in stack traces, which is exactly the
|
||||
* kind of quiet loss this registry exists to prevent.
|
||||
* it is the caller's. Every refusal raises an error -- a name that silently
|
||||
* fails to register degrades into "Unknown Error" in stack traces, which is
|
||||
* exactly the kind of quiet loss this registry exists to prevent -- so the
|
||||
* message carries everything a caller needs to see in a stack trace.
|
||||
*/
|
||||
static int akerr_store_status_name(const char *owner, int status, const char *name)
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *akerr_store_status_name(const char *owner,
|
||||
int status,
|
||||
const char *name)
|
||||
{
|
||||
akerr_StatusRange *range;
|
||||
akerr_StatusName *entry;
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
if ( name == NULL ) {
|
||||
return AKERR_STATUS_NAME_INVALID;
|
||||
FAIL_RETURN(errctx, AKERR_STATUS_NAME_INVALID,
|
||||
"Refusing to name status %d for %s: the name is NULL",
|
||||
status, owner == NULL ? "an unnamed caller" : owner);
|
||||
}
|
||||
if ( owner != NULL && ( owner[0] == '\0' ||
|
||||
strlen(owner) >= AKERR_MAX_STATUS_RANGE_OWNER_LENGTH ) ) {
|
||||
return AKERR_STATUS_NAME_INVALID;
|
||||
FAIL_RETURN(errctx, AKERR_STATUS_NAME_INVALID,
|
||||
"Refusing to name status %d (\"%s\"): the owner string is "
|
||||
"empty or longer than %d characters",
|
||||
status, name, AKERR_MAX_STATUS_RANGE_OWNER_LENGTH - 1);
|
||||
}
|
||||
|
||||
range = akerr_range_for_status(status);
|
||||
if ( range == NULL ) {
|
||||
if ( akerr_log_method != NULL ) {
|
||||
akerr_log_method("Refusing to name status %d (\"%s\") for %s: no "
|
||||
"reserved range contains it. Call "
|
||||
"akerr_reserve_status_range() first.\n",
|
||||
status, name, owner == NULL ? "an unnamed caller" : owner);
|
||||
}
|
||||
return AKERR_STATUS_NAME_UNRESERVED;
|
||||
FAIL_RETURN(errctx, AKERR_STATUS_NAME_UNRESERVED,
|
||||
"Refusing to name status %d (\"%s\") for %s: no reserved "
|
||||
"range contains it. Call akerr_reserve_status_range() first.",
|
||||
status, name, owner == NULL ? "an unnamed caller" : owner);
|
||||
}
|
||||
if ( owner != NULL && strcmp(owner, range->owner) != 0 ) {
|
||||
if ( akerr_log_method != NULL ) {
|
||||
akerr_log_method("Refusing to name status %d (\"%s\") for %s: that "
|
||||
"status is in range %d..%d owned by %s.\n",
|
||||
status, name, owner,
|
||||
range->first, range->last, range->owner);
|
||||
}
|
||||
return AKERR_STATUS_NAME_FOREIGN;
|
||||
FAIL_RETURN(errctx, AKERR_STATUS_NAME_FOREIGN,
|
||||
"Refusing to name status %d (\"%s\") for %s: that status is "
|
||||
"in range %d..%d owned by %s.",
|
||||
status, name, owner,
|
||||
range->first, range->last, range->owner);
|
||||
}
|
||||
|
||||
entry = akerr_status_slot(status, 1);
|
||||
if ( entry == NULL ) {
|
||||
if ( akerr_log_method != NULL ) {
|
||||
akerr_log_method("Status name registry is full (%d entries); "
|
||||
"dropping name \"%s\" for status %d. Rebuild "
|
||||
"libakerror with a larger AKERR_STATUS_NAME_SLOTS.\n",
|
||||
AKERR_MAX_REGISTERED_STATUS_NAMES, name, status);
|
||||
}
|
||||
return AKERR_STATUS_NAME_FULL;
|
||||
FAIL_RETURN(errctx, AKERR_STATUS_NAME_FULL,
|
||||
"Status name registry is full (%d entries); dropping name "
|
||||
"\"%s\" for status %d. Rebuild libakerror with a larger "
|
||||
"AKERR_STATUS_NAME_SLOTS.",
|
||||
AKERR_MAX_REGISTERED_STATUS_NAMES, name, status);
|
||||
}
|
||||
akerr_copy_string(entry->name, AKERR_MAX_ERROR_NAME_LENGTH, name);
|
||||
return AKERR_STATUS_NAME_OK;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/* Register a name for a status inside a range the caller reserved. */
|
||||
int akerr_register_status_name(const char *owner, int status, const char *name)
|
||||
akerr_ErrorContext *akerr_register_status_name(const char *owner, int status, const char *name)
|
||||
{
|
||||
akerr_init();
|
||||
if ( owner == NULL ) {
|
||||
return AKERR_STATUS_NAME_INVALID;
|
||||
}
|
||||
return akerr_store_status_name(owner, status, name);
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (owner != NULL), AKERR_STATUS_NAME_INVALID,
|
||||
"Refusing to name status %d: the owner string is NULL. "
|
||||
"Pass the same owner you reserved the range with.",
|
||||
status);
|
||||
PASS(errctx, akerr_store_status_name(owner, status, name));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/* Return or set a name. Status magnitude is unrelated to storage size. */
|
||||
/*
|
||||
* Return or set a name. Status magnitude is unrelated to storage size.
|
||||
*
|
||||
* The set path is the legacy two-argument form and cannot hand an error back to
|
||||
* its caller (it returns a name), so a refusal is logged here and released.
|
||||
* akerr_register_status_name() is the form that raises.
|
||||
*/
|
||||
char *akerr_name_for_status(int status, char *name)
|
||||
{
|
||||
akerr_StatusName *entry;
|
||||
|
||||
akerr_init();
|
||||
if ( name != NULL &&
|
||||
akerr_store_status_name(NULL, status, name) != AKERR_STATUS_NAME_OK ) {
|
||||
return "Unknown Error";
|
||||
if ( name != NULL ) {
|
||||
akerr_ErrorContext *errctx = akerr_store_status_name(NULL, status, name);
|
||||
if ( errctx != NULL ) {
|
||||
LOG_ERROR_WITH_MESSAGE(errctx, "** REFUSED STATUS NAME **");
|
||||
RELEASE_ERROR(errctx);
|
||||
return "Unknown Error";
|
||||
}
|
||||
}
|
||||
entry = akerr_status_slot(status, 0);
|
||||
if ( entry == NULL ) {
|
||||
@@ -359,15 +415,21 @@ char *akerr_name_for_status(int status, char *name)
|
||||
}
|
||||
|
||||
/* Reserve an inclusive status interval and reject collisions. */
|
||||
int akerr_reserve_status_range(int first_status, int count, const char *owner)
|
||||
akerr_ErrorContext *akerr_reserve_status_range(int first_status, int count, const char *owner)
|
||||
{
|
||||
int last_status;
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
akerr_init();
|
||||
if ( count <= 0 || owner == NULL || owner[0] == '\0' ||
|
||||
strlen(owner) >= AKERR_MAX_STATUS_RANGE_OWNER_LENGTH ||
|
||||
first_status > INT_MAX - (count - 1) ) {
|
||||
return AKERR_STATUS_RANGE_INVALID;
|
||||
FAIL_RETURN(errctx, AKERR_STATUS_RANGE_INVALID,
|
||||
"Invalid status range reservation: %d status values from "
|
||||
"%d for %s (count must be positive, the owner string "
|
||||
"non-empty and shorter than %d characters, and the range "
|
||||
"must not overflow int)",
|
||||
count, first_status, owner == NULL ? "(null)" : owner,
|
||||
AKERR_MAX_STATUS_RANGE_OWNER_LENGTH);
|
||||
}
|
||||
last_status = first_status + count - 1;
|
||||
|
||||
@@ -377,27 +439,23 @@ int akerr_reserve_status_range(int first_status, int count, const char *owner)
|
||||
if ( first_status == akerr_status_ranges[i].first &&
|
||||
last_status == akerr_status_ranges[i].last &&
|
||||
strcmp(owner, akerr_status_ranges[i].owner) == 0 ) {
|
||||
return AKERR_STATUS_RANGE_OK;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( akerr_log_method != NULL ) {
|
||||
akerr_log_method("Status range %d..%d requested by %s overlaps "
|
||||
"%d..%d owned by %s\n",
|
||||
first_status, last_status, owner,
|
||||
akerr_status_ranges[i].first,
|
||||
akerr_status_ranges[i].last,
|
||||
akerr_status_ranges[i].owner);
|
||||
}
|
||||
return AKERR_STATUS_RANGE_OVERLAP;
|
||||
FAIL_RETURN(errctx, AKERR_STATUS_RANGE_OVERLAP,
|
||||
"Status range %d..%d requested by %s overlaps %d..%d "
|
||||
"owned by %s",
|
||||
first_status, last_status, owner,
|
||||
akerr_status_ranges[i].first,
|
||||
akerr_status_ranges[i].last,
|
||||
akerr_status_ranges[i].owner);
|
||||
}
|
||||
}
|
||||
if ( akerr_status_range_count == AKERR_MAX_RESERVED_STATUS_RANGES ) {
|
||||
if ( akerr_log_method != NULL ) {
|
||||
akerr_log_method("Status range table is full (%d ranges); "
|
||||
"refusing %d..%d for %s.\n",
|
||||
AKERR_MAX_RESERVED_STATUS_RANGES,
|
||||
first_status, last_status, owner);
|
||||
}
|
||||
return AKERR_STATUS_RANGE_FULL;
|
||||
FAIL_RETURN(errctx, AKERR_STATUS_RANGE_FULL,
|
||||
"Status range table is full (%d ranges); refusing %d..%d "
|
||||
"for %s.",
|
||||
AKERR_MAX_RESERVED_STATUS_RANGES,
|
||||
first_status, last_status, owner);
|
||||
}
|
||||
|
||||
akerr_status_ranges[akerr_status_range_count].first = first_status;
|
||||
@@ -405,5 +463,5 @@ int akerr_reserve_status_range(int first_status, int count, const char *owner)
|
||||
akerr_copy_string(akerr_status_ranges[akerr_status_range_count].owner,
|
||||
AKERR_MAX_STATUS_RANGE_OWNER_LENGTH, owner);
|
||||
akerr_status_range_count++;
|
||||
return AKERR_STATUS_RANGE_OK;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user