libcruft-util/debug/assert.hpp
Danny Robson fdaa5e1392 assert: split CHECK_LIMIT into INCLUSIVE and INDEX
LIMIT hid an off-by-one bug when tests used end iterators. We rename the
assertion to uncover all uses of the flawed implementation, and split it
into an identical assertion, and one intended to protect against
iterator ends.
2020-09-24 08:03:41 +10:00

432 lines
21 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 "../platform.hpp"
//#include "maths.hpp" // XXX: See notes at the end of file for maths.hpp inclusion
#include "debugger.hpp"
#include <utility>
///////////////////////////////////////////////////////////////////////////////
// it is fractionally easier to define a constexpr variable which can be used
// in constexpr-if to enable/disable some codepaths rather than deal with
// macros in some scenarios. eg, templates are complicated enough without
// (more) macros.
#if !defined(NDEBUG)
constexpr bool debug_enabled = true;
constexpr bool assertions_enabled = true;
#else
constexpr bool debug_enabled = false;
constexpr bool assertions_enabled = false;
#endif
///----------------------------------------------------------------------------
/// enable some code only if assertions are enabled
///
/// explicitly does not use constexpr if to remove the code as some paths may
/// refer to variables which do not always exist, and current compiler
/// implementations are a little picky here.
#ifndef NDEBUG
#include <iostream>
#define DEBUG_ONLY(X) do { X; } while (0)
#else
#define DEBUG_ONLY(X) do { ; } while (0)
#endif
//#define DEBUG_ONLY(X) do { if constexpr (debug_enabled) { X } } while (0)
///////////////////////////////////////////////////////////////////////////////
#define EXIT_XSUCCESS 0
#define EXIT_XSKIP 77
#define EXIT_XHARD_ERROR 99
///////////////////////////////////////////////////////////////////////////////
#define TRACE { \
DEBUG_ONLY ( \
std::cerr << "tid: " << std::this_thread::get_id () \
<< "; " << __FILE__ \
<< ":" << __func__ \
<< ":" << __LINE__ \
<< '\n'; \
); \
}
#define WARN(C) do { \
DEBUG_ONLY ( \
if (C) { \
std::cerr << __FILE__ \
<< ":" << __func__ \
<< ":" << __LINE__ \
<< ", " << #C \
<< '\n'; \
} \
); \
} while (0)
#define RETURN_FALSE_UNLESS(CONDITION) { \
if (const auto &__return_false_unless = (CONDITION); !__return_false_unless) { \
if constexpr (debug_enabled) { \
std::cerr << __FILE__ << ':' \
<< __LINE__ << ':' \
<< __PRETTY_FUNCTION__ << "; " \
<< #CONDITION << '\n'; \
breakpoint (); \
} \
\
return false; \
} \
} while (0)
#define WARN_RETURN(CONDITION,VALUE) do { \
if (const auto& __warn_return = (CONDITION); !!__warn_return) { \
if constexpr (debug_enabled) { \
std::cerr << __FILE__ << ':' \
<< __LINE__ << ':' \
<< __PRETTY_FUNCTION__ << "; " \
<< #CONDITION << '\n'; \
breakpoint (); \
} \
\
return (VALUE); \
} \
} while (0)
#define RETURN_UNLESS(VALUE,CONDITION) do { \
if (const auto &__return_unless = (CONDITION); !__return_unless) { \
if constexpr (debug_enabled) { \
std::cerr << __FILE__ << ':' \
<< __LINE__ << ':' \
<< __PRETTY_FUNCTION__ << "; " \
<< #CONDITION << '\n'; \
breakpoint (); \
} \
\
return (VALUE); \
} \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#ifdef COMPILER_GCC
#define CHECK(C) do { \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wnonnull-compare\"") \
DEBUG_ONLY ( \
if (!(C)) \
panic (#C); \
); \
_Pragma("GCC diagnostic pop") \
} while (0)
#elif defined(COMPILER_CLANG)
#define CHECK(C) do { \
DEBUG_ONLY ( \
if (!(C)) \
panic (#C); \
); \
} while (0)
#else
#error Unhandled compiler
#endif
///////////////////////////////////////////////////////////////////////////////
#define SCOPED_SANITY(A) \
::cruft::debug::scoped_sanity PASTE(__scoped_sanity_checker,__LINE__) ((A)); \
(void)PASTE(__scoped_sanity_checker,__LINE__);
///////////////////////////////////////////////////////////////////////////////
#define CHECK_SANITY(A,...) CHECK(::cruft::debug::is_valid ((A) __VA_OPT__(,) __VA_ARGS__))
///////////////////////////////////////////////////////////////////////////////
#define CHECK_EQ(A,B) do { \
DEBUG_ONLY ( \
const auto &__a = (A); \
const auto &__b = (B); \
\
if (!::cruft::almost_equal (__a, __b)) { \
std::cerr << "expected equality\n" \
"__a: " #A " is " << __a << "\n" \
"__b: " #B " is " << __b << "\n"; \
breakpoint (); \
} \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_LT(A,B) do { \
DEBUG_ONLY ( \
const auto &__a = (A); \
const auto &__b = (B); \
\
if (__a >= __b) { \
std::cerr << "expected less than\n" \
"__a: " << #A << " is " << __a << "\n" \
"__b: " << #B << " is " << __b << "\n"; \
breakpoint (); \
}; \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_LE(A,B) do { \
DEBUG_ONLY ( \
const auto &__a = (A); \
const auto &__b = (B); \
\
if (!(__a <= __b)) { \
std::cerr << "expected less than or equal\n" \
"__a: " << #A << " is " << __a << "\n" \
"__b: " << #B << " is " << __b << "\n"; \
breakpoint (); \
} \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_GT(A,B) do { \
DEBUG_ONLY ( \
const auto &__a = (A); \
const auto &__b = (B); \
\
if (__a <= __b) { \
std::cerr << "expected greater than\n" \
"__a: " << #A << " is " << __a << "\n" \
"__b: " << #B << " is " << __b << "\n"; \
breakpoint (); \
} \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_GE(A,B) do { \
DEBUG_ONLY ( \
const auto &__a = (A); \
const auto &__b = (B); \
\
if (__a < __b) { \
std::cerr << "expected greater or equal\n" \
"__a: " << #A << " is " << __a << "\n" \
"__b: " << #B << " is " << __b << "\n"; \
breakpoint (); \
}; \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_INCLUSIVE(V,L,H) do { \
DEBUG_ONLY ( \
const auto &__v = (V); \
const auto &__l = (L); \
const auto &__h = (H); \
\
if (__v < __l || __v > __h) { \
std::cerr << "expected inclusive\n" \
"__l: " << #L << " is " << +__l << "\n" \
"__h: " << #H << " is " << +__h << "\n" \
"__v: " << #V << " is " << +__v << "\n"; \
breakpoint (); \
}; \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_INDEX(V,H) do { \
DEBUG_ONLY ( \
const auto &__v = (V); \
const auto &__h = (H); \
\
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wtype-limits\"") \
\
if (intmax_t (__v) < 0 || __v >= __h) { \
std::cerr << "expected index\n" \
"__h: " << #H << " is " << +__h << "\n" \
"__v: " << #V << " is " << +__v << "\n"; \
breakpoint (); \
}; \
\
_Pragma("GCC diagnostic pop") \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_NEQ(A,B) do { \
DEBUG_ONLY( \
const auto &__a = (A); \
const auto &__b = (B); \
\
if (::cruft::almost_equal (__a, __b)) { \
std::cerr << "expected inequality\n" \
"__a: " << #A << " is " << __a << "\n" \
"__b: " << #B << " is " << __b << "\n"; \
breakpoint (); \
}; \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_ZERO(A) do { \
DEBUG_ONLY ( \
const auto &__a = (A); \
\
if (!::cruft::almost_zero (__a)) { \
std::cerr << "expected zero\n" \
"__a: " << #A << " is " << __a << "\n"; \
breakpoint (); \
}; \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_NEZ(A) do { \
DEBUG_ONLY ( \
const auto &__a = (A); \
\
if (::cruft::exactly_zero (__a)) { \
std::cerr << "expected non-zero\n" \
"__a: " << #A << " is " << __a << '\n'; \
breakpoint (); \
} \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_MOD(V,M) do { \
DEBUG_ONLY ( \
const auto &__check_mod_v = (V); \
const auto &__check_mod_m = (M); \
\
if (!::cruft::exactly_zero (__check_mod_v % __check_mod_m)) { \
std::cerr << "expected zero modulus\n" \
"__v: " << #V << " is " << __check_mod_v << "\n" \
"__m: " << #M << " is " << __check_mod_m << "\n"; \
breakpoint (); \
} \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#if defined(ENABLE_DEBUGGING)
#define CHECK_ENUM(C, ...) do { \
const auto &__c = (C); \
const auto &__e = { __VA_ARGS__ }; \
\
if (std::find (std::cbegin (__e), \
std::cend (__e), \
__c) == std::end (__e)) { \
std::cerr << "expect enum\n" \
"__c: " << #C << " is " << __c << '\n'; \
breakpoint (); \
} \
} while (0)
#else
#define CHECK_ENUM(C,...)
#endif
#if !defined(NDEBUG)
#define CHECK_FINITE(V) do { \
const auto &__v = (V); \
if (!std::isfinite (__v)) { \
std::cerr << "expected finite value\n" \
"__v: " << #V << " is " << __v << '\n'; \
breakpoint (); \
} \
} while (0)
#else
#define CHECK_FINITE(V,...)
#endif
///////////////////////////////////////////////////////////////////////////////
#define CHECK_THROWS(E,C) do { \
DEBUG_ONLY ( \
bool caught = false; \
\
try \
{ C; } \
catch (E const&) \
{ caught = true; } \
\
if (!caught) { \
std::cerr << "expected exception: " << #E << '\n' \
breakpoint (); \
} \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#define CHECK_NOTHROW(C) do { \
DEBUG_ONLY ( \
try { \
C; \
} catch (const std::exception &e) { \
std::cerr << "unexpected exception: " << e.what () << '\n'; \
breakpoint (); \
} catch (...) { \
std::cerr << "unexpected exception: unknown\n"; \
breakpoint (); \
} \
); \
} while (0)
///////////////////////////////////////////////////////////////////////////////
#include <string>
#include <string_view>
void warn (void);
void warn (std::string const&);
void warn (std::string_view);
void warn (const char *);
template <typename ValueT>
decltype(auto)
warn_return (
char const *message,
ValueT &&value
) {
warn (message);
return std::forward<ValueT> (value);
}
///////////////////////////////////////////////////////////////////////////////
// XXX: maths needs to be included so that CHECK_EQ/NEQ can call almost_equal,
// but maths.hpp might be using CHECK_ macros so we must include maths.hpp
// after we define the CHECK_ macros so the preprocessor can resolve them.
#include "../maths.hpp"