libcruft-util/debug/panic.hpp

152 lines
4.1 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2010-2019 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include <fmt/core.h>
#include <type_traits>
#include <utility>
///////////////////////////////////////////////////////////////////////////////
// We define an identity macro for panic so that TCL and GCC don't decide to
// fuck us over by redefine the symbol.
//
// Apparently this can't be taken care of via a '-Dpanic=panic' argument,
// because it just ignores it for reasons I can't figure out.
#ifdef panic
#undef panic
#endif
#define panic panic
//-----------------------------------------------------------------------------
namespace cruft::debug::detail {
void
panic [[noreturn]] (const char *msg);
void not_implemented [[noreturn]] (const char *msg);
void unreachable [[noreturn]] (const char *msg);
}
//-----------------------------------------------------------------------------
constexpr void
panic [[noreturn]] (const char *msg)
{
! msg
? panic (msg)
: cruft::debug::detail::panic (msg);
}
//-----------------------------------------------------------------------------
inline void
panic [[noreturn]] (const std::string &msg)
{
panic (msg.c_str ());
}
template <typename TagT = void>
constexpr void consteval_panic (void) noexcept {
if constexpr (std::is_same_v<TagT, void>) {
throw nullptr;
}
}
///////////////////////////////////////////////////////////////////////////////
// not_implemented/unreachable/panic must be callable from constexpr contexts.
// but they rely on functions that aren't constexpr to perform the controlled
// abort.
//
// we can work around this in the same way assert does by using a conditional
// that hides an extern function that actually does the work. as we can't
// utilise external state this has to be the message variable which will
// assume isn't ever null.
//
// to avoid warnings about a return from a noreturn function we recurse into
// ourself in the alternate case. this branch shouldn't ever be taken, but if
// it is we were planning on crashing anyway...
///////////////////////////////////////////////////////////////////////////////
constexpr void
not_implemented [[noreturn]] (const char *msg)
{
! msg
? not_implemented (msg)
: cruft::debug::detail::not_implemented (msg);
}
constexpr void
not_implemented [[noreturn]] (void)
{
not_implemented ("operation not implemented");
}
constexpr void unimplemented [[noreturn]] (void) { not_implemented (); }
constexpr void unimplemented [[noreturn]] (const char *msg) { not_implemented (msg); }
///////////////////////////////////////////////////////////////////////////////
constexpr void
unreachable [[noreturn]] (const char *msg)
{
! msg
? unreachable (msg)
: cruft::debug::detail::unreachable (msg);
}
constexpr void
unreachable [[noreturn]] (void)
{
unreachable ("unreachable code isn't");
}
///////////////////////////////////////////////////////////////////////////////
/// report a fatal error induced by an unhandled value, especially in switch
/// statements. will almost invariably abort the application.
namespace cruft::debug::detail {
template <typename T>
T
unhandled [[noreturn]] (T &&t) noexcept
{
// If we've been given an enum we should handle it like its underlying
// type so that when we end up logging it we don't accidentally
// trigger an infinite loop of calls to `unhandled` within the
// ostream operator. It doesn't have to be pretty, just functional.
using base_type = std::remove_reference_t<std::decay_t<T>>;
if constexpr (std::is_enum_v<base_type>) {
unhandled (static_cast<std::underlying_type_t<base_type>> (t));
} else {
::panic (fmt::format ("unhandled value: {}", t));
}
}
}
template <typename T>
constexpr T
unhandled [[noreturn]] (T &&t) noexcept
{
cruft::debug::detail::unhandled (std::forward<T> (t));
}