Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions include/libassert/assert-macros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,48 @@
// https://github.com/jeremy-rifkin/libassert

#include <libassert/platform.hpp>

#include <libassert/expression-decomposition.hpp>
#include <string_view>

LIBASSERT_BEGIN_NAMESPACE
namespace detail {

template <typename Concept, typename = void>
inline constexpr bool is_referencable_expression_impl = false;

template <typename Concept>
inline constexpr bool is_referencable_expression_impl<
Concept,
decltype(void(std::declval<Concept>()(detail::expression_decomposer{})))> = true;

template <typename Concept>
constexpr bool is_referencable_expression(const Concept&) {
return is_referencable_expression_impl<Concept>;
}
// A macro that checks whether an expression can be decomposed by the
// decomposer. An expression that fails is a bitfield for example.
#define LIBASSERT_IS_DECOMPOSABLE_EXPR(expr) \
libassert::detail::is_referencable_expression( \
[](auto expr_decomposer) -> decltype(std::move(expr_decomposer) \
<< expr) {})

// The decomposer is passed as a template parameter to not evaluate the other
// path and cause a hard error
#define LIBASSERT_DECOMPOSER(expr) \
[&](auto decomposer) { \
if constexpr (sizeof(decomposer) && \
LIBASSERT_IS_DECOMPOSABLE_EXPR(expr)) { \
return libassert::detail::expression_decomposer(std::move(decomposer) \
<< expr); \
} else { \
return std::move(decomposer) << (expr); \
} \
}(libassert::detail::expression_decomposer{})

} // namespace detail
LIBASSERT_END_NAMESPACE


#if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC || !LIBASSERT_NON_CONFORMANT_MSVC_PREPROCESSOR
// Macro mapping utility by William Swanson https://github.com/swansontec/map-macro/blob/master/map.h
#define LIBASSERT_EVAL0(...) __VA_ARGS__
Expand Down Expand Up @@ -205,11 +244,10 @@ LIBASSERT_END_NAMESPACE

#define LIBASSERT_INVOKE(expr, name, type, failaction, ...) \
do { \
if constexpr(false) { (void)(expr);} \
LIBASSERT_WARNING_PRAGMA_PUSH \
LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA \
auto libassert_decomposer = libassert::detail::expression_decomposer( \
libassert::detail::expression_decomposer{} << expr \
); \
auto libassert_decomposer = LIBASSERT_DECOMPOSER(expr);\
LIBASSERT_WARNING_PRAGMA_POP \
LIBASSERT_ASSERT_MAIN_BODY( \
expr, \
Expand Down Expand Up @@ -263,6 +301,7 @@ LIBASSERT_END_NAMESPACE
auto libassert_decomposer = libassert::detail::expression_decomposer( \
libassert::detail::expression_decomposer{} << expr \
); \
if constexpr(false) { (void)(expr);} \
decltype(auto) libassert_value = libassert_decomposer.get_value(); \
constexpr bool libassert_ret_lhs = libassert_decomposer.ret_lhs(); \
if constexpr(check_expression) { \
Expand Down
51 changes: 49 additions & 2 deletions include/libassert/expression-decomposition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@

LIBASSERT_BEGIN_NAMESPACE
namespace detail {
template<typename T>
inline constexpr bool is_pointer_or_member_pointer = std::is_pointer_v<strip<T>> || std::is_member_pointer_v<strip<T>>;

template<typename T,typename U>
inline constexpr bool is_NULL_comparable = (std::is_integral_v<strip<T>> && is_pointer_or_member_pointer<U>)
|| (std::is_integral_v<strip<U>> && is_pointer_or_member_pointer<T>);

// Lots of boilerplate
// std:: implementations don't allow two separate types for lhs/rhs
// Note: is this macro potentially bad when it comes to debugging(?)
Expand Down Expand Up @@ -168,6 +175,7 @@ namespace detail {
struct expression_decomposer<nothing, nothing, nothing> {
explicit constexpr expression_decomposer() = default;


template<typename O> [[nodiscard]] constexpr auto operator<<(O&& operand) && {
return expression_decomposer<O, nothing, nothing>(std::forward<O>(operand));
}
Expand All @@ -191,8 +199,6 @@ namespace detail {
LIBASSERT_GEN_OP_BOILERPLATE(ops::shl, <<) \
LIBASSERT_GEN_OP_BOILERPLATE(ops::shr, >>) \
LIBASSERT_GEN_OP_BOILERPLATE_SPACESHIP \
LIBASSERT_GEN_OP_BOILERPLATE(ops::eq, ==) \
LIBASSERT_GEN_OP_BOILERPLATE(ops::neq, !=) \
LIBASSERT_GEN_OP_BOILERPLATE(ops::gt, >) \
LIBASSERT_GEN_OP_BOILERPLATE(ops::lt, <) \
LIBASSERT_GEN_OP_BOILERPLATE(ops::gteq, >=) \
Expand Down Expand Up @@ -242,6 +248,42 @@ namespace detail {
// and rvalues as rvalues.
return std::forward<A>(a);
}
template<typename Null,std::enable_if_t<
std::is_integral_v<Null> && is_pointer_or_member_pointer<A>,int> = 0>
constexpr auto operator==(Null) &&
{
return expression_decomposer<A, std::nullptr_t, ops::eq>(std::forward<A>(a), nullptr);
}

template<typename Pointer, std::enable_if_t<
is_pointer_or_member_pointer<Pointer> && std::is_integral_v<strip<A>>, int> = 0>
constexpr auto operator==(Pointer&& pointer) &&
{
return expression_decomposer<std::nullptr_t , Pointer, ops::eq > (nullptr, std::forward<Pointer>(pointer));
}

template<typename Null, std::enable_if_t<std::is_integral_v<Null> && is_pointer_or_member_pointer<A>, int> = 0>
constexpr auto operator!=(Null) &&
{
return expression_decomposer<A, std::nullptr_t, ops::neq>(std::forward<A>(a), nullptr);
}

template<typename Pointer, std::enable_if_t<
is_pointer_or_member_pointer<Pointer> &&
std::is_integral_v<strip<A>>,int> = 0>
constexpr auto operator!=(Pointer&& pointer) &&
{
return expression_decomposer<std::nullptr_t, Pointer, ops::neq>(nullptr, std::forward<Pointer>(pointer));
}

template<typename O,std::enable_if_t<!is_NULL_comparable<A,O>,int> = 0> [[nodiscard]] constexpr auto operator==(O&& operand) && {
return expression_decomposer<A, O, ops::eq>(std::forward<A>(a), std::forward<O>(operand));
}

template<typename O, std::enable_if_t<!is_NULL_comparable<A, O>, int> = 0> [[nodiscard]] constexpr auto operator!=(O&& operand)&& {
return expression_decomposer<A, O, ops::neq>(std::forward<A>(a), std::forward<O>(operand));
}

#define LIBASSERT_GEN_OP_BOILERPLATE(functor, op) \
template<typename O> [[nodiscard]] constexpr auto operator op(O&& operand) && { \
return expression_decomposer<A, O, functor>(std::forward<A>(a), std::forward<O>(operand)); \
Expand Down Expand Up @@ -278,13 +320,18 @@ namespace detail {
// This use of std::forward may look surprising but it's correct
return std::forward<A>(a);
}

#define LIBASSERT_GEN_OP_BOILERPLATE(functor, op) \
template<typename O> [[nodiscard]] constexpr auto operator op(O&& operand) && { \
static_assert(!is_nothing<A>); \
using V = decltype(deduce_type(get_value())); /* deduce_type turns T&& into T while leaving T& as T& */ \
return expression_decomposer<V, O, functor>(get_value(), std::forward<O>(operand)); \
}
LIBASSERT_DO_GEN_OP_BOILERPLATE

LIBASSERT_GEN_OP_BOILERPLATE(ops::eq,==)
LIBASSERT_GEN_OP_BOILERPLATE(ops::neq,!=)

#undef LIBASSERT_GEN_OP_BOILERPLATE
};

Expand Down
77 changes: 76 additions & 1 deletion tests/unit/assertion_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,14 @@ std::string normalize(std::string message) {
// clang does T *
replace_all(message, "void *", "void*");
replace_all(message, "char *", "char*");
replace_all(message, "int *", "int*");

// clang does T[N], gcc does T [N]
replace_all(message, "int [5]", "int[5]");
// msvc includes the std::less
replace_all(message, ", std::less<int>", "");
replace_all(message, ", std::less<std::string>", "");

return message;
}

Expand Down Expand Up @@ -224,6 +227,75 @@ TEST(LibassertBasic, PointerDiagnostics) {
);
}


TEST(LibassertBasic, NULLMacroComparison) {
int* p{ 0 };
CHECK(
ASSERT(p != 0),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT(p != 0);
| Where:
| p => int*: nullptr
| 0 => nullptr
)XX"
);

CHECK(
ASSERT(0 != p),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT(0 != p);
| Where:
| 0 => nullptr
| p => int*: nullptr
)XX"
);

CHECK(
ASSERT(0 != p + 0),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT(0 != p + 0);
| Where:
| 0 => nullptr
| p + 0 => int*: nullptr
)XX"
);

CHECK(
ASSERT(p + 0 != 0),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT(p + 0 != 0);
| Where:
| p + 0 => int*: nullptr
| 0 => nullptr
)XX"
);
}

TEST(LibassertBasic, Bitfields) {
struct Bit {
int bit : 1;
};
Bit bit{1};
CHECK(
ASSERT(bit.bit == 1),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT(bit.bit == 1);
)XX"
);

CHECK(ASSERT(1 == bit.bit),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT(1 == bit.bit);
)XX");
}


TEST(LibassertBasic, LiteralFormatting) {
const uint16_t flags = 0b000101010;
const uint16_t mask = 0b110010101;
Expand Down Expand Up @@ -547,13 +619,15 @@ TEST(LibassertBasic, General) {
| printable{2.55} => (printable = 2.55)
)XX"
);
#if LIBASSERT_CPLUSPLUS >= 202002
CHECK(
ASSERT([] { return false; } ()),
R"XX(
|Assertion failed at <LOCATION>:
| ASSERT([] { return false; } ());
)XX"
);
#endif
}

TEST(LibassertBasic, SignedUnsignedComparisonWithoutSafeCompareMode) {
Expand Down Expand Up @@ -648,6 +722,7 @@ TEST(LibassertBasic, ExpressionDecomposition) {
| x -= x -= 1 => 0
)XX"
);

CHECK(
ASSERT(true ? false : true, "pffft"),
R"XX(
Expand Down Expand Up @@ -943,4 +1018,4 @@ TEST(LibassertBasic, DebugAssert) {
// recursion / recursion folding
// Complex type resolution
// non-conformant msvc preprocessor
// source location unit test
// source location unit test
Loading