File: Asserts.h

package info (click to toggle)
jazz2-native 3.5.0-2
  • links: PTS, VCS
  • area: contrib
  • in suites: forky, sid
  • size: 16,912 kB
  • sloc: cpp: 172,557; xml: 113; python: 36; makefile: 5; sh: 2
file content (283 lines) | stat: -rw-r--r-- 12,757 bytes parent folder | download
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
#pragma once

#include "Common.h"		// for DEATH_HELPER_EXPAND/DEATH_HELPER_PICK/DEATH_REMOVE_PARENS
#include "Base/Format.h"

#if !defined(DEATH_NO_ASSERT) && (!defined(DEATH_ASSERT) || !defined(DEATH_DEBUG_ASSERT) || !defined(DEATH_CONSTEXPR_ASSERT) || !defined(DEATH_DEBUG_CONSTEXPR_ASSERT) || !defined(DEATH_ASSERT_UNREACHABLE))
#	if defined(DEATH_STANDARD_ASSERT)
#		include <cassert>
#	elif defined(DEATH_TRACE)
#		include <cstdlib>
#	endif
#endif

// Event Tracing
#if defined(DEATH_TRACE)
#	include <cstring>	// for strlen()

// If DEATH_TRACE symbol is #defined with no value, supply internal function name
#	if DEATH_PASTE(DEATH_TRACE, 1) == 1 || DEATH_PASTE(DEATH_TRACE, 1) == 11
#		undef DEATH_TRACE
#		define DEATH_TRACE __WriteTrace
#	endif

/** @brief Trace severity level */
enum class TraceLevel {
	Unknown,		/**< Unspecified */
	Debug,			/**< Debug */
	Deferred,		/**< Deferred (usually not written immediately) */
	Info,			/**< Info */
	Warning,		/**< Warning */
	Error,			/**< Error */
	Assert,			/**< Assert */
	Fatal			/**< Fatal */
};

/**
	@brief Callback function for writing to the event log

	This function needs to be provided by the target application to enable the event tracing.
	Alternatively, @relativeref{Death,ITraceSink} interface can be used instead.
*/
void DEATH_TRACE(TraceLevel level, const char* functionName, const char* message, std::uint32_t messageLength) noexcept;

#ifndef DOXYGEN_GENERATING_OUTPUT
inline void __DEATH_TRACE(TraceLevel level, const char* functionName, const char* format)
{
	DEATH_TRACE(level, functionName, format, std::uint32_t(std::strlen(format)));
}

template<class ...Args>
inline typename std::enable_if<(sizeof...(Args) > 0), void>::type 
	__DEATH_TRACE(TraceLevel level, const char* functionName, const char* format, const Args&... args)
{
	char formattedMessage[8192];
	std::size_t length = Death::formatInto(formattedMessage, format, args...);
	DEATH_TRACE(level, functionName, formattedMessage, std::uint32_t(length));
}

#	if defined(DEATH_TARGET_GCC) || defined(DEATH_TARGET_CLANG)
#		define __DEATH_CURRENT_FUNCTION __PRETTY_FUNCTION__
#	elif defined(DEATH_TARGET_MSVC)
#		define __DEATH_CURRENT_FUNCTION __FUNCTION__ "()"
#	else
#		define __DEATH_CURRENT_FUNCTION __func__
#	endif
#endif

/** @brief Write a formatted message with @ref TraceLevel::Debug to the event log */
#	if defined(DEATH_DEBUG)
#		define LOGD(fmt, ...) __DEATH_TRACE(TraceLevel::Debug, __DEATH_CURRENT_FUNCTION, fmt, ##__VA_ARGS__)
#	else
#		define LOGD(fmt, ...) do {} while (false)
#	endif
/** @brief Write a deferred formatted message with @ref TraceLevel::Deferred to the event log */
#	define LOGB(fmt, ...) __DEATH_TRACE(TraceLevel::Deferred, __DEATH_CURRENT_FUNCTION, fmt, ##__VA_ARGS__)
/** @brief Write a formatted message with @ref TraceLevel::Info to the event log */
#	define LOGI(fmt, ...) __DEATH_TRACE(TraceLevel::Info, __DEATH_CURRENT_FUNCTION, fmt, ##__VA_ARGS__)
/** @brief Write a formatted message with @ref TraceLevel::Warning to the event log */
#	define LOGW(fmt, ...) __DEATH_TRACE(TraceLevel::Warning, __DEATH_CURRENT_FUNCTION, fmt, ##__VA_ARGS__)
/** @brief Write a formatted message with @ref TraceLevel::Error to the event log */
#	define LOGE(fmt, ...) __DEATH_TRACE(TraceLevel::Error, __DEATH_CURRENT_FUNCTION, fmt, ##__VA_ARGS__)
/** @brief Write a formatted message with @ref TraceLevel::Fatal to the event log */
#	define LOGF(fmt, ...) __DEATH_TRACE(TraceLevel::Fatal, __DEATH_CURRENT_FUNCTION, fmt, ##__VA_ARGS__)
#else
/** @brief Write a formatted message with @ref TraceLevel::Debug to the event log */
#	define LOGD(fmt, ...) do {} while (false)
/** @brief Write a deferred formatted message with @ref TraceLevel::Deferred to the event log */
#	define LOGB(fmt, ...) do {} while (false)
/** @brief Write a formatted message with @ref TraceLevel::Info to the event log */
#	define LOGI(fmt, ...) do {} while (false)
/** @brief Write a formatted message with @ref TraceLevel::Warning to the event log */
#	define LOGW(fmt, ...) do {} while (false)
/** @brief Write a formatted message with @ref TraceLevel::Error to the event log */
#	define LOGE(fmt, ...) do {} while (false)
/** @brief Write a formatted message with @ref TraceLevel::Fatal to the event log */
#	define LOGF(fmt, ...) do {} while (false)
#endif

/** @brief Break the execution if `DEATH_DEBUG` is defined */
#if !defined(DEATH_ASSERT_BREAK)
#	if !defined(DEATH_DEBUG)
#		define DEATH_ASSERT_BREAK() do {} while (false)
#	elif defined(DEATH_TARGET_WINDOWS)
#		define DEATH_ASSERT_BREAK() __debugbreak()
#	else
#		if defined(__has_builtin)
#			if __has_builtin(__builtin_debugtrap)
#				define DEATH_ASSERT_BREAK() __builtin_debugtrap()
#			elif __has_builtin(__builtin_trap)
#				define DEATH_ASSERT_BREAK() __builtin_trap()
#			endif
#		endif
#		if !defined(DEATH_ASSERT_BREAK)
#			define DEATH_ASSERT_BREAK() ::abort()
#		endif
#	endif
#endif

// Assertions
#if !defined(DEATH_NO_ASSERT) && !defined(DEATH_STANDARD_ASSERT) && defined(DEATH_TRACE) && !defined(DOXYGEN_GENERATING_OUTPUT)
#	define __DEATH_TRACE_ASSERT_(fmt, ...) __DEATH_TRACE(TraceLevel::Assert, __DEATH_CURRENT_FUNCTION, fmt, ##__VA_ARGS__)
#	define __DEATH_TRACE_ASSERT(...) DEATH_HELPER_EXPAND(__DEATH_TRACE_ASSERT_(__VA_ARGS__))
#endif

/**
	@brief Assertion macro

	Usable for sanity checks on user input, as it prints explanational message on error.

	By default, if assertion fails, @p message is printed with @ref TraceLevel::Assert
	to the event log, the function returns with @p returnValue instead and the execution
	is break (if @cpp DEATH_DEBUG @ce is defined). If @cpp DEATH_STANDARD_ASSERT @ce is
	defined, this macro expands to @cpp assert(condition) @ce, ignoring @p message. If
	@cpp DEATH_NO_ASSERT @ce is defined (or if both @cpp DEATH_TRACE @ce and
	@cpp DEATH_STANDARD_ASSERT @ce are not defined), this macro expands to
	@cpp do{}while(false) @ce. It also allows to specify only the condition, and the
	message will be generated automatically without @cpp return @ce statement.

	You can override this implementation by placing your own @cpp #define DEATH_ASSERT @ce
	before including the @ref Asserts.h header.
*/
#if !defined(DEATH_ASSERT)
#	if defined(DEATH_NO_ASSERT) || (!defined(DEATH_STANDARD_ASSERT) && !defined(DEATH_TRACE)) || defined(DOXYGEN_GENERATING_OUTPUT)
#		define DEATH_ASSERT(condition, ...) do {} while (false)
#	elif defined(DEATH_STANDARD_ASSERT)
#		define DEATH_ASSERT(condition, ...) assert(condition)
#	else
#		define __DEATH_ASSERT1(condition)								\
			do {														\
				if DEATH_UNLIKELY(!(condition)) {						\
					__DEATH_TRACE_ASSERT("Assertion ({}) failed at \"{}:{}\"", #condition, __FILE__, __LINE__);	\
					DEATH_ASSERT_BREAK();								\
				}														\
			} while(false)
#		define __DEATH_ASSERT2(condition, message)						\
			do {														\
				if DEATH_UNLIKELY(!(condition)) {						\
					__DEATH_TRACE_ASSERT(DEATH_REMOVE_PARENS(message));	\
					DEATH_ASSERT_BREAK();								\
				}														\
			} while(false)
#		define __DEATH_ASSERT3(condition, message, returnValue)	\
			do {														\
				if DEATH_UNLIKELY(!(condition)) {						\
					__DEATH_TRACE_ASSERT(DEATH_REMOVE_PARENS(message));	\
					DEATH_ASSERT_BREAK();								\
					return returnValue;									\
				}														\
			} while(false)
#		define DEATH_ASSERT(...) DEATH_HELPER_EXPAND(DEATH_HELPER_PICK(__VA_ARGS__, __DEATH_ASSERT3, __DEATH_ASSERT3, __DEATH_ASSERT3, __DEATH_ASSERT3, __DEATH_ASSERT3, __DEATH_ASSERT3, __DEATH_ASSERT2, __DEATH_ASSERT1, )(__VA_ARGS__))
#	endif
#endif

/**
	@brief Debug-only assertion macro

	Unlike @ref DEATH_ASSERT() this macro expands to @cpp do{}while(false) @ce if
	@cpp DEATH_DEBUG @ce is not defined (i.e., in release configuration). It also allows
	to specify only the condition, and the message will be generated automatically without
	@cpp return @ce statement.

	You can override this implementation by placing your own @cpp #define DEATH_DEBUG_ASSERT @ce
	before including the @ref Asserts.h header.
*/
#if !defined(DEATH_DEBUG_ASSERT)
#	if defined(DEATH_NO_ASSERT) || !defined(DEATH_DEBUG) || (!defined(DEATH_STANDARD_ASSERT) && !defined(DEATH_TRACE)) || defined(DOXYGEN_GENERATING_OUTPUT)
#		define DEATH_DEBUG_ASSERT(condition, ...) do {} while (false)
#	elif defined(DEATH_STANDARD_ASSERT)
#		define DEATH_DEBUG_ASSERT(condition, ...) assert(condition)
#	else
#		define DEATH_DEBUG_ASSERT(...) DEATH_ASSERT(__VA_ARGS__)
#	endif
#endif

/**
	@brief Constexpr assertion macro

	Unlike @ref DEATH_ASSERT() this macro can be used in C++11 @cpp constexpr @ce functions.
	In a @cpp constexpr @ce context, if assertion fails, the code fails to compile. In a
	non-@cpp constexpr @ce context, if assertion fails, @p message is printed with
	@ref TraceLevel::Assert to the event log and the execution is break. If
	@cpp DEATH_STANDARD_ASSERT @ce is defined, @p message is ignored and the standard
	@cpp assert() @ce is called if condition fails. If @cpp DEATH_NO_ASSERT @ce is defined
	(or if both @cpp DEATH_TRACE @ce and @cpp DEATH_STANDARD_ASSERT @ce are not defined),
	this macro expands to @cpp static_cast<void>(0) @ce.

	You can override this implementation by placing your own @cpp #define DEATH_CONSTEXPR_ASSERT @ce
	before including the @ref Asserts.h header.
*/
#if !defined(DEATH_CONSTEXPR_ASSERT)
#	if defined(DEATH_NO_ASSERT) || (!defined(DEATH_STANDARD_ASSERT) && !defined(DEATH_TRACE))
#		define DEATH_CONSTEXPR_ASSERT(condition, message) static_cast<void>(0)
#	elif defined(DEATH_STANDARD_ASSERT)
#		define DEATH_CONSTEXPR_ASSERT(condition, message)				\
			static_cast<void>((condition) ? 0 : ([&]() {				\
				assert(!#condition);									\
			}(), 0))
#	else
#		define DEATH_CONSTEXPR_ASSERT(condition, message)				\
			static_cast<void>((condition) ? 0 : ([&]() {				\
				__DEATH_TRACE_ASSERT(DEATH_REMOVE_PARENS(message));		\
				DEATH_ASSERT_BREAK();									\
			}(), 0))
#	endif
#endif

/**
	@brief Debug-only constexpr assertion macro

	Unlike @ref DEATH_CONSTEXPR_ASSERT() this macro expands to @cpp static_cast<void>(0) @ce
	if @cpp DEATH_DEBUG @ce is not defined (i.e., in release configuration).

	You can override this implementation by placing your own @cpp #define DEATH_DEBUG_CONSTEXPR_ASSERT @ce
	before including the @ref Asserts.h header.
*/
#if !defined(DEATH_DEBUG_CONSTEXPR_ASSERT)
#	if defined(DEATH_DEBUG)
#		define DEATH_DEBUG_CONSTEXPR_ASSERT(condition, message) DEATH_CONSTEXPR_ASSERT(condition, message)
#	else
#		define DEATH_DEBUG_CONSTEXPR_ASSERT(condition, message) static_cast<void>(0)
#	endif
#endif

/**
	@brief Assert that the code is unreachable

	By default, if code marked with this macro is reached, a hint message is printed with
	@ref TraceLevel::Assert to the event log. and the execution is break (if @cpp DEATH_DEBUG @ce
	is defined). If @cpp DEATH_STANDARD_ASSERT @ce is defined, the standard
	@cpp assert(!"Unreachable code") @ce is called. If @cpp DEATH_NO_ASSERT @ce is defined
	(or if both @cpp DEATH_TRACE @ce and @cpp DEATH_STANDARD_ASSERT @ce are not defined), this
	macro hints to the compiler that given code is not reachable, possibly helping the optimizer
	(using a compiler builtin on GCC, Clang and MSVC; calling @ref std::abort() otherwise).

	You can override this implementation by placing your own @cpp #define DEATH_ASSERT_UNREACHABLE @ce
	before including the @ref Asserts.h header.
*/
#if !defined(DEATH_ASSERT_UNREACHABLE)
#	if defined(DEATH_NO_ASSERT) || (!defined(DEATH_STANDARD_ASSERT) && !defined(DEATH_TRACE)) || defined(DOXYGEN_GENERATING_OUTPUT)
#		if defined(DEATH_TARGET_GCC)
#			define DEATH_ASSERT_UNREACHABLE(...) __builtin_unreachable()
#		elif defined(DEATH_TARGET_MSVC)
#			define DEATH_ASSERT_UNREACHABLE(...) __assume(0)
#		else
#			define DEATH_ASSERT_UNREACHABLE(...) std::abort()
#		endif
#	elif defined(DEATH_STANDARD_ASSERT)
#		define DEATH_ASSERT_UNREACHABLE(...) assert(!"Unreachable code")
#	else
#		define __DEATH_ASSERT_UNREACHABLE0()							\
			do {														\
				__DEATH_TRACE_ASSERT("Reached unreachable code at \"{}:{}\"", __FILE__, __LINE__);	\
				DEATH_ASSERT_BREAK();									\
			} while(false)
#		define __DEATH_ASSERT_UNREACHABLE2(message, returnValue)		\
			do {														\
				__DEATH_TRACE_ASSERT(DEATH_REMOVE_PARENS(message));		\
				DEATH_ASSERT_BREAK();									\
				return returnValue;										\
			} while(false)
#		define DEATH_ASSERT_UNREACHABLE(...) DEATH_HELPER_EXPAND(DEATH_HELPER_PICK(__VA_ARGS__, __DEATH_ASSERT_UNREACHABLE2, __DEATH_ASSERT_UNREACHABLE2, __DEATH_ASSERT_UNREACHABLE2, __DEATH_ASSERT_UNREACHABLE2, __DEATH_ASSERT_UNREACHABLE2, __DEATH_ASSERT_UNREACHABLE2, __DEATH_ASSERT_UNREACHABLE2, __DEATH_ASSERT_UNREACHABLE0, __DEATH_ASSERT_UNREACHABLE0)(__VA_ARGS__))
#	endif
#endif