The thread-safety section filed two different things under "does not cover, and cannot": sharing a context between threads, and passing one to another thread. Only the first is unsupported. Transfer already works by construction -- the reference count is the only field the library reads across an ownership boundary, and it is only ever touched under the pool lock, so akerr_release_error() does not care which thread checked the slot out. The pool is process-global, not thread-local, so a context outlives the thread that raised it. Calling that unsupported told readers the worker/collector shape was off the table, which either cost them the pattern or cost them the stack trace when they rolled their own struct instead. Split the bullet: transfer joins the covered list and gets its own section with the rule, the worked pattern, and the four receiving-side hazards (PREPARE_ERROR cannot adopt, CATCH assigns over the pointer, FINISH in a void helper still parses its return, and an unhandled error now terminates from the collector's thread). Sharing keeps the "cannot" bullet, narrowed to what it actually is. err_threads_handoff.c proves it: the existing thread tests all keep every context on the thread that raised it, so the transfer path was exercised nowhere. Seven producers hand errors to one collector through a bounded mutex/condvar queue -- the mutex is the thing under test, since it is what publishes the unlocked content writes -- and the collector asserts the context is still a live slot at refcount 1, that message and trace arrive whole and in each producer's order, that the slot was never recycled in flight, and that a thread which never called akerr_next_error() can release it. A second phase reads a context whose raising thread has already exited. Also document why copying a context by assignment is silently wrong: stacktracebufptr is self-referential, so the copy's cursor points into the source's buffer and the first append corrupts a slot the copier no longer owns. TODO.md records the akerr_copy_error() shape that would fix it and the trigger for building it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
481 lines
22 KiB
C
481 lines
22 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];
|
|
|
|
/*
|
|
* A process exit status is one byte wide. exit() takes an int, but the kernel
|
|
* keeps only the low 8 bits of it and throws the rest away, so 0 through 255
|
|
* are the only statuses that can also be exit codes -- and every consumer status
|
|
* begins at AKERR_FIRST_CONSUMER_STATUS (256). No wider variant exists to reach
|
|
* for: _exit(), _Exit(), quick_exit() and the raw exit_group syscall all
|
|
* truncate identically, and even waitid()'s int-wide si_status reports the
|
|
* truncated value, because the truncation happened before the parent looked.
|
|
*
|
|
* akerr_exit() therefore substitutes AKERR_EXIT_STATUS_UNREPRESENTABLE for any
|
|
* status it cannot deliver intact, rather than passing the low byte -- status
|
|
* 256 would exit 0 and report success. 125 is the conventional "the tool itself
|
|
* failed" code (126, 127 and 128+n belong to the shell). It is inside the
|
|
* library's reserved band, so it is also some host's errno: the exit code says
|
|
* only that the process died of an error, and the stack trace carries the real
|
|
* status.
|
|
*/
|
|
#define AKERR_EXIT_STATUS_MAX 255
|
|
#define AKERR_EXIT_STATUS_UNREPRESENTABLE 125
|
|
|
|
#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))
|
|
/* akerr_exit() does not come back, and the compiler should know it: a handler
|
|
* whose last statement is a call to it is complete, not falling off the end. */
|
|
#define AKERR_NORETURN __attribute__((noreturn))
|
|
|
|
typedef void (*akerr_ErrorUnhandledErrorHandler)(akerr_ErrorContext *errctx);
|
|
typedef void (*akerr_ErrorLogFunction)(const char *f, ...);
|
|
|
|
/*
|
|
* The pool. Process-global, not thread-local: a context outlives the thread that
|
|
* raised it, which is what lets one be handed to another thread and released
|
|
* there.
|
|
*/
|
|
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;
|
|
|
|
/*
|
|
* Drop one reference, returning NULL once the last one is gone so the caller can
|
|
* null its own pointer.
|
|
*
|
|
* This need not be the thread that checked the context out. The reference count
|
|
* is the only field the library reads across threads, and it is only ever
|
|
* touched under the pool lock, so a context handed to another thread is released
|
|
* there. Exactly once, though: releasing a stale pointer takes the
|
|
* refcount-zero branch a second time and wipes a slot that by then holds
|
|
* somebody else's live error.
|
|
*/
|
|
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();
|
|
/*
|
|
* Terminate the process, reporting `status`. Use this instead of exit()
|
|
* anywhere you are leaving on account of an akerr status -- an unhandled-error
|
|
* handler of your own, a CLI's top-level HANDLE block, an init routine that
|
|
* cannot continue -- so that every exit out of the library's status space maps
|
|
* the same way.
|
|
*
|
|
* Exits with `status` when 0 <= status <= AKERR_EXIT_STATUS_MAX, and with
|
|
* AKERR_EXIT_STATUS_UNREPRESENTABLE otherwise (see above). Status 0 exits 0:
|
|
* zero is this library's success status, and passing it here says the program
|
|
* finished, not that it failed with a code that got lost.
|
|
*/
|
|
void AKERR_NORETURN akerr_exit(int status);
|
|
/*
|
|
* The default akerr_handler_unhandled_error: logs nothing further -- the stack
|
|
* trace has already been printed by the time it runs -- and hands `ptr->status`
|
|
* to akerr_exit(), or exits 1 when `ptr` is NULL. Replace it if you need a
|
|
* different mapping, and call akerr_exit() from your replacement.
|
|
*/
|
|
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_
|