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>
84 lines
3.3 KiB
C
84 lines
3.3 KiB
C
#include "akerror.h"
|
|
#include "err_capture.h"
|
|
#include <string.h>
|
|
|
|
/*
|
|
* akerr_init() registers a human-readable name for each library error code.
|
|
* Verify the names are actually installed (mutation testing showed the
|
|
* registration calls could be deleted without any test noticing).
|
|
*
|
|
* This list must stay exhaustive. AKERR_EOF, AKERR_ITERATOR_BREAK and
|
|
* AKERR_NOT_IMPLEMENTED were previously valid codes with no registered name, so
|
|
* they rendered as "Unknown Error" in every stack trace that carried them --
|
|
* the same class of silent gap that a too-small AKERR_MAX_ERR_VALUE used to
|
|
* cause. The sweep below walks the whole AKERR_* offset span so a newly added
|
|
* code without a name fails here rather than showing up in production traces.
|
|
*/
|
|
|
|
static const struct {
|
|
int code;
|
|
const char *name;
|
|
} expected[] = {
|
|
{ AKERR_NULLPOINTER, "Null Pointer Error" },
|
|
{ AKERR_OUTOFBOUNDS, "Out Of Bounds Error" },
|
|
{ AKERR_API, "API Error" },
|
|
{ AKERR_ATTRIBUTE, "Attribute Error" },
|
|
{ AKERR_TYPE, "Type Error" },
|
|
{ AKERR_KEY, "Key Error" },
|
|
{ AKERR_INDEX, "Index Error" },
|
|
{ AKERR_FORMAT, "Format Error" },
|
|
{ AKERR_IO, "Input Output Error" },
|
|
{ AKERR_VALUE, "Value Error" },
|
|
{ AKERR_RELATIONSHIP, "Relationship Error" },
|
|
{ AKERR_EOF, "End Of File" },
|
|
{ AKERR_CIRCULAR_REFERENCE, "Circular Reference Error" },
|
|
{ AKERR_ITERATOR_BREAK, "Iterator Break" },
|
|
{ AKERR_NOT_IMPLEMENTED, "Not Implemented" },
|
|
{ AKERR_BADEXC, "Invalid akerr_ErrorContext" },
|
|
{ AKERR_STATUS_RANGE_OVERLAP, "Status Range Overlap" },
|
|
{ AKERR_STATUS_RANGE_FULL, "Status Range Table Full" },
|
|
{ AKERR_STATUS_RANGE_INVALID, "Invalid Status Range" },
|
|
{ AKERR_STATUS_NAME_UNRESERVED, "Unreserved Status Name" },
|
|
{ AKERR_STATUS_NAME_FOREIGN, "Foreign Status Name" },
|
|
{ AKERR_STATUS_NAME_FULL, "Status Name Registry Full" },
|
|
{ AKERR_STATUS_NAME_INVALID, "Invalid Status Name" },
|
|
};
|
|
|
|
int main(void)
|
|
{
|
|
akerr_init();
|
|
|
|
for ( unsigned i = 0; i < sizeof(expected) / sizeof(expected[0]); i++ ) {
|
|
char *nm = akerr_name_for_status(expected[i].code, NULL);
|
|
AKERR_CHECK(nm != NULL);
|
|
AKERR_CHECK(strcmp(nm, expected[i].name) == 0);
|
|
}
|
|
|
|
/*
|
|
* Every value in the library's own offset span must resolve to a real name.
|
|
* AKERR_LAST_ERRNO_VALUE + 7 is the one deliberate hole (a removed code);
|
|
* anything else nameless is a code someone added without registering it.
|
|
*/
|
|
for ( int offset = 1;
|
|
offset <= AKERR_LAST_LIBRARY_STATUS - AKERR_LAST_ERRNO_VALUE;
|
|
offset++ ) {
|
|
int code = AKERR_LAST_ERRNO_VALUE + offset;
|
|
char *nm = akerr_name_for_status(code, NULL);
|
|
if ( offset == 7 ) {
|
|
AKERR_CHECK(strcmp(nm, "Unknown Error") == 0);
|
|
continue;
|
|
}
|
|
if ( strcmp(nm, "Unknown Error") == 0 || nm[0] == '\0' ) {
|
|
fprintf(stderr, "AKERR_LAST_ERRNO_VALUE + %d (%d) has no name\n",
|
|
offset, code);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Every AKERR_* code must sit inside the band the library reserves. */
|
|
AKERR_CHECK(AKERR_LAST_LIBRARY_STATUS < AKERR_FIRST_CONSUMER_STATUS);
|
|
|
|
fprintf(stderr, "err_error_names ok\n");
|
|
return 0;
|
|
}
|