Every entry point may now be called from any thread. akerr_init() runs exactly once however many threads race into it, the pool hands each slot to exactly one thread, and reservations, registrations and lookups are serialized against each other. One recursive lock covers both tables (src/lock.h, private). Recursive because raising an error re-enters the library -- FAIL needs a pool slot and a status name -- and single because two locks would mean an ordering to get wrong. Registry bodies that use the early-returning FAIL_*_RETURN macros are split into *_locked functions behind wrappers that take and release the lock on one path; consumer callbacks are never called under it. This is an ABI break, hence 2.0.0 and SOVERSION 2: - akerr_next_error() now returns a context that already holds its reference. Finding a free slot and claiming it has to be one operation, or two threads scanning at once are handed the same slot. ENSURE_ERROR_READY no longer increments. - __akerr_last_ignored is thread-local, as is the last-ditch context used to report akerr_release_error(NULL). The threading backend is chosen at configure time by AKERR_THREADS (auto, pthread, none). auto fails the configure when it cannot find POSIX threads rather than quietly building a library that reports itself thread safe and is not. generrno.sh stamps the decision into the generated header as AKERR_THREAD_SAFE, so a consumer cannot disagree with the library about it. Tests: err_threads_init, err_threads_pool and err_threads_registry assert exclusive slot ownership, exactly one winner for a contested range, and every registered name readable back under contention. AKERR_SANITIZE builds the library and the tests with any sanitizer; scripts/thread_test.sh runs the suite under ThreadSanitizer and CI runs it. Removing the pool lock makes both the sanitizer and the plain assertions fail, so the tests are not vacuous. Documented in README.md and UPGRADING.md, including what this does not cover: renaming a status while another thread looks it up, and which of two simultaneous unhandled errors sets the exit status. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
423 lines
19 KiB
C
423 lines
19 KiB
C
#ifndef _AKERR_H_
|
|
#define _AKERR_H_
|
|
|
|
#if (defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1) || (!defined(AKERR_USE_STDLIB))
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <limits.h>
|
|
#endif
|
|
|
|
/*
|
|
* Threading.
|
|
*
|
|
* scripts/generrno.sh stamps this value in at build time from the AKERR_THREADS
|
|
* build option, the same way it stamps AKERR_LAST_ERRNO_VALUE. It is generated
|
|
* rather than defined by the consumer on purpose: whether the library
|
|
* serializes its global state and whether __akerr_last_ignored is a
|
|
* thread-local are the same decision, and a consumer that disagreed with the
|
|
* library about it would link against a differently shaped symbol.
|
|
*
|
|
* 1 The error pool and the status registry are mutex protected, and the
|
|
* per-thread state below is thread local. Every entry point may be called
|
|
* from any thread. See "Thread safety" in README.md for what that does and
|
|
* does not cover.
|
|
* 0 The library was built -DAKERR_THREADS=none for a single-threaded
|
|
* process: no locking, no thread-local storage, and calling it from more
|
|
* than one thread is undefined.
|
|
*
|
|
* Consumers can test it: #if AKERR_THREAD_SAFE.
|
|
*/
|
|
#define AKERR_THREAD_SAFE AKERR_THREAD_SAFE_SED
|
|
|
|
#if AKERR_THREAD_SAFE == 1
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#define AKERR_THREAD_LOCAL __thread
|
|
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
|
#define AKERR_THREAD_LOCAL _Thread_local
|
|
#elif defined(_MSC_VER)
|
|
#define AKERR_THREAD_LOCAL __declspec(thread)
|
|
#else
|
|
#error "libakerror was built thread safe, but this compiler has no thread-local storage specifier that akerror.h knows about. Rebuild libakerror with -DAKERR_THREADS=none, or add the spelling here."
|
|
#endif
|
|
#else
|
|
#define AKERR_THREAD_LOCAL
|
|
#endif
|
|
|
|
// FIXME: This is huge now. It used to be 1000 bytes, then I wanted to report errors
|
|
// related to filesystem paths, which made it grow beyond PATH_MAX, then I started
|
|
// reporting messages including 2 file paths (PATH_MAX * 2), so now to make the compiler warnings
|
|
// shut up, it's enormous (PATH_MAX*3).
|
|
#define AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH 12384
|
|
|
|
#define AKERR_MAX_ERROR_NAME_LENGTH 64
|
|
#define AKERR_MAX_ERROR_FNAME_LENGTH PATH_MAX
|
|
#define AKERR_MAX_ERROR_FUNCTION_LENGTH 128
|
|
#define AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH (AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH + AKERR_MAX_ERROR_NAME_LENGTH + AKERR_MAX_ERROR_FNAME_LENGTH + AKERR_MAX_ERROR_FUNCTION_LENGTH + 16)
|
|
|
|
#define AKERR_LAST_ERRNO_VALUE AKERR_LAST_ERRNO_VALUE_SED
|
|
|
|
#define AKERR_NULLPOINTER (AKERR_LAST_ERRNO_VALUE + 1) /** A pointer had a NULL value where such was not permissible */
|
|
#define AKERR_OUTOFBOUNDS (AKERR_LAST_ERRNO_VALUE + 2) /** Attempt to access a datastructure outside of bounds */
|
|
#define AKERR_API (AKERR_LAST_ERRNO_VALUE + 3) /** An otherwise unspecified API contract has been violated */
|
|
#define AKERR_ATTRIBUTE (AKERR_LAST_ERRNO_VALUE + 4) /** Relates to accessing of attributes on objects */
|
|
#define AKERR_TYPE (AKERR_LAST_ERRNO_VALUE + 5) /** An object had the incorrect type */
|
|
#define AKERR_KEY (AKERR_LAST_ERRNO_VALUE + 6) /** A key was either invalid for or not present in a map */
|
|
#define AKERR_INDEX (AKERR_LAST_ERRNO_VALUE + 8) /** An error occurred when attempting to index an indexable datastructure (other than out of bounds) */
|
|
#define AKERR_FORMAT (AKERR_LAST_ERRNO_VALUE + 9) /** An error occurred in the formatting of an object (usually a string) */
|
|
#define AKERR_IO (AKERR_LAST_ERRNO_VALUE + 10) /** An unspecified IO error occurred. */
|
|
#define AKERR_VALUE (AKERR_LAST_ERRNO_VALUE + 11) /** A provided value was invalid */
|
|
#define AKERR_RELATIONSHIP (AKERR_LAST_ERRNO_VALUE + 12) /** An error occurred in establishing, maintaining or severing a relationship between two objects */
|
|
#define AKERR_EOF (AKERR_LAST_ERRNO_VALUE + 13) /** The end of a stream or file has been encountered */
|
|
#define AKERR_CIRCULAR_REFERENCE (AKERR_LAST_ERRNO_VALUE + 14) /** Indicates that a circular reference has been found in a linked list */
|
|
#define AKERR_ITERATOR_BREAK (AKERR_LAST_ERRNO_VALUE + 15) /** Used to prematurely end an iteration cycle (such as when searching a graph and the desired node has been found) */
|
|
#define AKERR_NOT_IMPLEMENTED (AKERR_LAST_ERRNO_VALUE + 16) /** A method was called that is defined but not currently implemented */
|
|
#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) */
|
|
|
|
/*
|
|
* Registry failures. These are ordinary status codes, not a private return
|
|
* enumeration: akerr_reserve_status_range() and akerr_register_status_name()
|
|
* return akerr_ErrorContext * like everything else in this library, so a refused
|
|
* reservation can be CATCH-ed, HANDLE-d, PASS-ed, or left unhandled to produce a
|
|
* stack trace and stop the program. Success returns NULL.
|
|
*/
|
|
#define AKERR_STATUS_RANGE_OVERLAP (AKERR_LAST_ERRNO_VALUE + 18) /** Some part of the range is already owned by someone else */
|
|
#define AKERR_STATUS_RANGE_FULL (AKERR_LAST_ERRNO_VALUE + 19) /** No reservation slots remain (see AKERR_MAX_RESERVED_STATUS_RANGES) */
|
|
#define AKERR_STATUS_RANGE_INVALID (AKERR_LAST_ERRNO_VALUE + 20) /** Bad count, bad owner string, or the range overflows int */
|
|
#define AKERR_STATUS_NAME_UNRESERVED (AKERR_LAST_ERRNO_VALUE + 21) /** No owner has reserved a range containing this status */
|
|
#define AKERR_STATUS_NAME_FOREIGN (AKERR_LAST_ERRNO_VALUE + 22) /** The status lies in a range reserved by a different owner */
|
|
#define AKERR_STATUS_NAME_FULL (AKERR_LAST_ERRNO_VALUE + 23) /** The name registry is full (raise AKERR_STATUS_NAME_SLOTS) */
|
|
#define AKERR_STATUS_NAME_INVALID (AKERR_LAST_ERRNO_VALUE + 24) /** NULL/empty/over-long owner, or a NULL name */
|
|
|
|
/* The last status the library defines for itself. Everything from
|
|
* AKERR_LAST_ERRNO_VALUE + 1 through here must have a registered name. */
|
|
#define AKERR_LAST_LIBRARY_STATUS AKERR_STATUS_NAME_INVALID
|
|
|
|
/*
|
|
* Status values 0 through 255 are reserved by libakerror at akerr_init() time:
|
|
* the host's errno values plus the AKERR_* codes above. Consumers allocate from
|
|
* 256 upwards. Reserving any part of this band fails with
|
|
* AKERR_STATUS_RANGE_OVERLAP naming AKERR_LIBRARY_OWNER.
|
|
*/
|
|
#define AKERR_LIBRARY_OWNER "libakerror"
|
|
#define AKERR_RESERVED_STATUS_COUNT 256
|
|
#define AKERR_FIRST_CONSUMER_STATUS AKERR_RESERVED_STATUS_COUNT
|
|
|
|
/*
|
|
* The library reserves status values 0 through 255 for itself (see akerr_init),
|
|
* which must contain every AKERR_* code above. AKERR_LAST_ERRNO_VALUE is
|
|
* derived from the host's errno list at build time, so on a platform with an
|
|
* unusually large errno space these codes could escape the band and collide
|
|
* with consumer codes allocated at 256. Fail the build instead.
|
|
*/
|
|
typedef char akerr_assert_codes_within_reserved_band[(AKERR_LAST_LIBRARY_STATUS < 256) ? 1 : -1];
|
|
|
|
#define AKERR_MAX_ARRAY_ERROR 128
|
|
|
|
|
|
typedef struct
|
|
{
|
|
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
|
int arrayid;
|
|
int status;
|
|
bool handled;
|
|
int refcount;
|
|
char fname[AKERR_MAX_ERROR_FNAME_LENGTH];
|
|
char function[AKERR_MAX_ERROR_FNAME_LENGTH];
|
|
int lineno;
|
|
bool reported;
|
|
char stacktracebuf[AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH];
|
|
char *stacktracebufptr;
|
|
} akerr_ErrorContext;
|
|
|
|
#define AKERR_NOIGNORE __attribute__((warn_unused_result))
|
|
|
|
typedef void (*akerr_ErrorUnhandledErrorHandler)(akerr_ErrorContext *errctx);
|
|
typedef void (*akerr_ErrorLogFunction)(const char *f, ...);
|
|
|
|
extern akerr_ErrorContext AKERR_ARRAY_ERROR[AKERR_MAX_ARRAY_ERROR];
|
|
/*
|
|
* Set these before starting threads. They are read on every error and written
|
|
* by nothing but your own code, so changing one while other threads are raising
|
|
* errors is a data race the library cannot mediate.
|
|
*/
|
|
extern akerr_ErrorUnhandledErrorHandler akerr_handler_unhandled_error;
|
|
extern akerr_ErrorLogFunction akerr_log_method;
|
|
/*
|
|
* The error IGNORE() last swallowed, per thread: an ignored error is a fact
|
|
* about the thread that ignored it, and one shared slot would have two threads
|
|
* overwriting each other's. Thread local only when AKERR_THREAD_SAFE is 1.
|
|
*/
|
|
extern AKERR_THREAD_LOCAL akerr_ErrorContext *__akerr_last_ignored;
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akerr_release_error(akerr_ErrorContext *ptr);
|
|
/*
|
|
* Check a context out of the pool. The returned context already carries one
|
|
* reference: finding a free slot and claiming it is a single operation under
|
|
* the pool lock, because two threads scanning at once would otherwise be handed
|
|
* the same slot. Release it with akerr_release_error() (or let RELEASE_ERROR,
|
|
* SUCCEED_RETURN or FINISH do it for you). Returns NULL when every slot is
|
|
* checked out.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akerr_next_error();
|
|
/*
|
|
* Look up (name == NULL) or register (name != NULL) the display name for a
|
|
* status. Registration succeeds only if some owner has reserved a range
|
|
* containing `status`; prefer akerr_register_status_name(), which also checks
|
|
* that the range belongs to you and raises an error saying why a registration
|
|
* was refused. This entry point cannot return an error context, so a refusal
|
|
* here is reported through akerr_log_method and reads back as "Unknown Error".
|
|
* Never returns NULL -- an unregistered status reads back as "Unknown Error".
|
|
*/
|
|
char *akerr_name_for_status(int status, char *name);
|
|
|
|
/*
|
|
* Register a display name for a status you own. `owner` must match the owner
|
|
* string passed to akerr_reserve_status_range() for the range containing
|
|
* `status`. Returns NULL on success, or an error context whose status is one of
|
|
* the AKERR_STATUS_NAME_* codes above -- CATCH it, HANDLE it, or let it
|
|
* propagate.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akerr_register_status_name(const char *owner, int status, const char *name);
|
|
|
|
/*
|
|
* Claim `count` status values starting at `first_status` for `owner`. Repeating
|
|
* an identical reservation for the same owner is a no-op; any other collision is
|
|
* refused. Returns NULL on success, or an error context whose status is one of
|
|
* the AKERR_STATUS_RANGE_* codes above. Treat any error as an initialization
|
|
* failure: either handle it or let it propagate out of your init function.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akerr_reserve_status_range(int first_status, int count, const char *owner);
|
|
void akerr_init();
|
|
void akerr_default_handler_unhandled_error(akerr_ErrorContext *ptr);
|
|
void akerr_default_logger(const char *f, ...);
|
|
int akerr_valid_error_address(akerr_ErrorContext *ptr);
|
|
/* defined in src/errno.c which is built dynamically at build time from system errno definitions */
|
|
void akerr_init_errno(void);
|
|
/*
|
|
* Internal. Names a status in the library's own reserved band on behalf of
|
|
* akerr_init() and the generated errno table, which have no caller to raise
|
|
* into: a failure here is logged and terminates the program. Not part of the
|
|
* consumer API -- use akerr_register_status_name(), which raises instead.
|
|
*/
|
|
void __akerr_name_library_status(int status, const char *name);
|
|
|
|
/*
|
|
* Internal. Bounded string copy into a fixed buffer, always NUL-terminated.
|
|
* Raises AKERR_NULLPOINTER for a NULL destination or source and AKERR_VALUE for
|
|
* a capacity that leaves no room for a terminator. Exported so the library's
|
|
* own tests can drive those guards; not part of the consumer API.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *__akerr_copy_string(char *destination, int capacity,
|
|
const char *source);
|
|
|
|
#define LOG_ERROR_WITH_MESSAGE(__err_context, __err_message) \
|
|
akerr_log_method("%s%s:%s:%d: %s %d (%s): %s", (char *)&__err_context->stacktracebuf, (char *)__FILE__, (char *)__func__, __LINE__, __err_message, __err_context->status, akerr_name_for_status(__err_context->status, NULL), __err_context->message); \
|
|
|
|
#define LOG_ERROR(__err_context) \
|
|
LOG_ERROR_WITH_MESSAGE(__err_context, "");
|
|
|
|
#define RELEASE_ERROR(__err_context) \
|
|
if ( __err_context != NULL ) { \
|
|
__err_context = akerr_release_error(__err_context); \
|
|
}
|
|
|
|
#define PREPARE_ERROR(__err_context) \
|
|
akerr_init(); \
|
|
akerr_ErrorContext __attribute__ ((unused)) *__err_context = NULL;
|
|
|
|
/*
|
|
* akerr_next_error() hands back a context that already holds one reference --
|
|
* it has to, or a second thread could be given the same slot between the scan
|
|
* and the increment. There is nothing to increment here.
|
|
*/
|
|
#define ENSURE_ERROR_READY(__err_context) \
|
|
if ( __err_context == NULL ) { \
|
|
__err_context = akerr_next_error(); \
|
|
if ( __err_context == NULL ) { \
|
|
akerr_log_method("%s:%s:%d: Unable to pull an error context from the array!", __FILE__, (char *)__func__, __LINE__); \
|
|
exit(1); \
|
|
} \
|
|
}
|
|
|
|
/*
|
|
* Append a formatted line to the error's stack-trace buffer, bounded by the
|
|
* space that remains so a deep propagation chain cannot write past the end of
|
|
* stacktracebuf. snprintf reports the length it *would* have written, which on
|
|
* truncation exceeds what it actually wrote, so the cursor advance is clamped
|
|
* to the remaining space.
|
|
*/
|
|
#define AKERR_STACKTRACE_APPEND(__err_context, ...) \
|
|
do { \
|
|
char *__akerr_stb = (char *)__err_context->stacktracebuf; \
|
|
size_t __akerr_used = (size_t)(__err_context->stacktracebufptr - __akerr_stb); \
|
|
if ( __akerr_used < AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH ) { \
|
|
size_t __akerr_rem = AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH - __akerr_used; \
|
|
int __akerr_n = snprintf(__err_context->stacktracebufptr, __akerr_rem, __VA_ARGS__); \
|
|
if ( __akerr_n < 0 ) { \
|
|
__akerr_n = 0; \
|
|
} \
|
|
__err_context->stacktracebufptr += ((size_t)__akerr_n < __akerr_rem) \
|
|
? (size_t)__akerr_n : (__akerr_rem - 1); \
|
|
} \
|
|
} while ( 0 )
|
|
|
|
/*
|
|
* Failure and success methods for functions that return akerr_ErrorContext *
|
|
*/
|
|
|
|
#define FAIL_ZERO_RETURN(__err_context, __x, __err, __message, ...) \
|
|
if ( __x == 0 ) { \
|
|
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
|
return __err_context; \
|
|
}
|
|
|
|
#define FAIL_NONZERO_RETURN(__err_context, __x, __err, __message, ...) \
|
|
if ( __x != 0 ) { \
|
|
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
|
return __err_context; \
|
|
}
|
|
|
|
#define FAIL_RETURN(__err_context, __err, __message, ...) \
|
|
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
|
return __err_context;
|
|
|
|
#define SUCCEED_RETURN(__err_context) \
|
|
RELEASE_ERROR(__err_context); \
|
|
return NULL;
|
|
|
|
/*
|
|
* Failure and success methods for use inside of ATTEMPT() blocks
|
|
*/
|
|
|
|
#define FAIL_ZERO_BREAK(__err_context, __x, __err, __message, ...) \
|
|
if ( __x == 0 ) { \
|
|
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
|
break; \
|
|
}
|
|
|
|
#define FAIL_NONZERO_BREAK(__err_context, __x, __err, __message, ...) \
|
|
if ( __x != 0 ) { \
|
|
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
|
break; \
|
|
}
|
|
|
|
#define FAIL_BREAK(__err_context, __err_, __message, ...) \
|
|
FAIL(__err_context, __err_, __message, ##__VA_ARGS__); \
|
|
break;
|
|
|
|
#define SUCCEED_BREAK(__err_context) \
|
|
SUCCEED(__err_context); \
|
|
break;
|
|
|
|
/*
|
|
* General failure and success methods
|
|
*/
|
|
|
|
#define FAIL(__err_context, __err, __message, ...) \
|
|
ENSURE_ERROR_READY(__err_context); \
|
|
__err_context->status = __err; \
|
|
snprintf((char *)__err_context->fname, AKERR_MAX_ERROR_FNAME_LENGTH, "%s", __FILE__); \
|
|
snprintf((char *)__err_context->function, AKERR_MAX_ERROR_FUNCTION_LENGTH, "%s", __func__); \
|
|
__err_context->lineno = __LINE__; \
|
|
snprintf((char *)__err_context->message, AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH, __message, ## __VA_ARGS__); \
|
|
AKERR_STACKTRACE_APPEND(__err_context, "%s:%s:%d: %d (%s) : %s\n", (char *)__err_context->fname, (char *)__err_context->function, __err_context->lineno, __err_context->status, akerr_name_for_status(__err_context->status, NULL), (__err_context->message == NULL ? "" : __err_context->message));
|
|
|
|
|
|
#define SUCCEED(__err_context) \
|
|
ENSURE_ERROR_READY(__err_context); \
|
|
__err_context->status = 0;
|
|
|
|
/*
|
|
* Defines for the ATTEMPT/CATCH/CLEANUP/PROCESS/HANDLE/FINISH process
|
|
*/
|
|
|
|
#define ATTEMPT \
|
|
switch ( 0 ) { \
|
|
case 0: \
|
|
|
|
#define VALID(__err_context, __stmt) \
|
|
__stmt; \
|
|
if ( akerr_valid_error_address(__err_context) == 0 ) { \
|
|
__err_context = NULL; \
|
|
FAIL(__err_context, AKERR_BADEXC, "Received (akerr_ErrorContext *) from an invalid memory region. (Did the method finish without calling SUCCEED_RETURN?)"); \
|
|
}
|
|
|
|
#define DETECT(__err_context, __stmt) \
|
|
VALID(__err_context, __stmt); \
|
|
if ( __err_context != NULL ) { \
|
|
if ( __err_context->status != 0 ) { \
|
|
AKERR_STACKTRACE_APPEND(__err_context, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
|
|
break; \
|
|
} \
|
|
}
|
|
|
|
#define CATCH(__err_context, __stmt) \
|
|
DETECT(__err_context, __err_context = __stmt);
|
|
|
|
#define PASS(__err_context, __stmt) \
|
|
switch ( 0 ) { \
|
|
case 0: \
|
|
DETECT(__err_context, __err_context = __stmt); \
|
|
} \
|
|
FINISH_LOGIC(__err_context, true);
|
|
|
|
#define IGNORE(__stmt) \
|
|
__akerr_last_ignored = __stmt; \
|
|
if ( __akerr_last_ignored != NULL ) { \
|
|
LOG_ERROR_WITH_MESSAGE(__akerr_last_ignored, "** IGNORED ERROR **"); \
|
|
}
|
|
|
|
#define CLEANUP \
|
|
};
|
|
|
|
#define PROCESS(__err_context) \
|
|
if ( __err_context != NULL ) { \
|
|
switch ( __err_context->status ) { \
|
|
case 0: \
|
|
__err_context->handled = true;
|
|
|
|
#define HANDLE(__err_context, __err_status) \
|
|
break; \
|
|
case __err_status: \
|
|
__err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \
|
|
__err_context->handled = true;
|
|
|
|
#define HANDLE_GROUP(__err_context, __err_status) \
|
|
case __err_status: \
|
|
__err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \
|
|
__err_context->handled = true;
|
|
|
|
#define HANDLE_DEFAULT(__err_context) \
|
|
break; \
|
|
default: \
|
|
__err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \
|
|
__err_context->handled = true;
|
|
|
|
#define FINISH_LOGIC(__err_context, __pass_up) \
|
|
if ( __err_context != NULL ) { \
|
|
if ( __err_context->handled == false && __pass_up == true ) { \
|
|
return __err_context; \
|
|
} \
|
|
} \
|
|
|
|
#define FINISH(__err_context, __pass_up) \
|
|
}; \
|
|
}; \
|
|
FINISH_LOGIC(__err_context, __pass_up) \
|
|
RELEASE_ERROR(__err_context);
|
|
|
|
#define FINISH_NORETURN(__err_context) \
|
|
}; \
|
|
}; \
|
|
if ( __err_context != NULL ) { \
|
|
if ( __err_context->handled == false ) { \
|
|
LOG_ERROR_WITH_MESSAGE(__err_context, "Unhandled Error"); \
|
|
akerr_handler_unhandled_error(__err_context); \
|
|
} \
|
|
} \
|
|
RELEASE_ERROR(__err_context);
|
|
|
|
#endif // _AKERR_H_
|