diff options
Diffstat (limited to 'src/util/fatal_error.h')
| -rw-r--r-- | src/util/fatal_error.h | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/src/util/fatal_error.h b/src/util/fatal_error.h new file mode 100644 index 0000000..ea4d168 --- /dev/null +++ b/src/util/fatal_error.h @@ -0,0 +1,180 @@ +// This file is part of the 64k demo project. +// Provides fatal error checking macros that can be stripped for final builds. +// These macros compile to nothing when FINAL_STRIP is enabled. + +#pragma once + +#include <cstdio> +#include <cstdlib> + +// Build Mode Hierarchy: +// - Debug: Full error checking with messages and file:line info +// - STRIP_ALL: Full error checking (same as Debug) +// - FINAL_STRIP: NO error checking (all macros compile to nothing) +// +// FINAL_STRIP implies STRIP_ALL (stricter superset) + +#if defined(FINAL_STRIP) + +// ============================================================================== +// FINAL_STRIP: All error checking compiled out (zero runtime cost) +// ============================================================================== + +// Conditional fatal error check - compiles to nothing +#define FATAL_CHECK(cond, ...) ((void)0) + +// Unconditional fatal error - compiles to nothing +#define FATAL_ERROR(...) ((void)0) + +// Unreachable code marker - compiles to nothing +#define FATAL_UNREACHABLE() ((void)0) + +// Assertion-style check (for documenting invariants) - compiles to nothing +#define FATAL_ASSERT(cond) ((void)0) + +// Block-based error checking - code inside is stripped entirely +#define FATAL_CODE_BEGIN if (0) { +#define FATAL_CODE_END } + +#else + +// ============================================================================== +// Debug / STRIP_ALL: Full error checking enabled +// ============================================================================== + +// Conditional fatal error check with formatted message +// Usage: FATAL_CHECK(x >= max, "x out of bounds: %d >= %d\n", x, max); +// +// If condition is TRUE (error detected): +// - Prints "FATAL: <message> [file.cc:line]" to stderr +// - Calls abort() +// +// Example output: +// FATAL: write_pos out of bounds: write=100 >= capacity=50 [ring_buffer.cc:57] +#define FATAL_CHECK(cond, ...) \ + do { \ + if (cond) { \ + fprintf(stderr, "FATAL: " __VA_ARGS__); \ + fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ + abort(); \ + } \ + } while (0) + +// Unconditional fatal error (for impossible code paths) +// Usage: FATAL_ERROR("Should never reach here!"); +// +// Always prints message and aborts. +// +// Example output: +// FATAL: Should never reach here! [main.cc:123] +#define FATAL_ERROR(...) \ + do { \ + fprintf(stderr, "FATAL: " __VA_ARGS__); \ + fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ + abort(); \ + } while (0) + +// Unreachable code marker (for switch default cases, impossible branches) +// Usage: +// switch (type) { +// case A: return handle_a(); +// case B: return handle_b(); +// default: FATAL_UNREACHABLE(); +// } +#define FATAL_UNREACHABLE() FATAL_ERROR("Unreachable code executed") + +// Assertion-style check (for documenting invariants) +// Usage: FATAL_ASSERT(buffer != nullptr); +// +// If condition is FALSE (assertion failed): +// - Prints "FATAL: Assertion failed: <condition> [file.cc:line]" +// - Calls abort() +// +// Example output: +// FATAL: Assertion failed: buffer != nullptr [audio.cc:42] +#define FATAL_ASSERT(cond) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "FATAL: Assertion failed: %s [%s:%d]\n", #cond, \ + __FILE__, __LINE__); \ + abort(); \ + } \ + } while (0) + +// Block-based error checking (for complex validation logic) +// Usage: +// FATAL_CODE_BEGIN +// static int callback_depth = 0; +// ++callback_depth; +// if (callback_depth > 1) { +// FATAL_ERROR("Callback re-entered! depth=%d", callback_depth); +// } +// FATAL_CODE_END +// +// The entire block is stripped when FINAL_STRIP is enabled. +#define FATAL_CODE_BEGIN +#define FATAL_CODE_END + +#endif /* defined(FINAL_STRIP) */ + +// ============================================================================== +// Usage Guidelines +// ============================================================================== +// +// When to use each macro: +// +// FATAL_CHECK(cond, msg, ...): +// - Most common use case (90% of error checks) +// - Bounds checking: FATAL_CHECK(idx >= size, "Index %d >= %d\n", idx, size) +// - Null checking: FATAL_CHECK(ptr == nullptr, "Null pointer: %s\n", name) +// - Range validation: FATAL_CHECK(x < min || x > max, "Out of range\n") +// +// FATAL_ERROR(msg, ...): +// - Unconditional errors (should-never-happen cases) +// - Example: FATAL_ERROR("Invalid state: %d\n", state) +// +// FATAL_UNREACHABLE(): +// - Switch default cases that should never be reached +// - Impossible code paths after exhaustive checks +// - Example: switch (enum) { ... default: FATAL_UNREACHABLE(); } +// +// FATAL_ASSERT(cond): +// - Documenting invariants (not error handling) +// - Pre/post conditions +// - Example: FATAL_ASSERT(count > 0 && count < MAX_COUNT) +// +// FATAL_CODE_BEGIN/END: +// - Complex validation logic that cannot be expressed as single condition +// - Temporary state tracking for validation +// - Example: Callback re-entry detection with static counter +// +// ============================================================================== +// Size Impact +// ============================================================================== +// +// Debug / STRIP_ALL builds: +// - Each FATAL_CHECK: ~50-80 bytes (depends on message length) +// - Includes fprintf() call, error message string, file:line info +// +// FINAL_STRIP builds: +// - All macros: 0 bytes (compiled out entirely) +// - Estimated savings: ~500-600 bytes for typical project +// +// ============================================================================== +// Testing Strategy +// ============================================================================== +// +// Always test both modes before committing: +// +// 1. Normal build (error checking enabled): +// cmake -S . -B build +// cmake --build build && cd build && ctest +// +// 2. FINAL_STRIP build (error checking stripped): +// cmake -S . -B build_final -DDEMO_FINAL_STRIP=ON +// cmake --build build_final +// +// Or use convenience commands: +// make final (CMake target) +// ./scripts/build_final.sh (Script) +// |
