Fix AKERR_MAX_ERR_VALUE to cover all AKERR_* codes
AKERR_MAX_ERR_VALUE was AKERR_LAST_ERRNO_VALUE + 15, but the highest defined code, AKERR_BADEXC, is + 17 (AKERR_NOT_IMPLEMENTED is + 16). akerr_name_for_status rejects any status above the max, so those codes could never have a registered name and the AKERR_BADEXC registration in akerr_init was dead code -- a gap found by mutation testing. Bump the max to + 17. - err_maxval: new test asserting the reserved AKERR_* range exceeds the number of AKERR_* codes and that every code is individually indexable. Fails against the old + 15 value (verified), guarding against regression. - err_error_names: now also checks AKERR_BADEXC's name, which the fix makes reachable. Mutation score on src/error.c rises 71% -> 74%: the previously-dead BADEXC registration and the name_for_status upper-bound check are now killable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,7 @@ set(AKERR_TESTS
|
|||||||
err_error_names
|
err_error_names
|
||||||
err_release_clears
|
err_release_clears
|
||||||
err_pool_exhaust
|
err_pool_exhaust
|
||||||
|
err_maxval
|
||||||
)
|
)
|
||||||
|
|
||||||
set(AKERR_WILL_FAIL_TESTS
|
set(AKERR_WILL_FAIL_TESTS
|
||||||
|
|||||||
@@ -40,7 +40,10 @@
|
|||||||
#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) */
|
#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) */
|
||||||
|
|
||||||
#ifndef AKERR_MAX_ERR_VALUE
|
#ifndef AKERR_MAX_ERR_VALUE
|
||||||
#define AKERR_MAX_ERR_VALUE (AKERR_LAST_ERRNO_VALUE + 15)
|
/* Must be >= the highest AKERR_* offset above (AKERR_BADEXC, +17) so every
|
||||||
|
* library error code is indexable in __AKERR_ERROR_NAMES. Keep in sync when
|
||||||
|
* adding codes; tests/err_maxval.c guards this invariant. */
|
||||||
|
#define AKERR_MAX_ERR_VALUE (AKERR_LAST_ERRNO_VALUE + 17)
|
||||||
#elif AKERR_MAX_ERR_VALUE < 256
|
#elif AKERR_MAX_ERR_VALUE < 256
|
||||||
#error user-defined AKERR_MAX_ERR_VALUE must be >= 256
|
#error user-defined AKERR_MAX_ERR_VALUE must be >= 256
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ Re-run after adding tests and confirm the score went up.
|
|||||||
|
|
||||||
## Current status
|
## Current status
|
||||||
|
|
||||||
`src/error.c` scores ~71% (the CI gate is set to 65% for headroom). The
|
`src/error.c` scores ~74% (the CI gate is set to 65% for headroom). The
|
||||||
remaining survivors are dominated by:
|
remaining survivors are dominated by:
|
||||||
|
|
||||||
* **Equivalent mutants** in `akerr_init`: deleting the `memset`/`NULL` setup of
|
* **Equivalent mutants** in `akerr_init`: deleting the `memset`/`NULL` setup of
|
||||||
@@ -90,11 +90,11 @@ remaining survivors are dominated by:
|
|||||||
test that captures a child's stderr and exit code, rather than the in-process
|
test that captures a child's stderr and exit code, rather than the in-process
|
||||||
capturing logger the other tests use.
|
capturing logger the other tests use.
|
||||||
|
|
||||||
Findings worth noting (surfaced by mutation testing, not yet fixed):
|
Findings surfaced by mutation testing:
|
||||||
|
|
||||||
* `AKERR_MAX_ERR_VALUE` is `AKERR_LAST_ERRNO_VALUE + 15`, but `AKERR_NOT_IMPLEMENTED`
|
* **Fixed:** `AKERR_MAX_ERR_VALUE` was `AKERR_LAST_ERRNO_VALUE + 15`, below
|
||||||
(+16) and `AKERR_BADEXC` (+17) exceed it. `akerr_name_for_status` rejects any
|
`AKERR_NOT_IMPLEMENTED` (+16) and `AKERR_BADEXC` (+17). `akerr_name_for_status`
|
||||||
status `> AKERR_MAX_ERR_VALUE`, so those two codes can never store or return a
|
rejects any status `> AKERR_MAX_ERR_VALUE`, so those codes could never store or
|
||||||
name — the `akerr_name_for_status(AKERR_BADEXC, ...)` call in `akerr_init` is
|
return a name and the `akerr_name_for_status(AKERR_BADEXC, ...)` call in
|
||||||
dead code (which is why deleting it survives). Bumping `AKERR_MAX_ERR_VALUE` to
|
`akerr_init` was dead code (which is why deleting it survived). The max is now
|
||||||
`+ 17` would fix it.
|
`+ 17`, and `tests/err_maxval.c` guards the invariant so it can't regress.
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
* Verify the names are actually installed (mutation testing showed the
|
* Verify the names are actually installed (mutation testing showed the
|
||||||
* registration calls could be deleted without any test noticing).
|
* registration calls could be deleted without any test noticing).
|
||||||
*
|
*
|
||||||
* Note: AKERR_NOT_IMPLEMENTED and AKERR_BADEXC are intentionally omitted -- they
|
* Note: AKERR_EOF, AKERR_ITERATOR_BREAK and AKERR_NOT_IMPLEMENTED are omitted --
|
||||||
* exceed AKERR_MAX_ERR_VALUE, so akerr_name_for_status cannot store or return
|
* they are valid codes but akerr_init does not register a display name for them,
|
||||||
* their names (see tests/MUTATION.md).
|
* so akerr_name_for_status returns an empty string rather than a known name.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static const struct {
|
static const struct {
|
||||||
@@ -28,6 +28,7 @@ static const struct {
|
|||||||
{ AKERR_VALUE, "Value Error" },
|
{ AKERR_VALUE, "Value Error" },
|
||||||
{ AKERR_RELATIONSHIP, "Relationship Error" },
|
{ AKERR_RELATIONSHIP, "Relationship Error" },
|
||||||
{ AKERR_CIRCULAR_REFERENCE, "Circular Reference Error" },
|
{ AKERR_CIRCULAR_REFERENCE, "Circular Reference Error" },
|
||||||
|
{ AKERR_BADEXC, "Invalid akerr_ErrorContext" },
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
|
|||||||
52
tests/err_maxval.c
Normal file
52
tests/err_maxval.c
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#include "akerror.h"
|
||||||
|
#include "err_capture.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AKERR_MAX_ERR_VALUE sizes the __AKERR_ERROR_NAMES table and is the upper bound
|
||||||
|
* akerr_name_for_status() will accept. If it is smaller than the highest AKERR_*
|
||||||
|
* code, those codes silently lose their names (this was a real bug: the max was
|
||||||
|
* +15 while AKERR_BADEXC is +17).
|
||||||
|
*
|
||||||
|
* Guard the invariant: the AKERR_* range reserved above AKERR_LAST_ERRNO_VALUE
|
||||||
|
* must be larger than the number of AKERR_* codes defined, and every code must
|
||||||
|
* be individually indexable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Every AKERR_* library error code. Keep in sync with akerror.h. */
|
||||||
|
static const int akerr_codes[] = {
|
||||||
|
AKERR_NULLPOINTER,
|
||||||
|
AKERR_OUTOFBOUNDS,
|
||||||
|
AKERR_API,
|
||||||
|
AKERR_ATTRIBUTE,
|
||||||
|
AKERR_TYPE,
|
||||||
|
AKERR_KEY,
|
||||||
|
AKERR_INDEX,
|
||||||
|
AKERR_FORMAT,
|
||||||
|
AKERR_IO,
|
||||||
|
AKERR_VALUE,
|
||||||
|
AKERR_RELATIONSHIP,
|
||||||
|
AKERR_EOF,
|
||||||
|
AKERR_CIRCULAR_REFERENCE,
|
||||||
|
AKERR_ITERATOR_BREAK,
|
||||||
|
AKERR_NOT_IMPLEMENTED,
|
||||||
|
AKERR_BADEXC,
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
int n = (int)(sizeof(akerr_codes) / sizeof(akerr_codes[0]));
|
||||||
|
|
||||||
|
/* AKERR_MAX_ERR_VALUE must reserve more codes than are actually defined. */
|
||||||
|
AKERR_CHECK((AKERR_MAX_ERR_VALUE - AKERR_LAST_ERRNO_VALUE) > n);
|
||||||
|
|
||||||
|
/* Stronger: every defined code must fall within the addressable range so
|
||||||
|
* its name can be stored and retrieved. */
|
||||||
|
for ( int i = 0; i < n; i++ ) {
|
||||||
|
AKERR_CHECK(akerr_codes[i] > AKERR_LAST_ERRNO_VALUE);
|
||||||
|
AKERR_CHECK(akerr_codes[i] <= AKERR_MAX_ERR_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "err_maxval ok (%d codes, range %d)\n",
|
||||||
|
n, AKERR_MAX_ERR_VALUE - AKERR_LAST_ERRNO_VALUE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user