Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
assert.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <cstdio>
8#include <cstdlib>
9#include <format>
10#include <functional>
11#include <source_location>
12#include <string>
13#include <string_view>
14
15#if defined(__cpp_lib_print) && (__cpp_lib_print >= 202302L)
16#include <print>
17#endif
18
19namespace helios {
20
21/**
22 * @brief Function signature for custom assertion handlers.
23 * @param condition The failed condition as a string
24 * @param loc Source location of the assertion
25 * @param message Additional message (may be empty)
26 */
27using AssertionHandler = void (*)(std::string_view condition,
28 const std::source_location& loc,
29 std::string_view message) noexcept;
30
31/// @brief Default assertion handler (`nullptr` means use built-in default
32/// behavior).
33inline constexpr AssertionHandler kDefaultAssertionHandler = nullptr;
34
35namespace details {
36
37#ifdef HELIOS_ENABLE_ASSERTS
38inline constexpr bool kEnableAssert = true;
39#else
40inline constexpr bool kEnableAssert = false;
41#endif
42
43/**
44 * @brief Formats a complete assertion failure message with source location and
45 * stack trace.
46 * @param condition The failed condition as a string
47 * @param loc Source location of the assertion
48 * @param message Additional message (may be empty)
49 * @return Formatted assertion message
50 */
51[[nodiscard]] std::string FormatAssertionMessage(
52 std::string_view condition, const std::source_location& loc,
53 std::string_view message);
54
55/**
56 * @brief Storage for the custom assertion handler.
57 * @details When set, this handler is called instead of the default behavior.
58 * The handler can be set by calling `SetAssertionHandler()`.
59 */
61
62/**
63 * @brief Default assertion handler that prints to stderr.
64 * @param condition The failed condition as a string
65 * @param loc Source location of the assertion
66 * @param message Additional message
67 */
68[[noreturn]] inline void DefaultAssertionHandler(
69 std::string_view condition, const std::source_location& loc,
70 std::string_view message) noexcept {
71 const std::string formatted = FormatAssertionMessage(condition, loc, message);
72
73#if defined(__cpp_lib_print) && (__cpp_lib_print >= 202302L)
74 std::println(stderr, "{}", formatted);
75#else
76 std::fprintf(stderr, "%s\n", formatted.c_str());
77#endif
78 std::fflush(stderr);
79 std::abort();
80}
81
82/**
83 * @brief Log plugin assertion handler (weak symbol).
84 * @details This is defined as a weak symbol that defaults to `nullptr`.
85 * When the log plugin is linked, it will provide the real implementation.
86 * The implementation should log via the logger system with critical level.
87 * @param condition The failed condition as a string
88 * @param loc Source location of the assertion
89 * @param message Additional message
90 */
91#ifdef _MSC_VER
92// MSVC doesn't support weak symbols, so we use a different approach.
93// The log plugin will set this via `SetLogPluginHandler` at initialization.
94inline AssertionHandler g_log_plugin_handler = nullptr;
95
96inline void LogPluginAssertionHandler(std::string_view condition,
97 const std::source_location& loc,
98 std::string_view message) noexcept {
99 if (g_log_plugin_handler) {
100 g_log_plugin_handler(condition, loc, message);
101 }
102}
103
104inline bool HasLogPluginHandler() noexcept {
105 return g_log_plugin_handler != nullptr;
106}
107
108/**
109 * @brief Sets the log plugin handler (called by log plugin at initialization).
110 * @param handler The handler function from the log plugin
111 */
112inline void SetLogPluginHandler(AssertionHandler handler) noexcept {
113 g_log_plugin_handler = handler;
114}
115
116#else
117// GCC/Clang support weak symbols
118
119/**
120 * @brief Weak symbol for log plugin handler check.
121 * @details The log plugin provides the real implementation.
122 * @return true if the log plugin handler is available
123 */
124[[gnu::weak]] bool HasLogPluginHandler() noexcept;
125
126/**
127 * @brief Weak symbol for log plugin assertion handler.
128 * @details The log plugin provides the real implementation.
129 */
130[[gnu::weak]] void LogPluginAssertionHandler(std::string_view condition,
131 const std::source_location& loc,
132 std::string_view message) noexcept;
133
134#endif
135
136/**
137 * @brief Unified assertion handling function.
138 * @details Priority order:
139 * 1. Custom handler (if set by user via SetAssertionHandler)
140 * 2. Log plugin handler (if log module/plugin is available and linked)
141 * 3. Default handler (prints to stderr)
142 *
143 * @param condition The failed condition as a string
144 * @param loc Source location of the assertion
145 * @param message Additional message
146 */
147inline void HandleAssertion(std::string_view condition,
148 const std::source_location& loc,
149 std::string_view message) noexcept {
150 // Priority 1: Custom user handler
151 if (g_custom_assertion_handler != nullptr) {
152 g_custom_assertion_handler(condition, loc, message);
153 return;
154 }
155
156 // Priority 2: Log plugin handler (if available)
157#ifdef _MSC_VER
158 if (HasLogPluginHandler()) {
159 LogPluginAssertionHandler(condition, loc, message);
160 return;
161 }
162#else
163 if (HasLogPluginHandler != nullptr && LogPluginAssertionHandler != nullptr &&
165 LogPluginAssertionHandler(condition, loc, message);
166 return;
167 }
168#endif
169
170 // Priority 3: Default handler (printf/println to stderr)
171 DefaultAssertionHandler(condition, loc, message);
172}
173
174} // namespace details
175
176/**
177 * @brief Sets a custom assertion handler.
178 * @details When set, this handler is called for all assertion failures instead
179 * of the default behavior. Set to `nullptr` to restore default behavior.
180 *
181 * Priority order for assertion handling:
182 * 1. Custom handler (if set via this function)
183 * 2. Log plugin handler (if log plugin is linked)
184 * 3. Default handler (prints to stderr)
185 *
186 * @param handler The custom handler function, or `nullptr` to use default
187 *
188 * @code
189 * // Set custom handler
190 * helios::SetAssertionHandler(
191 * [](std::string_view condition, const std::source_location& loc,
192 * std::string_view message) noexcept {
193 * // Your custom logging here
194 * helios::log::Critical("Assert: {} | {} [{}:{}]", condition, message,
195 * loc.file_name(), loc.line());
196 * });
197 *
198 * // Reset to default behavior
199 * helios::SetAssertionHandler(helios::kDefaultAssertionHandler);
200 * @endcode
201 */
202inline void SetAssertionHandler(AssertionHandler handler) noexcept {
204}
205
206/**
207 * @brief Gets the current custom assertion handler.
208 * @return The current custom handler, or `nullptr` if using default behavior
209 */
210[[nodiscard]] inline AssertionHandler GetAssertionHandler() noexcept {
212}
213
214/**
215 * @brief Prints a message with stack trace and aborts the program execution.
216 * @details Useful for placing in dead code branches or unreachable states.
217 * @param message The message to print before aborting
218 */
219void AbortWithStacktrace(std::string_view message) noexcept;
220
221} // namespace helios
222
223// NOLINTBEGIN(cppcoreguidelines-avoid-do-while)
224// NOLINTBEGIN(cppcoreguidelines-macro-usage)
225
226/**
227 * @brief Assertion macro that aborts execution in debug builds.
228 * @details Does nothing in release builds.
229 * Uses the configured assertion handler (custom -> log plugin -> default).
230 * Supports format strings and arguments.
231 * @param condition The condition to check
232 * @param ... Optional message (can be format string with arguments)
233 * @hideinitializer
234 */
235#ifdef HELIOS_ENABLE_ASSERTS
236#define HELIOS_ASSERT(condition, ...) \
237 do { \
238 if constexpr (::helios::details::kEnableAssert) { \
239 if (HELIOS_EXPECT_FALSE(!(condition))) [[unlikely]] { \
240 if constexpr (sizeof(#__VA_ARGS__) > 1) { \
241 try { \
242 const std::string msg = std::format("" __VA_ARGS__); \
243 ::helios::details::HandleAssertion( \
244 #condition, ::std::source_location::current(), msg); \
245 } catch (...) { \
246 ::helios::details::HandleAssertion( \
247 #condition, ::std::source_location::current(), \
248 "Formatting error in assertion"); \
249 } \
250 } else { \
251 ::helios::details::HandleAssertion( \
252 #condition, ::std::source_location::current(), ""); \
253 } \
254 HELIOS_DEBUG_BREAK(); \
255 } \
256 } \
257 } while (false)
258#else
259#define HELIOS_ASSERT(condition, ...) \
260 [[maybe_unused]] static constexpr auto HELIOS_ANONYMOUS_VAR(unused_assert) = 0
261#endif
262
263/**
264 * @brief Invariant check that asserts in debug builds and logs error in
265 * release.
266 * @details Provides runtime safety checks that are enforced even in release
267 * builds. In debug builds, triggers assertion. In release builds, logs error
268 * and continues.
269 * @param condition The condition to check
270 * @param ... Optional message (can be format string with arguments)
271 * @hideinitializer
272 */
273#ifdef HELIOS_ENABLE_ASSERTS
274#define HELIOS_INVARIANT(condition, ...) \
275 do { \
276 if (HELIOS_EXPECT_FALSE(!(condition))) [[unlikely]] { \
277 if constexpr (sizeof(#__VA_ARGS__) > 1) { \
278 try { \
279 const std::string msg = std::format("" __VA_ARGS__); \
280 ::helios::details::HandleAssertion( \
281 #condition, ::std::source_location::current(), msg); \
282 } catch (...) { \
283 ::helios::details::HandleAssertion( \
284 #condition, ::std::source_location::current(), \
285 "Formatting error in invariant"); \
286 } \
287 } else { \
288 ::helios::details::HandleAssertion( \
289 #condition, ::std::source_location::current(), ""); \
290 } \
291 HELIOS_DEBUG_BREAK(); \
292 } \
293 } while (false)
294#else
295#define HELIOS_INVARIANT(condition, ...) \
296 do { \
297 if (HELIOS_EXPECT_FALSE(!(condition))) [[unlikely]] { \
298 if constexpr (sizeof(#__VA_ARGS__) > 1) { \
299 try { \
300 const std::string msg = std::format("" __VA_ARGS__); \
301 ::helios::details::HandleAssertion( \
302 #condition, ::std::source_location::current(), msg); \
303 } catch (...) { \
304 ::helios::details::HandleAssertion( \
305 #condition, ::std::source_location::current(), ""); \
306 } \
307 } else { \
308 ::helios::details::HandleAssertion( \
309 #condition, ::std::source_location::current(), ""); \
310 } \
311 } \
312 } while (false)
313#endif
314
315/**
316 * @brief Verify macro that always checks the condition.
317 * @details Similar to assert but runs in both debug and release builds.
318 * Useful for validating external input or critical invariants.
319 * @param condition The condition to check
320 * @param ... Optional message (can be format string with arguments)
321 * @hideinitializer
322 */
323#define HELIOS_VERIFY(condition, ...) \
324 do { \
325 if (HELIOS_EXPECT_FALSE(!(condition))) [[unlikely]] { \
326 if constexpr (sizeof(#__VA_ARGS__) > 1) { \
327 try { \
328 const std::string msg = std::format("" __VA_ARGS__); \
329 ::helios::details::HandleAssertion( \
330 #condition, ::std::source_location::current(), msg); \
331 } catch (...) { \
332 ::helios::details::HandleAssertion( \
333 #condition, ::std::source_location::current(), \
334 "Formatting error in verify"); \
335 } \
336 } else { \
337 ::helios::details::HandleAssertion( \
338 #condition, ::std::source_location::current(), ""); \
339 } \
340 HELIOS_DEBUG_BREAK(); \
341 } \
342 } while (false)
343
344// NOLINTEND(cppcoreguidelines-macro-usage)
345// NOLINTEND(cppcoreguidelines-avoid-do-while)
AssertionHandler g_custom_assertion_handler
Storage for the custom assertion handler.
Definition assert.hpp:60
void HandleAssertion(std::string_view condition, const std::source_location &loc, std::string_view message) noexcept
Unified assertion handling function.
Definition assert.hpp:147
bool HasLogPluginHandler() noexcept
Log plugin assertion handler (weak symbol).
Definition assert.cpp:116
void DefaultAssertionHandler(std::string_view condition, const std::source_location &loc, std::string_view message) noexcept
Default assertion handler that prints to stderr.
Definition assert.hpp:68
std::string FormatAssertionMessage(std::string_view condition, const std::source_location &loc, std::string_view message)
Formats a complete assertion failure message with source location and stack trace.
Definition assert.cpp:37
void LogPluginAssertionHandler(std::string_view condition, const std::source_location &loc, std::string_view message) noexcept
Weak symbol for log plugin assertion handler.
Definition assert.cpp:123
constexpr bool kEnableAssert
Definition assert.hpp:40
void AbortWithStacktrace(std::string_view message) noexcept
Prints a message with stack trace and aborts the program execution.
Definition assert.cpp:69
void(*)(std::string_view condition, const std::source_location &loc, std::string_view message) noexcept AssertionHandler
Function signature for custom assertion handlers.
Definition assert.hpp:27
constexpr AssertionHandler kDefaultAssertionHandler
Default assertion handler (nullptr means use built-in default behavior).
Definition assert.hpp:33
AssertionHandler GetAssertionHandler() noexcept
Gets the current custom assertion handler.
Definition assert.hpp:210
void SetAssertionHandler(AssertionHandler handler) noexcept
Sets a custom assertion handler.
Definition assert.hpp:202