mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
Add SRB2_ASSERT, srb2::NotNull<T>
Add SRB2_ASSERT, superceding I_Assert This assertion macro always expands to a call of srb2::do_assert, which is overloaded with two templates: one which applies if the provided Level is less than or equal to the SRB2_ASSERTION_LEVEL, and one which is a no-op. When optimizations are enabled, this will verifiably remove the evaluation of the expression in all cases, instead of evaluating the expression and doing nothing with it. Add srb2::NotNull wrapper utility This is meant to be used in places where pointers are used as parameters. It can be used with any pointer-like type, not just raw pointers. During construction of NotNull, the pointer will be asserted not-null in debug and paranoia builds, and in release optimizations with no assertions, the code decays gracefully to standard pointer-passing.
This commit is contained in:
parent
fe32cba866
commit
6af003771d
6 changed files with 157 additions and 1 deletions
115
src/cxxutil.hpp
115
src/cxxutil.hpp
|
|
@ -1,9 +1,12 @@
|
|||
#ifndef __SRB2_CXXUTIL_HPP__
|
||||
#define __SRB2_CXXUTIL_HPP__
|
||||
|
||||
#include <cstdlib>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "doomdef.h"
|
||||
|
||||
namespace srb2 {
|
||||
|
||||
template <class F>
|
||||
|
|
@ -19,6 +22,7 @@ public:
|
|||
void operator=(Finally&& from) = delete;
|
||||
|
||||
~Finally() noexcept {
|
||||
if (call_)
|
||||
f_();
|
||||
}
|
||||
|
||||
|
|
@ -32,6 +36,117 @@ Finally<std::decay_t<F>> finally(F&& f) noexcept {
|
|||
return Finally {std::forward<F>(f)};
|
||||
}
|
||||
|
||||
struct SourceLocation {
|
||||
const char* file_name;
|
||||
unsigned int line_number;
|
||||
};
|
||||
|
||||
#define SRB2_SOURCE_LOCATION \
|
||||
srb2::SourceLocation { __FILE__, __LINE__ }
|
||||
|
||||
#ifndef SRB2_ASSERT_HANDLER
|
||||
#define SRB2_ASSERT_HANDLER srb2::IErrorAssertHandler
|
||||
#endif
|
||||
|
||||
#if !defined(NDEBUG) || defined(PARANOIA)
|
||||
// An assertion level of 2 will activate all invocations of the SRB2_ASSERT macro
|
||||
#define SRB2_ASSERTION_LEVEL 2
|
||||
#else
|
||||
// The minimum assertion level is 1
|
||||
#define SRB2_ASSERTION_LEVEL 1
|
||||
#endif
|
||||
|
||||
/// Assert a precondition expression in debug builds.
|
||||
#define SRB2_ASSERT(expr) srb2::do_assert<2, SRB2_ASSERT_HANDLER>([&] { return (expr); }, SRB2_SOURCE_LOCATION, #expr)
|
||||
|
||||
class IErrorAssertHandler {
|
||||
public:
|
||||
static void handle(const SourceLocation& source_location, const char* expression) {
|
||||
I_Error("Assertion failed at %s:%u: %s != true",
|
||||
source_location.file_name,
|
||||
source_location.line_number,
|
||||
expression);
|
||||
}
|
||||
};
|
||||
|
||||
class NoOpAssertHandler {
|
||||
public:
|
||||
static void handle(const SourceLocation& source_location, const char* expression) {}
|
||||
};
|
||||
|
||||
/// @brief Assert a precondition expression, aborting the application if it fails.
|
||||
/// @tparam Expr
|
||||
/// @tparam Level the level of this assertion; if it is less than or equal to SRB2_ASSERTION_LEVEL, this overload will
|
||||
/// activate.
|
||||
/// @param expr a callable which returns a bool
|
||||
/// @param source_location a struct containing the source location of the assertion, e.g. SRB2_SOURCE_LOCATION
|
||||
/// @param expression the expression evaluated in the expression callable
|
||||
/// @param message an optional message to display for the assertion
|
||||
template <unsigned int Level, class Handler, class Expr>
|
||||
std::enable_if_t<(Level <= SRB2_ASSERTION_LEVEL), void>
|
||||
do_assert(const Expr& expr, const SourceLocation& source_location, const char* expression = "") noexcept {
|
||||
static_assert(Level > 0, "level of an assertion must not be 0");
|
||||
if (!expr()) {
|
||||
Handler::handle(source_location, expression);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
template <unsigned int Level, class, class Expr>
|
||||
std::enable_if_t<(Level > SRB2_ASSERTION_LEVEL), void>
|
||||
do_assert(const Expr&, const SourceLocation&, const char* = "") noexcept {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class NotNull final {
|
||||
T ptr_;
|
||||
|
||||
public:
|
||||
static_assert(std::is_convertible_v<decltype(std::declval<T>() != nullptr), bool>,
|
||||
"T is not comparable with nullptr_t");
|
||||
|
||||
/// @brief Move-construct from the pointer value U, asserting that it is not null. Allows construction of a
|
||||
/// NotNull<T> from any compatible pointer U, for example with polymorphic classes.
|
||||
template <typename U, typename = std::enable_if_t<std::is_convertible_v<U, T>>>
|
||||
constexpr NotNull(U&& rhs) : ptr_(std::forward<U>(rhs)) {
|
||||
SRB2_ASSERT(ptr_ != nullptr);
|
||||
}
|
||||
|
||||
/// @brief Wrap the pointer type T, asserting that the pointer is not null.
|
||||
template <typename = std::enable_if_t<!std::is_same_v<std::nullptr_t, T>>>
|
||||
constexpr NotNull(T rhs) : ptr_(std::move(rhs)) {
|
||||
SRB2_ASSERT(ptr_ != nullptr);
|
||||
}
|
||||
|
||||
/// @brief Copy construction from NotNull of convertible type U. Only if the incoming pointer is NotNull already.
|
||||
template <typename U, typename = std::enable_if_t<std::is_convertible_v<U, T>>>
|
||||
constexpr NotNull(const NotNull<U>& rhs) : NotNull(rhs.get()) {
|
||||
// Value is guaranteed to be not null by construction; no assertion necessary
|
||||
}
|
||||
|
||||
NotNull(const NotNull& rhs) = default;
|
||||
NotNull& operator=(const NotNull& rhs) = default;
|
||||
|
||||
/// @brief Get the stored pointer.
|
||||
constexpr T get() const { return ptr_; }
|
||||
|
||||
/// @brief Convert to T (the pointer type).
|
||||
constexpr operator T() const { return get(); }
|
||||
|
||||
/// @brief Arrow-dereference to *T (the actual value pointed to).
|
||||
constexpr decltype(auto) operator->() const { return get(); }
|
||||
|
||||
/// @brief Dereference to *T (the actual value pointed to).
|
||||
constexpr decltype(auto) operator*() const { return *get(); }
|
||||
|
||||
// It is not allowed to construct NotNull<T> with nullptr regardless of T.
|
||||
NotNull(std::nullptr_t) = delete;
|
||||
NotNull& operator=(std::nullptr_t) = delete;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
NotNull(T) -> NotNull<T>;
|
||||
|
||||
} // namespace srb2
|
||||
|
||||
#endif // __SRB2_CXXUTIL_HPP__
|
||||
|
|
|
|||
|
|
@ -645,6 +645,7 @@ UINT32 quickncasehash (const char *p, size_t n)
|
|||
#endif
|
||||
|
||||
// An assert-type mechanism.
|
||||
// NOTE: USE SRB2_ASSERT FOR C++ CODE INSTEAD
|
||||
#ifdef PARANOIA
|
||||
#define I_Assert(e) ((e) ? (void)0 : I_Error("assert failed: %s, file %s, line %d", #e, __FILE__, __LINE__))
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
target_sources(srb2tests PRIVATE
|
||||
boolcompat.cpp
|
||||
notnull.cpp
|
||||
testbase.hpp
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include "testbase.hpp"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "../doomtype.h"
|
||||
|
|
|
|||
31
src/tests/notnull.cpp
Normal file
31
src/tests/notnull.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#include "testbase.hpp"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
|
||||
namespace {
|
||||
class A {
|
||||
public:
|
||||
virtual bool foo() = 0;
|
||||
};
|
||||
class B : public A {
|
||||
public:
|
||||
virtual bool foo() override final { return true; };
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("NotNull<int*> is constructible from int*") {
|
||||
int a = 0;
|
||||
REQUIRE(srb2::NotNull(static_cast<int*>(&a)));
|
||||
}
|
||||
|
||||
TEST_CASE("NotNull<A*> is constructible from B* where B inherits from A") {
|
||||
B b;
|
||||
REQUIRE(srb2::NotNull(static_cast<B*>(&b)));
|
||||
}
|
||||
|
||||
TEST_CASE("NotNull<A*> dereferences to B& to call foo") {
|
||||
B b;
|
||||
srb2::NotNull<A*> a {static_cast<B*>(&b)};
|
||||
REQUIRE(a->foo());
|
||||
}
|
||||
6
src/tests/testbase.hpp
Normal file
6
src/tests/testbase.hpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef __SRB2_TESTS_TESTBASE_HPP__
|
||||
#define __SRB2_TESTS_TESTBASE_HPP__
|
||||
|
||||
#define SRB2_ASSERT_HANDLER srb2::NoOpAssertHandler
|
||||
|
||||
#endif // __SRB2_TESTS_TESTBASE_HPP__
|
||||
Loading…
Add table
Reference in a new issue