Files
libakerror/src/error.c
Andrew Kesterson f42e2bdcd8 Add collision-safe status code registry
Replace the consumer-sized status-name array with private sparse storage and accept arbitrary integer status values. Add explicit range reservations with overlap diagnostics, reserve the library's 0-255 compatibility band, and harden pointer and string boundary handling.

Update regression coverage and document the required migration for custom status-code consumers.
2026-07-30 13:53:52 -04:00

222 lines
6.8 KiB
C

#include "akerror.h"
#if defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#endif // AKERR_USE_STDLIB
akerr_ErrorContext __akerr_last_ditch;
akerr_ErrorContext *__akerr_last_ignored;
akerr_ErrorUnhandledErrorHandler akerr_handler_unhandled_error;
akerr_ErrorLogFunction akerr_log_method = NULL;
#define AKERR_MAX_REGISTERED_STATUS_NAMES 512
#define AKERR_MAX_RESERVED_STATUS_RANGES 64
#define AKERR_MAX_STATUS_RANGE_OWNER_LENGTH 64
typedef struct
{
int status;
char name[AKERR_MAX_ERROR_NAME_LENGTH];
} akerr_StatusName;
typedef struct
{
int first;
int last;
char owner[AKERR_MAX_STATUS_RANGE_OWNER_LENGTH];
} akerr_StatusRange;
static akerr_StatusName akerr_status_names[AKERR_MAX_REGISTERED_STATUS_NAMES];
static int akerr_status_name_count;
static akerr_StatusRange akerr_status_ranges[AKERR_MAX_RESERVED_STATUS_RANGES];
static int akerr_status_range_count;
akerr_ErrorContext AKERR_ARRAY_ERROR[AKERR_MAX_ARRAY_ERROR];
static void akerr_copy_string(char *destination, int capacity, const char *source)
{
strncpy(destination, source, (size_t)capacity - 1);
destination[capacity - 1] = '\0';
}
int akerr_valid_error_address(akerr_ErrorContext *ptr)
{
// Is this within the memory region occupied by AKERR_ARRAY_ERROR?
if ( ptr == NULL ) {
return 1;
}
for ( int i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
if ( ptr == &AKERR_ARRAY_ERROR[i] ) {
return 1;
}
}
return 0;
}
void akerr_default_logger(const char *fmt, ...)
{
#if defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
#else
return;
#endif
}
void akerr_init()
{
static int inited = 0;
if ( inited == 0 ) {
for (int i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
memset((void *)&AKERR_ARRAY_ERROR[i], 0x00, sizeof(akerr_ErrorContext));
AKERR_ARRAY_ERROR[i].arrayid = i;
AKERR_ARRAY_ERROR[i].stacktracebufptr = (char *)&AKERR_ARRAY_ERROR[i].stacktracebuf;
}
__akerr_last_ignored = NULL;
memset((void *)&__akerr_last_ditch, 0x00, sizeof(akerr_ErrorContext));
__akerr_last_ditch.stacktracebufptr = (char *)&__akerr_last_ditch.stacktracebuf;
if ( akerr_log_method == NULL ) {
akerr_log_method = &akerr_default_logger;
}
akerr_handler_unhandled_error = &akerr_default_handler_unhandled_error;
memset((void *)&akerr_status_names[0], 0x00, sizeof(akerr_status_names));
memset((void *)&akerr_status_ranges[0], 0x00, sizeof(akerr_status_ranges));
akerr_status_name_count = 0;
akerr_status_range_count = 0;
/* errno and AKERR_* values are the library-owned compatibility band. */
(void)akerr_reserve_status_range(0, 256, "libakerror");
akerr_name_for_status(AKERR_NULLPOINTER, "Null Pointer Error");
akerr_name_for_status(AKERR_OUTOFBOUNDS, "Out Of Bounds Error");
akerr_name_for_status(AKERR_API, "API Error");
akerr_name_for_status(AKERR_ATTRIBUTE, "Attribute Error");
akerr_name_for_status(AKERR_TYPE, "Type Error");
akerr_name_for_status(AKERR_KEY, "Key Error");
akerr_name_for_status(AKERR_INDEX, "Index Error");
akerr_name_for_status(AKERR_FORMAT, "Format Error");
akerr_name_for_status(AKERR_IO, "Input Output Error");
akerr_name_for_status(AKERR_VALUE, "Value Error");
akerr_name_for_status(AKERR_RELATIONSHIP, "Relationship Error");
akerr_name_for_status(AKERR_CIRCULAR_REFERENCE, "Circular Reference Error");
akerr_name_for_status(AKERR_BADEXC, "Invalid akerr_ErrorContext");
#if (defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1) || (!defined(AKERR_USE_STDLIB))
akerr_init_errno();
#endif
inited = 1;
}
}
void akerr_default_handler_unhandled_error(akerr_ErrorContext *errctx)
{
if ( errctx == NULL ) {
exit(1);
}
exit(errctx->status);
}
akerr_ErrorContext *akerr_next_error()
{
for (int i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
if ( AKERR_ARRAY_ERROR[i].refcount == 0 ) {
return &AKERR_ARRAY_ERROR[i];
}
}
return (akerr_ErrorContext *)NULL;
}
akerr_ErrorContext *akerr_release_error(akerr_ErrorContext *err)
{
int oldid = 0;
if ( err == NULL ) {
akerr_ErrorContext *errctx = &__akerr_last_ditch;
FAIL_RETURN(errctx, AKERR_NULLPOINTER, "akerr_release_error got NULL context pointer");
}
if ( err->refcount > 0 ) {
err->refcount -= 1;
}
if ( err->refcount == 0 ) {
oldid = err->arrayid;
memset(err, 0x00, sizeof(akerr_ErrorContext));
err->stacktracebufptr = (char *)&err->stacktracebuf;
err->arrayid = oldid;
return NULL;
}
return err;
}
/* Return or set a name. Status magnitude is unrelated to storage size. */
char *akerr_name_for_status(int status, char *name)
{
int i;
for ( i = 0; i < akerr_status_name_count; i++ ) {
if ( akerr_status_names[i].status == status ) {
if ( name != NULL ) {
akerr_copy_string(akerr_status_names[i].name,
AKERR_MAX_ERROR_NAME_LENGTH, name);
}
return akerr_status_names[i].name;
}
}
if ( name != NULL ) {
if ( akerr_status_name_count == AKERR_MAX_REGISTERED_STATUS_NAMES ) {
return "Unknown Error";
}
i = akerr_status_name_count++;
akerr_status_names[i].status = status;
akerr_copy_string(akerr_status_names[i].name,
AKERR_MAX_ERROR_NAME_LENGTH, name);
return akerr_status_names[i].name;
}
return "Unknown Error";
}
/* Reserve an inclusive status interval and reject collisions. */
int akerr_reserve_status_range(int first_status, int count, const char *owner)
{
int last_status;
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;
}
last_status = first_status + count - 1;
for ( int i = 0; i < akerr_status_range_count; i++ ) {
if ( first_status <= akerr_status_ranges[i].last &&
last_status >= akerr_status_ranges[i].first ) {
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;
}
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;
}
}
if ( akerr_status_range_count == AKERR_MAX_RESERVED_STATUS_RANGES ) {
return AKERR_STATUS_RANGE_FULL;
}
akerr_status_ranges[akerr_status_range_count].first = first_status;
akerr_status_ranges[akerr_status_range_count].last = last_status;
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;
}