diff --git a/include/ircd/exception.h b/include/ircd/exception.h index 8d52592b8..8169f96fe 100644 --- a/include/ircd/exception.h +++ b/include/ircd/exception.h @@ -28,6 +28,7 @@ namespace ircd { + // Root exception struct exception; // Prefer ircd::terminate() to std::terminate() if possible. @@ -35,6 +36,11 @@ namespace ircd [[noreturn]] void terminate(std::exception_ptr) noexcept; [[noreturn]] void terminate() noexcept; + // Terminates in debug mode; throws in release mode; always logs critical. + [[noreturn]] void assertion(const std::exception &) noexcept(RB_DEBUG); + [[noreturn]] void assertion(std::exception_ptr) noexcept(RB_DEBUG); + [[noreturn]] void assertion() noexcept(RB_DEBUG); + // Can be used to clobber the std::terminate_handler void aborting() noexcept; } @@ -147,6 +153,31 @@ struct name \ } \ }; +/// Creates an assertion-type exception. +/// +/// Throwable exception which will terminate on construction in debug mode +/// but throw normally in release mode. Ideally this should never be thrown +/// in release mode because the termination in debug means a test can never +/// pass and the triggering callsite should be eliminated. Nevertheless it +/// throws normally in release mode. +#define IRCD_ASSERTION(parent, name) \ +struct name \ +:parent \ +{ \ + template \ + name(const char *const &fmt = " ", args&&... ap) noexcept(RB_DEBUG) \ + :parent{generate_skip} \ + { \ + generate(#name, fmt, va_rtti{std::forward(ap)...}); \ + ircd::assertion(*this); \ + } \ + \ + name(generate_skip_t) noexcept(RB_DEBUG) \ + :parent{generate_skip} \ + { \ + } \ +}; \ + namespace ircd { /// Root error exception type. Inherit from this. @@ -155,6 +186,10 @@ namespace ircd /// /// IRCD_EXCEPTION(ircd::error, error) /// - IRCD_EXCEPTION(exception, error) // throw ircd::error("something bad") + IRCD_EXCEPTION(exception, error) // throw ircd::error("something bad") IRCD_EXCEPTION(error, user_error) // throw ircd::user_error("something silly") + + // Assertion errors; see IRCD_ASSERTION docs. + IRCD_ASSERTION(exception, assertive) + IRCD_ASSERTION(assertive, not_implemented) } diff --git a/ircd/exception.cc b/ircd/exception.cc index e034d0bd5..3b33f98ff 100644 --- a/ircd/exception.cc +++ b/ircd/exception.cc @@ -47,6 +47,44 @@ noexcept return size; } +void +ircd::assertion() +noexcept(RB_DEBUG) +{ + if(std::uncaught_exceptions()) + { + assertion(std::current_exception()); + } else { + log::critical("IRCd Assertion without active exception."); + assert(0); + throw assertive{}; + } +} + +void +ircd::assertion(std::exception_ptr eptr) +noexcept(RB_DEBUG) try +{ + std::rethrow_exception(eptr); +} +catch(const std::exception &e) +{ + assertion(e); +} + +void +ircd::assertion(const std::exception &e) +noexcept(RB_DEBUG) +{ + log::critical("IRCd Assertion %s", e.what()); + + #ifdef RB_DEBUG + terminate(e); + #else + throw e; + #endif +} + void ircd::terminate() noexcept @@ -76,7 +114,7 @@ ircd::terminate(const std::exception &e) noexcept { log::critical("IRCd Terminated: %s", e.what()); - std::terminate(); + throw e; } [[noreturn]] static void