KASSERT  0.0.1
Karlsruhe Assertion Library
assertion_macros.hpp
Go to the documentation of this file.
1// This file is part of KAssert.
2//
3// Copyright 2021-2022 The KAssert Authors
4
5/// @file
6/// @brief Provides macros to implement the KASSERT, THROWING_KASSERT and THROWING_KASSERT_SPECIFIED macros.
7
8#pragma once
9
10/// @cond IMPLEMENTATION
11
12// To decompose expressions, the KASSERT_KASSERT_HPP_ASSERT_IMPL() produces code such as
13//
14// Decomposer{} <= a == b [ with implicit parentheses: ((Decomposer{} <= a) == b) ]
15//
16// This triggers a warning with -Wparentheses, suggesting to set explicit parentheses, which is impossible in this
17// situation. Thus, we use compiler-specific _Pragmas to suppress these warning.
18// Note that warning suppression in GCC does not work if the KASSERT() call is passed through >= two macro calls:
19//
20// #define A(stmt) B(stmt)
21// #define B(stmt) stmt;
22// A(KASSERT(1 != 1)); -- warning suppression does not work
23//
24// This is a known limitation of the current implementation.
25#if defined(__GNUC__) && !defined(__clang__) // GCC
26 #define KASSERT_KASSERT_HPP_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")
27 #define KASSERT_KASSERT_HPP_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop")
28 #define KASSERT_KASSERT_HPP_DIAGNOSTIC_IGNORE_PARENTHESES _Pragma("GCC diagnostic ignored \"-Wparentheses\"")
29#elif defined(__clang__) // Clang
30 #define KASSERT_KASSERT_HPP_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push")
31 #define KASSERT_KASSERT_HPP_DIAGNOSTIC_POP _Pragma("clang diagnostic pop")
32 #define KASSERT_KASSERT_HPP_DIAGNOSTIC_IGNORE_PARENTHESES _Pragma("clang diagnostic ignored \"-Wparentheses\"")
33#else // Other compilers -> no supression supported
34 #define KASSERT_KASSERT_HPP_DIAGNOSTIC_PUSH
35 #define KASSERT_KASSERT_HPP_DIAGNOSTIC_POP
36 #define KASSERT_KASSERT_HPP_DIAGNOSTIC_IGNORE_PARENTHESES
37#endif
38
39// This is the actual implementation of the KASSERT() macro.
40//
41// - Note that expanding the macro into a `do { ... } while(false)` pseudo-loop is a common trick to make a macro
42// "act like a statement". Otherwise, it would have surprising effects if the macro is used inside a `if` branch
43// without braces.
44// - If the assertion level is disabled, this should not generate any code (assuming that the compiler removes the
45// dead loop).
46// - `evaluate_and_print_assertion` evaluates the assertion and prints an error message if it failed.
47// - The call to `std::abort()` is not wrapped in a function to keep the stack trace clean.
48#define KASSERT_KASSERT_HPP_KASSERT_IMPL(type, expression, message, level) \
49 do { \
50 if constexpr (kassert::internal::assertion_enabled(level)) { \
51 KASSERT_KASSERT_HPP_DIAGNOSTIC_PUSH \
52 KASSERT_KASSERT_HPP_DIAGNOSTIC_IGNORE_PARENTHESES \
53 if (!kassert::internal::evaluate_and_print_assertion( \
54 type, \
55 kassert::internal::finalize_expr(kassert::internal::Decomposer{} <= expression), \
56 KASSERT_KASSERT_HPP_SOURCE_LOCATION, \
57 #expression \
58 )) { \
59 kassert::Logger<std::ostream&>(std::cerr) << message << "\n"; \
60 std::abort(); \
61 } \
62 KASSERT_KASSERT_HPP_DIAGNOSTIC_POP \
63 } \
64 } while (false)
65
66// Expands a macro depending on its number of arguments. For instance,
67//
68// #define FOO(...) KASSERT_KASSERT_HPP_VARARG_HELPER_3(, __VA_ARGS__, IMPL3, IMPL2, IMPL1, dummy)
69//
70// expands to IMPL3 with 3 arguments, IMPL2 with 2 arguments and IMPL1 with 1 argument.
71// To do this, the macro always expands to its 5th argument. Depending on the number of parameters, __VA_ARGS__
72// pushes the right implementation to the 5th parameter.
73#define KASSERT_KASSERT_HPP_VARARG_HELPER_3(X, Y, Z, W, FUNC, ...) FUNC
74#define KASSERT_KASSERT_HPP_VARARG_HELPER_2(X, Y, Z, FUNC, ...) FUNC
75
76// KASSERT() chooses the right implementation depending on its number of arguments.
77#define KASSERT_3(expression, message, level) KASSERT_KASSERT_HPP_KASSERT_IMPL("ASSERTION", expression, message, level)
78#define KASSERT_2(expression, message) KASSERT_3(expression, message, kassert::assert::normal)
79#define KASSERT_1(expression) KASSERT_2(expression, "")
80
81// Implementation of the THROWING_KASSERT() macro.
82// In KASSERT_EXCEPTION_MODE, we throw an exception similar to the implementation of KASSERT(), although expression
83// decomposition in exceptions is currently unsupported. Otherwise, the macro delegates to KASSERT().
84#ifdef KASSERT_EXCEPTION_MODE
85 #define KASSERT_KASSERT_HPP_THROWING_KASSERT_IMPL_INTERNAL(expression, exception_type, message, ...) \
86 do { \
87 if (!(expression)) { \
88 throw exception_type(message, ##__VA_ARGS__); \
89 } \
90 } while (false)
91#else
92 #define KASSERT_KASSERT_HPP_THROWING_KASSERT_IMPL_INTERNAL(expression, exception_type, message, ...) \
93 do { \
94 if constexpr (kassert::internal::assertion_enabled(kassert::assert::kthrow)) { \
95 if (!(expression)) { \
96 kassert::Logger<std::ostream&>(std::cerr) \
97 << (exception_type(message, ##__VA_ARGS__).what()) << "\n"; \
98 std::abort(); \
99 } \
100 } \
101 } while (false)
102#endif
103
104#define KASSERT_KASSERT_HPP_THROWING_KASSERT_IMPL(expression, message) \
105 KASSERT_KASSERT_HPP_THROWING_KASSERT_IMPL_INTERNAL( \
106 expression, \
107 kassert::KassertException, \
108 kassert::internal::build_what( \
109 #expression, \
110 KASSERT_KASSERT_HPP_SOURCE_LOCATION, \
111 (kassert::internal::RrefOStringstreamLogger{std::ostringstream{}} << message).stream().str() \
112 ) \
113 )
114
115#define KASSERT_KASSERT_HPP_THROWING_KASSERT_CUSTOM_IMPL(expression, exception_type, message, ...) \
116 KASSERT_KASSERT_HPP_THROWING_KASSERT_IMPL_INTERNAL( \
117 expression, \
118 exception_type, \
119 kassert::internal::build_what( \
120 #expression, \
121 KASSERT_KASSERT_HPP_SOURCE_LOCATION, \
122 (kassert::internal::RrefOStringstreamLogger{std::ostringstream{}} << message).stream().str() \
123 ), \
124 ##__VA_ARGS__ \
125 )
126
127// THROWING_KASSERT() chooses the right implementation depending on its number of arguments.
128#define THROWING_KASSERT_2(expression, message) KASSERT_KASSERT_HPP_THROWING_KASSERT_IMPL(expression, message)
129#define THROWING_KASSERT_1(expression) THROWING_KASSERT_2(expression, "")
130
131// __PRETTY_FUNCTION__ is a compiler extension supported by GCC and clang that prints more information than __func__
132#if defined(__GNUC__) || defined(__clang__)
133 #define KASSERT_KASSERT_HPP_FUNCTION_NAME __PRETTY_FUNCTION__
134#else
135 #define KASSERT_KASSERT_HPP_FUNCTION_NAME __func__
136#endif
137
138// Represents the static location in the source code.
139#define KASSERT_KASSERT_HPP_SOURCE_LOCATION \
140 kassert::internal::SourceLocation { \
141 __FILE__, __LINE__, KASSERT_KASSERT_HPP_FUNCTION_NAME \
142 }
143
144/// @endcond