summaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/fatal_error.h180
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)
+//