Skip to content

Commit

Permalink
feat: panic: add ASSERT, UNREACHABLE, UNIMPLEMENTED, safer env var
Browse files Browse the repository at this point in the history
Require `SILO_PANIC` env var to be set to `panic` instead of `DEBUG`
to be `1` before calling abort, to avoid the risk of accidentally
triggering it.
  • Loading branch information
pflanze committed Sep 9, 2024
1 parent 2e2a31d commit 867d08f
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 22 deletions.
58 changes: 47 additions & 11 deletions include/silo/common/panic.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,55 @@

namespace silo::common {

void panic(const std::string& msg, const char* file, int line);
/// ** This is the basic building block for the various panicking
/// features offered in this file. You probably want to use the
/// `PANIC` macro instead of `panic` directly, to get format
/// functionality and file/line number information! **
///
/// `panic` stops execution because of a situation that the author of
/// the program expected to never happen, i.e. that is against
/// expectations of internal consistency, and means that there is
/// either a bug, or a misunderstanding of the consistency rules on
/// behalf of the programmer.
///
/// By default, `panic` throws a `std::runtime_error` exception, so
/// that the program can continue by e.g. abandoning the current http
/// connection but continue servicing other and future
/// connections. OTOH, to make debugging via gdb or core dumps
/// possible even when the code that captures exceptions can't be
/// disabled, `panic` can be instructed at runtime to call `abort`
/// instead by setting the `SILO_PANIC` environment variable to the
/// string `abort` (with any other value, or when unset, panic
/// silently throws the mentioned exception instead).
[[noreturn]] void panic(const std::string& msg, const char* file, int line);

/// Passes arguments to `fmt::format` (at least a format string
/// argument is required), then if the `DEBUG` environment variable is
/// set to anything else than the string "0", the resulting string is
/// printed to stderr and `abort` is called, otherwise a
/// `std::runtime_error` is thrown (details subject to
/// change). `PANIC` should only be used in situations that can never
/// occur unless there is a bug; the aim, besides allowing the
/// programmer to declare clearly what constitutes buggy behaviour, is
/// to allow these situations to be debugged via a debugger like gdb
/// without needing contortions like setting breakpoints, or also to
/// allow the collection of core dumps of these situations.
/// argument is required) and adds file and line information, then
/// calls `panic`.
#define PANIC(...) silo::common::panic(fmt::format(__VA_ARGS__), __FILE__, __LINE__)

/// Denotes a place that theoretically can't be reached. Follows the
/// same path as `PANIC` when reached.
#define UNREACHABLE() silo::common::unreachable(__FILE__, __LINE__)

[[noreturn]] void unreachable(const char* file, int line);

/// Denotes a missing implementation. Follows the same path as `PANIC`
/// when reached.
#define UNIMPLEMENTED() silo::common::unimplemented(__FILE__, __LINE__)

[[noreturn]] void unimplemented(const char* file, int line);

/// Asserts that the expression `e` evaluates to true. On failure
/// calls `panic` with the stringification of the code `e` and
/// file/line information.
#define ASSERT(e) \
do { \
if (!(e)) { \
silo::common::assertFailure(#e, __FILE__, __LINE__); \
} \
} while (0)

[[noreturn]] void assertFailure(const char* msg, const char* file, int line);

} // namespace silo::common
47 changes: 36 additions & 11 deletions src/silo/common/panic.cpp
Original file line number Diff line number Diff line change
@@ -1,31 +1,56 @@
#include "silo/common/panic.h"

#include <cstdlib>
#include <cstring>
#include <iostream>

namespace silo::common {

namespace {
bool is0(const char* str) {
return str[0] == '0' && str[1] == '\0';
}
} // namespace

void panic(const std::string& msg, const char* file, int line) {
const char* env = getenv("DEBUG");
bool debug;
[[noreturn]] void panic(
const std::string& prefix,
const std::string& msg,
const char* file,
int line
) {
const char* env = getenv("SILO_PANIC");
bool do_abort;
if (env) {
debug = !is0(env);
do_abort = strcmp(env, "abort") == 0;
} else {
debug = false;
do_abort = false;
}
auto full_msg = fmt::format("PANIC: {} at {}:{}", msg, file, line);
if (debug) {
auto full_msg = fmt::format("{}{} at {}:{}", prefix, msg, file, line);
if (do_abort) {
std::cerr << full_msg << "\n" << std::flush;
abort();
} else {
throw std::runtime_error(full_msg);
}
}

} // namespace

[[noreturn]] void panic(const std::string& msg, const char* file, int line) {
panic("PANIC: ", msg, file, line);
}

[[noreturn]] void unreachable(const char* file, int line) {
panic(
"UNREACHABLE: ",
"Please report this as a bug in SILO: this code should never be reachable",
file,
line
);
}

[[noreturn]] void unimplemented(const char* file, int line) {
panic("UNIMPLEMENTED: ", "This execution path is not implemented yet", file, line);
}

[[noreturn]] void assertFailure(const char* msg, const char* file, int line) {
panic("ASSERT failure: ", msg, file, line);
}

} // namespace silo::common

0 comments on commit 867d08f

Please sign in to comment.