Every libakgl test suite could report success while failing. libakerror's default unhandled-error handler ended in exit(errctx->status), an exit status is one byte wide, and libakgl's band starts at 256 -- so AKGL_ERR_SDL, the most common failure a library built on SDL can have, exited 0 and CTest recorded a pass. tests/character.c aborted at its second of four tests on a bad renderer and was green for months. 0.5.0 worked around that here with TEST_TRAP_UNHANDLED_ERRORS() in tests/testutil.h, and TODO.md ended the entry saying any consumer's suites have the same problem and it was worth raising upstream. It was. 2.0.1 fixes it at the source: akerr_exit() owns the mapping and the default handler calls it, so 0 exits 0, 1 through 255 exit themselves, and anything else exits AKERR_EXIT_STATUS_UNREPRESENTABLE (125). The trap and its 21 call sites are gone. Verified by putting the original failure back rather than by reading the release notes: a FAIL_BREAK(AKGL_ERR_SDL) in tests/character.c's main exits 125 and CTest reports a failure. A standalone consumer raising the same status unhandled exits 125 where it exited 0 before. tests/actor.c installs its own handler and called exit(errctx->status) from it, which is the same defect one layer up. It calls akerr_exit() now. 2.0.0 also makes the error pool and the status registry thread safe, which libakgl needs more than it knew: audio_stream_callback raises error contexts on SDL's audio thread. With an unlocked pool that callback and the main thread could scan AKERR_ARRAY_ERROR at the same time and be handed the same slot. The comment there says so. This is a hard dependency floor, not a preference. 2.0.0 moved __akerr_last_ignored to thread-local storage and made akerr_next_error() return a context that already holds its reference, and both expand at libakgl's call sites -- and at a consumer's, because akerror.h is part of libakgl's public interface. Mixing headers and libraries across that line double-counts every reference and never returns a pool slot. The soname moved to libakerror.so.2; include/akgl/error.h now also feature- tests AKERR_EXIT_STATUS_UNREPRESENTABLE, which is the narrowest probe for 2.0.1 since libakerror publishes no version macro. 0.7.0 for that reason: libakgl's own ABI is unchanged, but the one it re-exports through its headers is not. TODO.md records the pkg-config gap this makes sharper -- akgl.pc names no dependencies at all, so nothing tells a pkg-config consumer which libakerror it needs. Clean build, 26/26 ctest, memcheck clean, warning-clean at -Wall -Werror. libakgl.so.0.7 links libakerror.so.2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8T5FAYXE8HEJqFLCYwNNc
100 lines
5.1 KiB
C
100 lines
5.1 KiB
C
/**
|
|
* @file error.h
|
|
* @brief Declares the public error API.
|
|
*/
|
|
|
|
#ifndef _AKGL_ERROR_H_
|
|
#define _AKGL_ERROR_H_
|
|
|
|
#include <akerror.h>
|
|
|
|
/*
|
|
* libakerror 1.0.0 is the floor. That release moved the status-name table into a
|
|
* private registry -- AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, the
|
|
* registry entry points raise akerr_ErrorContext * instead of returning int, and
|
|
* the library gained an soname -- so a translation unit that pairs this header
|
|
* with a pre-1.0.0 akerror.h is an ABI mismatch, not just a compile problem.
|
|
*
|
|
* libakerror publishes no version macro, so this feature-tests on
|
|
* AKERR_FIRST_CONSUMER_STATUS, which that release introduced, rather than on a
|
|
* version number that does not exist. Without the guard an embedded build is
|
|
* fine but a stale installed header fails much further in, on the AKGL_ERR_*
|
|
* codes below and again inside src/heap.c.
|
|
*
|
|
* See deps/libakerror/UPGRADING.md.
|
|
*/
|
|
#ifndef AKERR_FIRST_CONSUMER_STATUS
|
|
#error "libakgl requires libakerror >= 2.0.1: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror."
|
|
#endif
|
|
// 2.0.0 is an ABI break -- akerr_next_error() returns a context that already
|
|
// holds its reference, and __akerr_last_ignored is thread-local -- and both of
|
|
// those expand at *this* library's call sites through IGNORE and the FAIL
|
|
// macros. A libakgl built against a 1.x header and linked against 2.x
|
|
// double-counts every reference and never returns a slot to the pool. The
|
|
// soname moved to libakerror.so.2 so the two cannot be mixed by accident, but
|
|
// the header can still be stale in an install tree, so check it here too.
|
|
//
|
|
// AKERR_EXIT_STATUS_UNREPRESENTABLE arrived in 2.0.1 and is the narrowest thing
|
|
// to probe for: libakerror publishes no version macro.
|
|
#ifndef AKERR_EXIT_STATUS_UNREPRESENTABLE
|
|
#error "libakgl requires libakerror >= 2.0.1: the akerror.h on the include path predates akerr_exit(). Rebuild and reinstall libakerror."
|
|
#endif
|
|
|
|
// Silences -Wformat-truncation on a string concatenation that genuinely may not
|
|
// fit -- e.g. joining two PATH_MAX strings into one AKGL_MAX_STRING_LENGTH
|
|
// buffer. The line has to be drawn somewhere or the buffers grow forever to
|
|
// keep the compiler happy.
|
|
//
|
|
// **Nothing in libakgl uses these any more.** Both sites -- the path join in
|
|
// akgl_path_relative and the tileset image join in
|
|
// akgl_tilemap_load_layer_image -- now go through aksl_snprintf, which raises
|
|
// AKERR_OUTOFBOUNDS on truncation instead. That is the better answer: the
|
|
// compiler was right both times, and silencing it left the truncation
|
|
// happening and unreported. Kept because they are public and a consumer may
|
|
// have them; see TODO.md.
|
|
#define DISABLE_GCC_WARNING_FORMAT_TRUNCATION \
|
|
_Pragma("GCC diagnostic push") \
|
|
_Pragma("GCC diagnostic ignored \"-Wformat-truncation\"")
|
|
|
|
#define RESTORE_GCC_WARNINGS \
|
|
_Pragma("GCC diagnostic pop")
|
|
|
|
// libakerror reserves statuses 0-255 for the host's errno values and its own
|
|
// AKERR_* codes; consumers allocate from AKERR_FIRST_CONSUMER_STATUS upward.
|
|
// These are fixed offsets from that base rather than from AKERR_LAST_ERRNO_VALUE
|
|
// so that a libc which grows an errno cannot move them out from under us.
|
|
//
|
|
// akgl_error_init() reserves this whole band in one call and registers a name
|
|
// for every code below. Add a code here and you must name it there, or it
|
|
// prints as "Unknown Error" in every stack trace that carries it.
|
|
#define AKGL_ERR_OWNER "libakgl"
|
|
#define AKGL_ERR_BASE AKERR_FIRST_CONSUMER_STATUS
|
|
|
|
#define AKGL_ERR_SDL (AKGL_ERR_BASE + 0) /**< An SDL call failed; the message carries SDL_GetError() */
|
|
#define AKGL_ERR_REGISTRY (AKGL_ERR_BASE + 1) /**< A registry property or lookup operation failed */
|
|
#define AKGL_ERR_HEAP (AKGL_ERR_BASE + 2) /**< A heap pool has no free object left to hand out */
|
|
#define AKGL_ERR_BEHAVIOR (AKGL_ERR_BASE + 3) /**< A component did not behave the way its contract requires */
|
|
#define AKGL_ERR_LOGICINTERRUPT (AKGL_ERR_BASE + 4) /**< Actor logic is telling the physics simulator to skip it */
|
|
|
|
// One past the last libakgl status. The reservation is all-or-nothing -- a
|
|
// subset or superset of an existing one is refused -- so this must stay one
|
|
// past the highest code above.
|
|
#define AKGL_ERR_LIMIT (AKGL_ERR_BASE + 5)
|
|
#define AKGL_ERR_COUNT (AKGL_ERR_LIMIT - AKGL_ERR_BASE)
|
|
|
|
/**
|
|
* @brief Claim the libakgl status band and register a name for every code in it.
|
|
*
|
|
* Call this before anything else in libakgl. Every other subsystem raises
|
|
* AKGL_ERR_* codes, and a code raised before this runs carries no name into its
|
|
* stack trace. Repeating the call is a no-op, so a program that cannot order its
|
|
* initialization precisely may call it more than once.
|
|
*
|
|
* @throws AKERR_STATUS_RANGE_OVERLAP When another component already owns part of the band.
|
|
* @throws AKERR_STATUS_RANGE_FULL When libakerror has no reservation slots left.
|
|
* @throws AKERR_STATUS_NAME_FULL When libakerror's name registry is full.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_error_init(void);
|
|
|
|
#endif // _AKGL_ERROR_H_
|