//======================================================================// // Copyright (C) 2016-2018 Jonathan Müller // // This software is provided 'as-is', without any express or // implied warranty. In no event will the authors be held // liable for any damages arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute // it freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; // you must not claim that you wrote the original software. // If you use this software in a product, an acknowledgment // in the product documentation would be appreciated but // is not required. // // 2. Altered source versions must be plainly marked as such, // and must not be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any // source distribution. //======================================================================// #ifndef DEBUG_ASSERT_HPP_INCLUDED #define DEBUG_ASSERT_HPP_INCLUDED #include #ifndef DEBUG_ASSERT_NO_STDIO # include #endif #ifndef DEBUG_ASSERT_MARK_UNREACHABLE # ifdef __GNUC__ # define DEBUG_ASSERT_MARK_UNREACHABLE __builtin_unreachable() # elif defined(_MSC_VER) # define DEBUG_ASSERT_MARK_UNREACHABLE __assume(0) # else /// Hint to the compiler that a code branch is unreachable. /// Define it yourself prior to including the header to override it. /// \notes This must be usable in an expression. # define DEBUG_ASSERT_MARK_UNREACHABLE # endif #endif #ifndef DEBUG_ASSERT_FORCE_INLINE # ifdef __GNUC__ # define DEBUG_ASSERT_FORCE_INLINE [[gnu::always_inline]] inline # elif defined(_MSC_VER) # define DEBUG_ASSERT_FORCE_INLINE __forceinline # else /// Strong hint to the compiler to inline a function. /// Define it yourself prior to including the header to override it. # define DEBUG_ASSERT_FORCE_INLINE inline # endif #endif namespace debug_assert { //=== source location ===// /// Defines a location in the source code. struct source_location { const char* file_name; ///< The file name. unsigned line_number; ///< The line number. }; /// Expands to the current [debug_assert::source_location](). #define DEBUG_ASSERT_CUR_SOURCE_LOCATION \ debug_assert::source_location \ { \ __FILE__, static_cast(__LINE__) \ } //=== level ===// /// Tag type to indicate the level of an assertion. template struct level {}; /// Helper class that sets a certain level. /// Inherit from it in your module handler. template struct set_level { static const unsigned level = Level; }; template const unsigned set_level::level; /// Helper class that controls whether the handler can throw or not. /// Inherit from it in your module handler. /// If the module does not inherit from this class, it is assumed that /// the handle does not throw. struct allow_exception { static const bool throwing_exception_is_allowed = true; }; //=== handler ===// /// Does not do anything to handle a failed assertion (except calling /// [std::abort()]()). /// Inherit from it in your module handler. struct no_handler { /// \effects Does nothing. /// \notes Can take any additional arguments. template static void handle(const source_location&, const char*, Args&&...) noexcept {} }; /// The default handler that writes a message to `stderr`. /// Inherit from it in your module handler. struct default_handler { /// \effects Prints a message to `stderr`. /// \notes It can optionally accept an additional message string. /// \notes If `DEBUG_ASSERT_NO_STDIO` is defined, it will do nothing. static void handle(const source_location& loc, const char* expression, const char* message = nullptr) noexcept { #ifndef DEBUG_ASSERT_NO_STDIO if (*expression == '\0') { if (message) ::fprintf(stderr, "[debug assert] %s:%u: Unreachable code reached - %s.\n", loc.file_name, loc.line_number, message); else ::fprintf(stderr, "[debug assert] %s:%u: Unreachable code reached.\n", loc.file_name, loc.line_number); } else if (message) ::fprintf(stderr, "[debug assert] %s:%u: Assertion '%s' failed - %s.\n", loc.file_name, loc.line_number, expression, message); else ::fprintf(stderr, "[debug assert] %s:%u: Assertion '%s' failed.\n", loc.file_name, loc.line_number, expression); #else (void)loc; (void)expression; (void)message; #endif } }; /// \exclude namespace detail { //=== boilerplate ===// // from http://en.cppreference.com/w/cpp/types/remove_reference template struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; // from http://stackoverflow.com/a/27501759 template T&& forward(typename remove_reference::type& t) { return static_cast(t); } template T&& forward(typename remove_reference::type&& t) { return static_cast(t); } template struct enable_if; template struct enable_if { using type = T; }; template struct enable_if {}; //=== helper class to check if throw is allowed ===// template struct allows_exception { static const bool value = false; }; template struct allows_exception::type> { static const bool value = Handler::throwing_exception_is_allowed; }; //=== regular void fake ===// struct regular_void { constexpr regular_void() = default; // enable conversion to anything // conversion must not actually be used template constexpr operator T&() const noexcept { // doesn't matter how to get the T return DEBUG_ASSERT_MARK_UNREACHABLE, *static_cast(nullptr); } }; //=== assert implementation ===// // function name will be shown on constexpr assertion failure template regular_void debug_assertion_failed(const source_location& loc, const char* expression, Args&&... args) { #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable : 4702) #endif return Handler::handle(loc, expression, detail::forward(args)...), ::abort(), regular_void(); #if defined(_MSC_VER) # pragma warning(pop) #endif } // use enable if instead of tag dispatching // this removes on additional function and encourage optimization template constexpr auto do_assert( const Expr& expr, const source_location& loc, const char* expression, Handler, level, Args&&... args) noexcept(!allows_exception::value || noexcept(Handler::handle(loc, expression, detail::forward(args)...))) -> typename enable_if::type { static_assert(Level > 0, "level of an assertion must not be 0"); return expr() ? regular_void() : debug_assertion_failed(loc, expression, detail::forward(args)...); } template DEBUG_ASSERT_FORCE_INLINE constexpr auto do_assert(const Expr&, const source_location&, const char*, Handler, level, Args&&...) noexcept -> typename enable_if<(Level > Handler::level), regular_void>::type { return regular_void(); } template constexpr auto do_assert( const Expr& expr, const source_location& loc, const char* expression, Handler, Args&&... args) noexcept(!allows_exception::value || noexcept(Handler::handle(loc, expression, detail::forward(args)...))) -> typename enable_if::type { return expr() ? regular_void() : debug_assertion_failed(loc, expression, detail::forward(args)...); } template DEBUG_ASSERT_FORCE_INLINE constexpr auto do_assert(const Expr&, const source_location&, const char*, Handler, Args&&...) noexcept -> typename enable_if::type { return regular_void(); } constexpr bool always_false() noexcept { return false; } } // namespace detail } // namespace debug_assert //=== assertion macros ===// #ifndef DEBUG_ASSERT_DISABLE /// The assertion macro. // /// Usage: `DEBUG_ASSERT(, , [], /// []. /// Where: /// * `` - the expression to check for, the expression `!` must be /// well-formed and contextually convertible to `bool`. /// * `` - an object of the module specific handler /// * `` (optional, defaults to `1`) - the level of the assertion, must /// be an object of type [debug_assert::level](). /// * `` (optional) - any additional arguments that are /// just forwarded to the handler function. /// /// It will only check the assertion if `` is less than or equal to /// `Handler::level`. /// A failed assertion will call: `Handler::handle(location, expression, args)`. /// `location` is the [debug_assert::source_location]() at the macro expansion, /// `expression` is the stringified expression and `args` are the /// `` as-is. /// If the handler function returns, it will call [std::abort()]. /// /// The macro will expand to a `void` expression. /// /// \notes Define `DEBUG_ASSERT_DISABLE` to completely disable this macro, it /// will expand to nothing. /// This should not be necessary, the regular version is optimized away /// completely. # define DEBUG_ASSERT(Expr, ...) \ static_cast(debug_assert::detail::do_assert([&]() noexcept { return Expr; }, \ DEBUG_ASSERT_CUR_SOURCE_LOCATION, #Expr, \ __VA_ARGS__)) /// Marks a branch as unreachable. /// /// Usage: `DEBUG_UNREACHABLE(, [], [])` /// Where: /// * `` - an object of the module specific handler /// * `` (optional, defaults to `1`) - the level of the assertion, must /// be an object of type [debug_assert::level](). /// * `` (optional) - any additional arguments that are /// just forwarded to the handler function. /// /// It will only check the assertion if `` is less than or equal to /// `Handler::level`. /// A failed assertion will call: `Handler::handle(location, "", args)`. /// and `args` are the `` as-is. /// If the handler function returns, it will call [std::abort()]. /// /// This macro is also usable in a constant expression, /// i.e. you can use it in a `constexpr` function to verify a condition like so: /// `cond(val) ? do_sth(val) : DEBUG_UNREACHABLE(…)`. /// You can't use `DEBUG_ASSERT` there. /// /// The macro will expand to an expression convertible to any type, /// although the resulting object is invalid, /// which doesn't matter, as the statement is unreachable anyway. /// /// \notes Define `DEBUG_ASSERT_DISABLE` to completely disable this macro, it /// will expand to `DEBUG_ASSERT_MARK_UNREACHABLE`. /// This should not be necessary, the regular version is optimized away /// completely. # define DEBUG_UNREACHABLE(...) \ debug_assert::detail::do_assert(debug_assert::detail::always_false, \ DEBUG_ASSERT_CUR_SOURCE_LOCATION, "", __VA_ARGS__) #else # define DEBUG_ASSERT(Expr, ...) static_cast(0) # define DEBUG_UNREACHABLE(...) \ (DEBUG_ASSERT_MARK_UNREACHABLE, debug_assert::detail::regular_void()) #endif #endif // DEBUG_ASSERT_HPP_INCLUDED