#include "akerror.h" #include "err_capture.h" #include /* * Reserving a range used to be pure bookkeeping: akerr_name_for_status() would * name any status for any caller, so two components could still register names * for the same code -- and HANDLE the same code -- with nothing detecting it. * Reservation only caught components that both opted in AND declared ranges * that happened to overlap. * * Naming a status is now permitted only inside a reservation: * - akerr_register_status_name() requires the range to belong to the caller, * and raises AKERR_STATUS_NAME_* when it does not; * - the legacy two-argument akerr_name_for_status() set path cannot identify * its caller, so it can only require that *some* reservation covers the * status -- still enough to stop a code nobody claimed. It returns a name * rather than an error context, so its refusals are logged instead. * Either way the refusal is visible, because a name that fails to register * degrades the status to "Unknown Error" in every later stack trace. */ int main(void) { akerr_capture_install(); akerr_init(); AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(256, 16, "lib-a")); AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(512, 16, "lib-b")); /* The owner of a range may name statuses inside it. */ AKERR_CHECK_SUCCEEDS(akerr_register_status_name("lib-a", 256, "A Parse Error")); AKERR_CHECK_SUCCEEDS(akerr_register_status_name("lib-a", 271, "A Last Error")); AKERR_CHECK(strcmp(akerr_name_for_status(256, NULL), "A Parse Error") == 0); /* Naming another owner's status is refused and names the real owner. */ AKERR_CHECK_RAISES(akerr_register_status_name("lib-b", 256, "B Hijack"), AKERR_STATUS_NAME_FOREIGN); AKERR_CHECK_MESSAGE_CONTAINS("lib-a"); AKERR_CHECK_MESSAGE_CONTAINS("lib-b"); AKERR_CHECK(strcmp(akerr_name_for_status(256, NULL), "A Parse Error") == 0); /* Including the library's own reserved band. */ AKERR_CHECK_RAISES(akerr_register_status_name("lib-b", AKERR_VALUE, "B Value"), AKERR_STATUS_NAME_FOREIGN); AKERR_CHECK_MESSAGE_CONTAINS(AKERR_LIBRARY_OWNER); AKERR_CHECK(strcmp(akerr_name_for_status(AKERR_VALUE, NULL), "Value Error") == 0); /* A status nobody reserved cannot be named through either entry point. */ AKERR_CHECK_RAISES(akerr_register_status_name("lib-a", 9999, "Unclaimed"), AKERR_STATUS_NAME_UNRESERVED); AKERR_CHECK_MESSAGE_CONTAINS("no reserved range"); AKERR_CHECK(strcmp(akerr_name_for_status(9999, NULL), "Unknown Error") == 0); /* The legacy path has no caller to raise into, so it logs the refusal. * It also cannot name the caller, and must say so rather than printing a * stray owner. */ akerr_capture_reset(); AKERR_CHECK(strcmp(akerr_name_for_status(9999, "Unclaimed Legacy"), "Unknown Error") == 0); AKERR_CHECK_CONTAINS("no reserved range"); AKERR_CHECK_CONTAINS("an unnamed caller"); AKERR_CHECK_CONTAINS("REFUSED STATUS NAME"); AKERR_CHECK(strcmp(akerr_name_for_status(9999, NULL), "Unknown Error") == 0); /* ... and hands the context it raised back to the pool. */ AKERR_CHECK(akerr_slots_in_use() == 0); /* Boundaries: just outside lib-a's range is not lib-a's to name. */ AKERR_CHECK_RAISES(akerr_register_status_name("lib-a", 255, "Below"), AKERR_STATUS_NAME_FOREIGN); AKERR_CHECK_RAISES(akerr_register_status_name("lib-a", 272, "Above"), AKERR_STATUS_NAME_UNRESERVED); /* The legacy set path still works inside any reservation. */ AKERR_CHECK(strcmp(akerr_name_for_status(513, "B Legacy"), "B Legacy") == 0); AKERR_CHECK(strcmp(akerr_name_for_status(513, NULL), "B Legacy") == 0); /* Re-registering your own status overwrites the name. */ AKERR_CHECK_SUCCEEDS(akerr_register_status_name("lib-a", 256, "A Renamed")); AKERR_CHECK(strcmp(akerr_name_for_status(256, NULL), "A Renamed") == 0); /* * Argument validation. Each message must identify the caller it refused, * since that message is the whole report a consumer gets. */ AKERR_CHECK_RAISES(akerr_register_status_name(NULL, 257, "No Owner"), AKERR_STATUS_NAME_INVALID); AKERR_CHECK_MESSAGE_CONTAINS("257"); AKERR_CHECK_RAISES(akerr_register_status_name("", 257, "Empty Owner"), AKERR_STATUS_NAME_INVALID); AKERR_CHECK_MESSAGE_CONTAINS("Empty Owner"); AKERR_CHECK_RAISES(akerr_register_status_name("lib-a", 257, NULL), AKERR_STATUS_NAME_INVALID); AKERR_CHECK_MESSAGE_CONTAINS("lib-a"); /* Over-long owner strings: 63 characters fit, 64 and beyond do not. */ char owner63[64]; char owner64[65]; char owner70[71]; memset(owner63, 'a', sizeof(owner63) - 1); owner63[sizeof(owner63) - 1] = '\0'; memset(owner64, 'b', sizeof(owner64) - 1); owner64[sizeof(owner64) - 1] = '\0'; memset(owner70, 'c', sizeof(owner70) - 1); owner70[sizeof(owner70) - 1] = '\0'; AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(600, 1, owner63)); AKERR_CHECK_SUCCEEDS(akerr_register_status_name(owner63, 600, "Long Owner")); AKERR_CHECK_RAISES(akerr_register_status_name(owner64, 600, "Too Long"), AKERR_STATUS_NAME_INVALID); AKERR_CHECK_MESSAGE_CONTAINS("63"); AKERR_CHECK_RAISES(akerr_register_status_name(owner70, 600, "Far Too Long"), AKERR_STATUS_NAME_INVALID); AKERR_CHECK(strcmp(akerr_name_for_status(600, NULL), "Long Owner") == 0); /* A refused registration must not consume a slot or leave a partial entry. */ AKERR_CHECK(strcmp(akerr_name_for_status(257, NULL), "Unknown Error") == 0); /* Lookup is unaffected by ownership -- anyone may read any name. */ AKERR_CHECK(strcmp(akerr_name_for_status(271, NULL), "A Last Error") == 0); /* Every refusal above released its context. */ AKERR_CHECK(akerr_slots_in_use() == 0); fprintf(stderr, "err_name_ownership ok\n"); return 0; }