header_utils
Loading...
Searching...
No Matches
assuming.h
1
4
5#pragma once
6
7#include <version>
8#include <cassert>
9
10#if !defined(__cpp_lib_format)
11#error "This library requires std::format"
12#endif
13
14#include "source_location.h"
15
33
36
38#ifndef ASSUMING_DEBUG
39#ifdef NDEBUG
40#define ASSUMING_DEBUG 0
41#else
42#define ASSUMING_DEBUG 1
43#endif
44#endif
45
47#ifndef ASSUMING_INCLUDE_MAGIC_ENUM
48#if __has_include(<magic_enum_format.hpp>)
49#define ASSUMING_INCLUDE_MAGIC_ENUM 1
50#else
51#define ASSUMING_INCLUDE_MAGIC_ENUM 0
52#endif
53#endif
54
56#ifndef ASSUMING_BREAKPOINT
57#if __has_include(<debugging>)
58#include <debugging>
59#define ASSUMING_BREAKPOINT std::breakpoint()
60#else
61#ifndef __has_builtin // Optional of course.
62#define __has_builtin(x) 0 // Compatibility with non-clang compilers.
63#endif
64#if __has_builtin(__builtin_debugtrap)
65#define ASSUMING_BREAKPOINT __builtin_debugtrap()
66#elif (defined(__clang__) || defined(__GNUC__)) && !defined(_MSC_VER)
67#define ASSUMING_BREAKPOINT __builtin_trap()
68#elif defined(_MSC_VER)
69#define ASSUMING_BREAKPOINT __debugbreak()
70#else
71#define ASSUMING_BREAKPOINT do { try { throw 0; } catch (...) {} } while (false)
72#endif
73#endif
74#endif
75
77#ifndef ASSUMING_USE_STACKTRACE
78#if defined(__cpp_lib_stacktrace)
79#define ASSUMING_USE_STACKTRACE 1
80#else
81#define ASSUMING_USE_STACKTRACE 0
82#endif
83#endif
84
86
87#if ASSUMING_DEBUG
88#include <sstream>
89#include <format>
90#endif
91
92#if ASSUMING_USE_STACKTRACE
93#if defined(__cpp_lib_stacktrace)
94#include <stacktrace>
95#else
96#undef ASSUMING_USE_STACKTRACE
97#define ASSUMING_USE_STACKTRACE 0
98#endif
99#endif
100
101#if ASSUMING_INCLUDE_MAGIC_ENUM
102#include <magic_enum/magic_enum_format.hpp>
103#endif
104
105#if defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L
107#define GHPL_UNREACHABLE() std::unreachable()
108#else
109#if (defined(__clang__) || defined(__GNUC__)) && !defined(_MSC_VER)
110#define GHPL_UNREACHABLE() (__builtin_unreachable())
111#else
112#define GHPL_UNREACHABLE() GHPL_ASSUME(false)
113#endif
114#endif
115
116#ifdef __has_cpp_attribute
117#if __has_cpp_attribute(assume)
119#define GHPL_ASSUME(...) [[assume(__VA_ARGS__)]]; (::std::ignore = (__VA_ARGS__))
120#endif
121#endif
122#ifndef GHPL_ASSUME
123#if (defined(__clang__) || defined(__GNUC__)) && !defined(_MSC_VER)
124#define GHPL_ASSUME(...) ((__VA_ARGS__) ? static_cast<void>(0) : GHPL_UNREACHABLE())
125#else
126#define GHPL_ASSUME(...) (__assume(__VA_ARGS__), (::std::ignore = (__VA_ARGS__)))
127#endif
128#endif
129
130#if ASSUMING_DEBUG || defined(DOXYGEN)
131
132#define ASSUMING_HANDLE_HANDLER_RESULT(result) do { \
133 switch (result) \
134 { \
135 case ::ghassanpl::AssumptionHandlerResult::Continue: break; \
136 case ::ghassanpl::AssumptionHandlerResult::Break: ASSUMING_BREAKPOINT; break; \
137 case ::ghassanpl::AssumptionHandlerResult::Terminate: std::abort(); break; \
138 } \
139 } while (false)
140
141#define ASSUMING_REPORT(...) ASSUMING_HANDLE_HANDLER_RESULT(::ghassanpl::ReportAssumptionFailure(__VA_ARGS__))
142
145
147#define Assuming(exp, ...) do { if (auto&& _assuming_exp_v = (exp); !_assuming_exp_v) [[unlikely]] \
148 ASSUMING_REPORT(#exp " will evalute to true", { { #exp, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_exp_v)) } }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
149
151#define AssumingNotReachable(...) do { ASSUMING_REPORT("execution will never reach this point", {}, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); GHPL_UNREACHABLE(); } while (false)
152
154#define AssumingNotRecursive(...) \
155 static int _assuming_recursion_counter##__LINE__ = 0; \
156 if (_assuming_recursion_counter##__LINE__ != 0) \
157 ASSUMING_REPORT("enclosing block will not be entered recursively", {}, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); \
158 const ::ghassanpl::detail::RecursionScopeMarker _assuming_scope_marker##__LINE__( _assuming_recursion_counter##__LINE__ )
159
161#define AssumingSingleThread(...) do { \
162 static std::thread::id _assuming_thread_id = std::this_thread::get_id(); \
163 auto _assuming_current_thread_id = std::this_thread::get_id(); \
164 if (_assuming_thread_id != _assuming_current_thread_id)\
165 ASSUMING_REPORT("this code will be executed in one thread only", { {"required_thread_id", std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_thread_id))}, {"thread_id", std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_current_thread_id))} }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); \
166 } while (false)
167
169#define AssumingOnThread(thread_to_check, ...) do { \
170 auto _assuming_thread_id = (thread_to_check); \
171 auto _assuming_current_thread_id = std::this_thread::get_id(); \
172 if (_assuming_thread_id != _assuming_current_thread_id)\
173 ASSUMING_REPORT("this code will be executed in one thread only", { {"required_thread_id", std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_thread_id))}, { "thread_id", std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_current_thread_id))} }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); \
174 } while (false)
175
177#define AssumingNotOnThread(thread_to_check, ...) do { \
178 auto _assuming_thread_id = (thread_to_check); \
179 auto _assuming_current_thread_id = std::this_thread::get_id(); \
180 if (_assuming_thread_id == _assuming_current_thread_id)\
181 ASSUMING_REPORT("this code will not be executed in specific thread", { {"forbidden_thread_id", std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_thread_id))}, { "thread_id", std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_current_thread_id))} }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); \
182 } while (false)
183
185#define AssumingNull(exp, ...) do { if (auto _assuming_exp_v = (const void*)std::to_address(exp); _assuming_exp_v != nullptr) [[unlikely]] \
186 ASSUMING_REPORT(#exp " will be null", { { #exp, std::format("{}", _assuming_exp_v) } }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
187
189#define AssumingNotNull(exp, ...) do { if (auto _assuming_exp_v = (const void*)std::to_address(exp); _assuming_exp_v == nullptr) [[unlikely]] \
190 ASSUMING_REPORT(#exp " will not be null", { { #exp, std::format("{}", _assuming_exp_v) } }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
191
193#define AssumingBinOp(a, b, op, text, ...) do { auto&& _assuming_a_v = (a); auto&& _assuming_b_v = (b); if (!(_assuming_a_v op _assuming_b_v)) [[unlikely]] \
194 ASSUMING_REPORT(#a " will " text " " #b, { \
195 { #a, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_a_v)) }, \
196 { #b, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_b_v)) } \
197 }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
198
200#define AssumingEqual(a, b, ...) AssumingBinOp(a, b, ==, "be equal to", __VA_ARGS__)
202#define AssumingNotEqual(a, b, ...) AssumingBinOp(a, b, !=, "not be equal to", __VA_ARGS__)
204#define AssumingGreater(a, b, ...) AssumingBinOp(a, b, >, "be greater than", __VA_ARGS__)
206#define AssumingLess(a, b, ...) AssumingBinOp(a, b, <, "be less than", __VA_ARGS__)
208#define AssumingGreaterEqual(a, b, ...) AssumingBinOp(a, b, >=, "be greater or equal to", __VA_ARGS__)
210#define AssumingLessEqual(a, b, ...) AssumingBinOp(a, b, <=, "be less or equal to", __VA_ARGS__)
211
213#define AssumingContainsBits(a, b, ...) do { auto&& _assuming_a_v = (a); auto&& _assuming_b_v = (b); if (!((_assuming_a_v & _assuming_b_v) == _assuming_b_v)) [[unlikely]] \
214 ASSUMING_REPORT(#a " will contain flags " #b, { \
215 { #a, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_a_v)) }, \
216 { #b, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_b_v)) } \
217 }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
218
220#define AssumingZero(a, ...) AssumingBinOp(a, 0, ==, "be equal to", __VA_ARGS__)
221
223#define AssumingEmpty(exp, ...) do { using std::empty; using std::size; if (auto&& _assuming_exp_v = (exp); !empty(_assuming_exp_v)) [[unlikely]] \
224 ASSUMING_REPORT(#exp " will be empty", { { "size of " #exp, std::format("{}", size(_assuming_exp_v)) } }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
225
227#define AssumingNotEmpty(exp, ...) do { using std::empty; using std::size; if (auto&& _assuming_exp_v = (exp); empty(_assuming_exp_v)) [[unlikely]] \
228 ASSUMING_REPORT(#exp " will not be empty", { { "size of " #exp, std::format("{}", size(_assuming_exp_v)) } }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
229
231#define AssumingNullOrEmpty(exp, ...) do { using std::empty; using std::size; if (auto&& _assuming_exp_v = (exp); !::ghassanpl::detail::IsNullOrEmpty(_assuming_exp_v)) [[unlikely]] \
232 ASSUMING_REPORT(#exp " will be null or empty", { { #exp, _assuming_exp_v ? std::format("'{}'", _assuming_exp_v) : "(null)" } }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
233
235#define AssumingNotNullOrEmpty(exp, ...) do { using std::empty; using std::size; if (auto&& _assuming_exp_v = (exp); ::ghassanpl::detail::IsNullOrEmpty(_assuming_exp_v)) [[unlikely]] \
236 ASSUMING_REPORT(#exp " will not be null or empty", { { #exp, _assuming_exp_v ? std::format("'{}'", _assuming_exp_v) : "(null)" } }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
237
239#define AssumingValidIndex(_index, _container, ...) do { using std::size; auto&& _assuming_index = (_index); auto&& _assuming_container = (_container); const auto _assuming_container_size = size(_assuming_container); \
240 if (!(_assuming_index >= 0 && size_t(_assuming_index) < _assuming_container_size)) [[unlikely]] { \
241 ASSUMING_REPORT(#_index " will be a valid index to " #_container, { \
242 { #_index, std::format("{}", _assuming_index) }, \
243 { "size of " #_container, std::format("{}", _assuming_container_size) }, \
244 }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } } while (false)
245
247#define AssumingContains(_key, _container, ...) do { auto&& _assuming_key = (_key); auto&& _assuming_container = (_container); \
248 if (!(_assuming_container.contains(_assuming_key))) [[unlikely]] { \
249 ASSUMING_REPORT(#_key " will be a valid key to " #_container, { \
250 { #_key, std::format("{}", _assuming_key) }, \
251 }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } } while (false)
252
254#define AssumingValidIterator(_iterator, _container, ...) do { using std::end; auto&& _assuming_iterator = (_iterator); auto&& _assuming_container = (_container); const auto _assuming_end = end(_assuming_container); \
255 if (_assuming_iterator == _assuming_end) [[unlikely]] { \
256 ASSUMING_REPORT(#_iterator " will be a valid iterator to " #_container, {}, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } } while (false)
257
259#define AssumingBetween(v, a, b, ...) do { auto&& _assuming_v_v = (v); auto&& _assuming_a_v = (a); auto&& _assuming_b_v = (b); if (!(_assuming_v_v >= _assuming_a_v && _assuming_v_v < _assuming_b_v)) [[unlikely]] \
260 ASSUMING_REPORT(#v " will be between " #a " and " #b, { \
261 { #v, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_v_v)) }, \
262 { #a, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_a_v)) }, \
263 { #b, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_b_v)) } \
264 }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
265
267#define AssumingBetweenInclusive(v, a, b, ...) do { auto&& _assuming_v_v = (v); auto&& _assuming_a_v = (a); auto&& _assuming_b_v = (b); if (!(_assuming_v_v >= _assuming_a_v && _assuming_v_v <= _assuming_b_v)) [[unlikely]] \
268 ASSUMING_REPORT(#v " will be between " #a " and " #b " (inclusive)", { \
269 { #v, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_v_v)) }, \
270 { #a, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_a_v)) }, \
271 { #b, std::format("{}", ::ghassanpl::detail::GetFormattable(_assuming_b_v)) } \
272 }, ::ghassanpl::detail::AdditionalDataToString(__VA_ARGS__)); } while (false)
273#else
274
275#define Assuming(exp, ...) GHPL_ASSUME(!!(exp))
276#define AssumingNotReachable(...) GHPL_ASSUME(false)
277
278// NOTE: The three macros below have a significant codegen overhead, so we do not assume what they check. I don't think the compiler can infer any useful information from it anyway.
279#define AssumingNotRecursive(...) do {} while (false)
280#define AssumingSingleThread(...) do {} while (false)
281#define AssumingOnThread(thread_to_check, ...) do { \
282 std::ignore = (thread_to_check); \
283 } while (false)
284
285#define AssumingNull(exp, ...) GHPL_ASSUME(!((const void*)std::to_address(exp) != nullptr))
286#define AssumingNotNull(exp, ...) GHPL_ASSUME(!((const void*)std::to_address(exp) == nullptr))
287#define AssumingBinOp(a, b, op, text, ...) GHPL_ASSUME(((a) op (b)))
288#define AssumingEqual(a, b, ...) AssumingBinOp(a, b, ==, "be equal to", __VA_ARGS__)
289#define AssumingZero(a, ...) AssumingBinOp(a, 0, ==, "be equal to", __VA_ARGS__)
290#define AssumingNotEqual(a, b, ...) AssumingBinOp(a, b, !=, "not be equal to", __VA_ARGS__)
291#define AssumingGreater(a, b, ...) AssumingBinOp(a, b, >, "be greater than", __VA_ARGS__)
292#define AssumingLess(a, b, ...) AssumingBinOp(a, b, <, "be less than", __VA_ARGS__)
293#define AssumingGreaterEqual(a, b, ...) AssumingBinOp(a, b, >=, "be greater or equal to", __VA_ARGS__)
294#define AssumingLessEqual(a, b, ...) AssumingBinOp(a, b, <=, "be less or equal to", __VA_ARGS__)
295#define AssumingEmpty(exp, ...) do { using std::empty; GHPL_ASSUME(empty(exp)); } while (false)
296#define AssumingNotEmpty(exp, ...) do { using std::empty; using std::size; GHPL_ASSUME(!empty(exp)); } while (false)
297#define AssumingNullOrEmpty(exp, ...) do { using std::empty; using std::size; GHPL_ASSUME(::ghassanpl::detail::IsNullOrEmpty(exp)); } while (false)
298#define AssumingNotNullOrEmpty(exp, ...) do { using std::empty; using std::size; GHPL_ASSUME(!::ghassanpl::detail::IsNullOrEmpty(exp)); } while (false)
299#define AssumingValidIndex(_index, _container, ...) do { using std::size; auto&& _assuming_index = (_index); GHPL_ASSUME(((_assuming_index) >= 0 && size_t(_assuming_index) < size(_container))); } while (false)
300#define AssumingValidIterator(_iterator, _container, ...) do { using std::end; auto&& _assuming_iterator = (_iterator); auto&& _assuming_container = (_container); const auto _assuming_end = end(_assuming_container); GHPL_ASSUME(!(_assuming_iterator == _assuming_end)); } while (false)
301#define AssumingBetween(v, a, b, ...) do { auto&& _assuming_v_v = (v); auto&& _assuming_a_v = (a); auto&& _assuming_b_v = (b); GHPL_ASSUME(_assuming_v_v >= _assuming_a_v && _assuming_v_v < _assuming_b_v); } while (false)
302#define AssumingBetweenInclusive(v, a, b, ...) do { auto&& _assuming_v_v = (v); auto&& _assuming_a_v = (a); auto&& _assuming_b_v = (b); GHPL_ASSUME(_assuming_v_v >= _assuming_a_v && _assuming_v_v <= _assuming_b_v); } while (false)
303
304#define AssumingContainsBits(bits_to_find, bit_container, ...) do { auto&& _assuming_a_v = (bit_container); auto&& _assuming_b_v = (bits_to_find); GHPL_ASSUME(!((_assuming_a_v & _assuming_b_v) == _assuming_b_v)); } while (false)
305
306#define AssumingContains(_key, _container, ...) do { auto&& _assuming_key = (_key); auto&& _assuming_container = (_container); \
307 GHPL_ASSUME(_assuming_container.contains(_assuming_key)); } while (false)
308
309
310#endif
311
313
314namespace ghassanpl
315{
316 namespace detail
317 {
318 inline bool IsNullOrEmpty(const char* str) { return str == nullptr || str[0] == 0; }
319 template <typename T>
320 inline bool IsNullOrEmpty(T&& str) { using std::empty; return empty(str); }
321
322#if ASSUMING_DEBUG
323 /*
324 template <typename T>
325 concept formattable = requires { typename std::formatter<T>; };
326 */
327
328 template <typename T>
329 concept formattable = requires { { std::formatter<T>{} }; };
330
331 template <typename T>
332 concept streamable = requires (T val, std::stringstream & ss) { { ss << val }; };
333
334 template <typename T>
335 decltype(auto) GetFormattable(T&& val)
336 {
337 using simple_type = std::remove_cvref_t<T>;
338 if constexpr (std::is_constructible_v<std::string_view, simple_type>)
339 return std::forward<T>(val);
340 else if constexpr (std::is_pointer_v<simple_type>)
341 return (const void*)std::to_address(std::forward<T>(val));
342 else if constexpr (streamable<simple_type> && !formattable<simple_type>)
343 {
344 std::stringstream ss;
345 ss << std::forward<T>(val);
346 return std::move(ss).str();
347 }
348 else if constexpr (!formattable<simple_type> && !streamable<simple_type>)
349 return std::format("<{}>", typeid(simple_type).name());
350 else
351 return std::forward<T>(val);
352 }
353
354 inline std::string AdditionalDataToString() { return {}; }
355
356 template <typename T, typename... ARGS>
357 std::string AdditionalDataToString(T&& fmt, ARGS&&... args)
358 {
359 auto formattable = std::tuple{ GetFormattable(std::forward<ARGS>(args))... };
360 auto format_args = std::apply([](auto&... args) { return std::make_format_args(args...); }, formattable);
361 return std::vformat(std::forward<T>(fmt), std::move(format_args));
362 }
363
365 struct RecursionScopeMarker
366 {
367 explicit RecursionScopeMarker(int& counter) : mCounter(counter) { ++mCounter; }
368 ~RecursionScopeMarker() { --mCounter; }
369 int& mCounter;
370 };
371
372
373#endif
374 }
375
383
384 inline auto DefaultReportAssumptionFailure(std::string_view expectation, std::initializer_list<std::pair<std::string_view, std::string>> values, std::string data, source_location loc
386 , std::stacktrace stacktrace
387#endif // ASSUMING_USE_STACKTRACE
389 {
390 throw std::make_tuple(
391 std::move(expectation),
392 std::move(values),
393 std::move(data),
394 std::move(loc)
396 , std::move(stacktrace)
397#endif // ASSUMING_USE_STACKTRACE
398 );
399 }
400
408 inline auto (*AssumptionFailureHandler)(std::string_view expectation, std::initializer_list<std::pair<std::string_view, std::string>> values, std::string data, source_location loc
409#if ASSUMING_USE_STACKTRACE
410 , std::stacktrace stacktrace
411#endif // ASSUMING_USE_STACKTRACE
412 ) -> AssumptionHandlerResult = nullptr;
413
415
416#if defined(ASSUMING_REPORT_NORETURN) && ASSUMING_REPORT_NORETURN
417 [[noreturn]]
418#endif
419 inline auto ReportAssumptionFailure(std::string_view expectation, std::initializer_list<std::pair<std::string_view, std::string>> values, std::string data, source_location loc
420#if __INTELLISENSE__
421 = {}
422#else
423 = source_location::current()
424#endif
425#if ASSUMING_USE_STACKTRACE
426 , std::stacktrace stacktrace = std::stacktrace::current()
427#endif // ASSUMING_USE_STACKTRACE
429 {
431 return AssumptionFailureHandler(std::move(expectation), std::move(values), std::move(data), std::move(loc)
433 , std::move(stacktrace)
434#endif // ASSUMING_USE_STACKTRACE
435 );
436 else
437 {
438 return DefaultReportAssumptionFailure(std::move(expectation), std::move(values), std::move(data), std::move(loc)
440 , std::move(stacktrace)
441#endif // ASSUMING_USE_STACKTRACE
442 );
443 }
444 }
445}
446
449
453
457
462
auto(* AssumptionFailureHandler)(std::string_view expectation, std::initializer_list< std::pair< std::string_view, std::string > > values, std::string data, source_location loc) -> AssumptionHandlerResult=nullptr
This variable should be set to a handler function by your own code, as it is called by an assumption ...
Definition assuming.h:408
#define ASSUMING_USE_STACKTRACE
You can define this macro to 1 or 0 to include <stacktrace> (if available) or not; if this functional...
Definition assuming.h:81
The below code is based on Sun's libm library code, which is licensed under the following license:
Primary namespace for everything in this library.
Definition align+rec2.h:10
AssumptionHandlerResult
Return one of these values from the assumption failure handler AssumptionFailureHandler to trigger a ...
Definition assuming.h:378
@ Terminate
Terminate the program after reporting the assumption failure.
Definition assuming.h:380
@ Continue
Continue execution after reporting the assumption failure.
Definition assuming.h:381
@ Break
Break into debugger after reporting the assumption failure.
Definition assuming.h:379