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.
This commit is contained in:
@@ -1,92 +1,89 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
#include <regex.h>
|
||||
|
||||
/*
|
||||
* AKERR_MAX_ERR_VALUE sizes the __AKERR_ERROR_NAMES table and is the upper bound
|
||||
* akerr_name_for_status() accepts. 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).
|
||||
*
|
||||
* Rather than hardcode the list of codes (which rots the moment someone adds a
|
||||
* code), this test parses the generated akerror.h, discovers every
|
||||
* #define AKERR_<NAME> (AKERR_LAST_ERRNO_VALUE + <N>)
|
||||
* finds the highest offset actually defined, and verifies AKERR_MAX_ERR_VALUE
|
||||
* covers it. The path to the header the library was built from is injected by
|
||||
* CMake as AKERR_GENERATED_HEADER.
|
||||
*/
|
||||
|
||||
#ifndef AKERR_GENERATED_HEADER
|
||||
#error "AKERR_GENERATED_HEADER (path to generated akerror.h) must be defined"
|
||||
#endif
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Status magnitude is no longer coupled to a public array bound. */
|
||||
int main(void)
|
||||
{
|
||||
FILE *fh = fopen(AKERR_GENERATED_HEADER, "r");
|
||||
AKERR_CHECK(fh != NULL);
|
||||
akerr_capture_install();
|
||||
akerr_init();
|
||||
AKERR_CHECK(strcmp(akerr_name_for_status(INT_MAX, "Maximum Status"),
|
||||
"Maximum Status") == 0);
|
||||
AKERR_CHECK(strcmp(akerr_name_for_status(INT_MIN, "Minimum Status"),
|
||||
"Minimum Status") == 0);
|
||||
AKERR_CHECK(strcmp(akerr_name_for_status(INT_MAX, NULL),
|
||||
"Maximum Status") == 0);
|
||||
AKERR_CHECK(strcmp(akerr_name_for_status(INT_MIN, NULL),
|
||||
"Minimum Status") == 0);
|
||||
|
||||
/* #define AKERR_NAME (AKERR_LAST_ERRNO_VALUE + N) */
|
||||
regex_t re;
|
||||
const char *pattern =
|
||||
"^[[:space:]]*#define[[:space:]]+(AKERR_[A-Za-z0-9_]+)[[:space:]]+"
|
||||
"\\(AKERR_LAST_ERRNO_VALUE[[:space:]]*\\+[[:space:]]*([0-9]+)\\)";
|
||||
AKERR_CHECK(regcomp(&re, pattern, REG_EXTENDED) == 0);
|
||||
const char *long_name =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-extra";
|
||||
char *stored = akerr_name_for_status(1000000, (char *)long_name);
|
||||
AKERR_CHECK(strlen(stored) == AKERR_MAX_ERROR_NAME_LENGTH - 1);
|
||||
AKERR_CHECK(stored[AKERR_MAX_ERROR_NAME_LENGTH - 1] == '\0');
|
||||
|
||||
int highest_code = -1; /* highest offset among real error codes */
|
||||
char highest_name[64] = "";
|
||||
int max_err_value = -1; /* offset parsed from AKERR_MAX_ERR_VALUE */
|
||||
int code_count = 0;
|
||||
AKERR_CHECK(akerr_reserve_status_range(256, 16, "component-a") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_reserve_status_range(256, 16, "component-a") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_reserve_status_range(260, 2, "component-b") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK_CONTAINS("component-a");
|
||||
AKERR_CHECK(akerr_reserve_status_range(255, 1, "component-b") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK(akerr_reserve_status_range(INT_MAX, 2, "overflow") ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK(akerr_reserve_status_range(300, 0, "empty") ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK(akerr_reserve_status_range(300, -1, "negative") ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK(akerr_reserve_status_range(300, 1, NULL) ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK(akerr_reserve_status_range(300, 1, "") ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
|
||||
char line[4096];
|
||||
regmatch_t m[3];
|
||||
while ( fgets(line, sizeof(line), fh) != NULL ) {
|
||||
if ( regexec(&re, line, 3, m, 0) != 0 ) {
|
||||
continue;
|
||||
}
|
||||
char owner63[AKERR_MAX_ERROR_NAME_LENGTH];
|
||||
char owner64[AKERR_MAX_ERROR_NAME_LENGTH + 1];
|
||||
memset(owner63, 'a', sizeof(owner63) - 1);
|
||||
owner63[sizeof(owner63) - 1] = '\0';
|
||||
memset(owner64, 'b', sizeof(owner64) - 1);
|
||||
owner64[sizeof(owner64) - 1] = '\0';
|
||||
AKERR_CHECK(akerr_reserve_status_range(400, 1, owner63) ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_reserve_status_range(401, 1, owner64) ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK(akerr_reserve_status_range(INT_MAX, 1, "int-max") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
|
||||
char name[64];
|
||||
int nlen = (int)(m[1].rm_eo - m[1].rm_so);
|
||||
if ( nlen >= (int)sizeof(name) ) {
|
||||
nlen = (int)sizeof(name) - 1;
|
||||
}
|
||||
memcpy(name, line + m[1].rm_so, nlen);
|
||||
name[nlen] = '\0';
|
||||
AKERR_CHECK(akerr_reserve_status_range(500, 2, "endpoint") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_reserve_status_range(499, 2, "left") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK(akerr_reserve_status_range(500, 1, "endpoint") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK(akerr_reserve_status_range(501, 1, "endpoint") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK(akerr_reserve_status_range(500, 2, "other") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
|
||||
char num[16];
|
||||
int vlen = (int)(m[2].rm_eo - m[2].rm_so);
|
||||
if ( vlen >= (int)sizeof(num) ) {
|
||||
vlen = (int)sizeof(num) - 1;
|
||||
}
|
||||
memcpy(num, line + m[2].rm_so, vlen);
|
||||
num[vlen] = '\0';
|
||||
int offset = atoi(num);
|
||||
/* Five ranges exist: library, component-a, owner63, INT_MAX, endpoint. */
|
||||
for ( int i = 0; i < 59; i++ ) {
|
||||
AKERR_CHECK(akerr_reserve_status_range(1000 + (i * 2), 1, "fill") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
}
|
||||
AKERR_CHECK(akerr_reserve_status_range(2000, 1, "too-many") ==
|
||||
AKERR_STATUS_RANGE_FULL);
|
||||
|
||||
if ( strcmp(name, "AKERR_MAX_ERR_VALUE") == 0 ) {
|
||||
max_err_value = offset;
|
||||
} else {
|
||||
code_count++;
|
||||
if ( offset > highest_code ) {
|
||||
highest_code = offset;
|
||||
snprintf(highest_name, sizeof(highest_name), "%s", name);
|
||||
}
|
||||
int filled = 0;
|
||||
for ( int status = 2000000; status < 2000600; status++ ) {
|
||||
if ( strcmp(akerr_name_for_status(status, "Filled"), "Filled") != 0 ) {
|
||||
filled = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
regfree(&re);
|
||||
fclose(fh);
|
||||
AKERR_CHECK(filled == 1);
|
||||
|
||||
/* We must have actually parsed both the codes and the ceiling. */
|
||||
AKERR_CHECK(code_count > 0);
|
||||
AKERR_CHECK(highest_code > 0);
|
||||
AKERR_CHECK(max_err_value > 0);
|
||||
|
||||
/* Guard against parsing a different header than the one compiled in. */
|
||||
AKERR_CHECK(max_err_value == (AKERR_MAX_ERR_VALUE - AKERR_LAST_ERRNO_VALUE));
|
||||
|
||||
/* The actual invariant: every defined code is indexable in the names table. */
|
||||
AKERR_CHECK(max_err_value >= highest_code);
|
||||
|
||||
fprintf(stderr,
|
||||
"err_maxval ok (%d codes parsed, highest %s at +%d, max +%d)\n",
|
||||
code_count, highest_name, highest_code, max_err_value);
|
||||
fprintf(stderr, "err_maxval ok\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user