-
Notifications
You must be signed in to change notification settings - Fork 21
/
debug_assert.hpp
370 lines (335 loc) · 13.5 KB
/
debug_assert.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
//======================================================================//
// Copyright (C) 2016-2018 Jonathan Müller <[email protected]>
//
// 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 <stdlib.h>
#ifndef DEBUG_ASSERT_NO_STDIO
# include <stdio.h>
#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<unsigned>(__LINE__) \
}
//=== level ===//
/// Tag type to indicate the level of an assertion.
template <unsigned Level>
struct level
{};
/// Helper class that sets a certain level.
/// Inherit from it in your module handler.
template <unsigned Level>
struct set_level
{
static const unsigned level = Level;
};
template <unsigned Level>
const unsigned set_level<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 <typename... Args>
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 <typename T>
struct remove_reference
{
using type = T;
};
template <typename T>
struct remove_reference<T&>
{
using type = T;
};
template <typename T>
struct remove_reference<T&&>
{
using type = T;
};
// from http://stackoverflow.com/a/27501759
template <class T>
T&& forward(typename remove_reference<T>::type& t)
{
return static_cast<T&&>(t);
}
template <class T>
T&& forward(typename remove_reference<T>::type&& t)
{
return static_cast<T&&>(t);
}
template <bool Value, typename T = void>
struct enable_if;
template <typename T>
struct enable_if<true, T>
{
using type = T;
};
template <typename T>
struct enable_if<false, T>
{};
//=== helper class to check if throw is allowed ===//
template <class Handler, typename = void>
struct allows_exception
{
static const bool value = false;
};
template <class Handler>
struct allows_exception<Handler,
typename enable_if<Handler::throwing_exception_is_allowed>::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 <typename T>
constexpr operator T&() const noexcept
{
// doesn't matter how to get the T
return DEBUG_ASSERT_MARK_UNREACHABLE, *static_cast<T*>(nullptr);
}
};
//=== assert implementation ===//
// function name will be shown on constexpr assertion failure
template <class Handler, typename... Args>
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>(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 <class Expr, class Handler, unsigned Level, typename... Args>
constexpr auto do_assert(
const Expr& expr, const source_location& loc, const char* expression, Handler, level<Level>,
Args&&... args) noexcept(!allows_exception<Handler>::value
|| noexcept(Handler::handle(loc, expression,
detail::forward<Args>(args)...))) ->
typename enable_if<Level <= Handler::level, regular_void>::type
{
static_assert(Level > 0, "level of an assertion must not be 0");
return expr() ? regular_void()
: debug_assertion_failed<Handler>(loc, expression,
detail::forward<Args>(args)...);
}
template <class Expr, class Handler, unsigned Level, typename... Args>
DEBUG_ASSERT_FORCE_INLINE constexpr auto do_assert(const Expr&, const source_location&,
const char*, Handler, level<Level>,
Args&&...) noexcept ->
typename enable_if<(Level > Handler::level), regular_void>::type
{
return regular_void();
}
template <class Expr, class Handler, typename... Args>
constexpr auto do_assert(
const Expr& expr, const source_location& loc, const char* expression, Handler,
Args&&... args) noexcept(!allows_exception<Handler>::value
|| noexcept(Handler::handle(loc, expression,
detail::forward<Args>(args)...))) ->
typename enable_if<Handler::level != 0, regular_void>::type
{
return expr() ? regular_void()
: debug_assertion_failed<Handler>(loc, expression,
detail::forward<Args>(args)...);
}
template <class Expr, class Handler, typename... Args>
DEBUG_ASSERT_FORCE_INLINE constexpr auto do_assert(const Expr&, const source_location&,
const char*, Handler, Args&&...) noexcept ->
typename enable_if<Handler::level == 0, regular_void>::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(<expr>, <handler>, [<level>],
/// [<handler-specific-args>].
/// Where:
/// * `<expr>` - the expression to check for, the expression `!<expr>` must be
/// well-formed and contextually convertible to `bool`.
/// * `<handler>` - an object of the module specific handler
/// * `<level>` (optional, defaults to `1`) - the level of the assertion, must
/// be an object of type [debug_assert::level<Level>]().
/// * `<handler-specific-args>` (optional) - any additional arguments that are
/// just forwarded to the handler function.
///
/// It will only check the assertion if `<level>` 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
/// `<handler-specific-args>` 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<void>(debug_assert::detail::do_assert([&]() noexcept { return Expr; }, \
DEBUG_ASSERT_CUR_SOURCE_LOCATION, #Expr, \
__VA_ARGS__))
/// Marks a branch as unreachable.
///
/// Usage: `DEBUG_UNREACHABLE(<handler>, [<level>], [<handler-specific-args>])`
/// Where:
/// * `<handler>` - an object of the module specific handler
/// * `<level>` (optional, defaults to `1`) - the level of the assertion, must
/// be an object of type [debug_assert::level<Level>]().
/// * `<handler-specific-args>` (optional) - any additional arguments that are
/// just forwarded to the handler function.
///
/// It will only check the assertion if `<level>` is less than or equal to
/// `Handler::level`.
/// A failed assertion will call: `Handler::handle(location, "", args)`.
/// and `args` are the `<handler-specific-args>` 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<void>(0)
# define DEBUG_UNREACHABLE(...) \
(DEBUG_ASSERT_MARK_UNREACHABLE, debug_assert::detail::regular_void())
#endif
#endif // DEBUG_ASSERT_HPP_INCLUDED