Atlas - SDL_assert.c

Home / ext / SDL / src Lines: 19 | Size: 13141 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#if defined(SDL_PLATFORM_WINDOWS) 24#include "core/windows/SDL_windows.h" 25#endif 26 27#include "SDL_assert_c.h" 28#include "video/SDL_sysvideo.h" 29 30#if defined(SDL_PLATFORM_WINDOWS) 31#ifndef WS_OVERLAPPEDWINDOW 32#define WS_OVERLAPPEDWINDOW 0 33#endif 34#endif 35 36#ifdef SDL_PLATFORM_EMSCRIPTEN 37#include <emscripten.h> 38#endif 39 40// The size of the stack buffer to use for rendering assert messages. 41#define SDL_MAX_ASSERT_MESSAGE_STACK 256 42 43static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata); 44 45/* 46 * We keep all triggered assertions in a singly-linked list so we can 47 * generate a report later. 48 */ 49static SDL_AssertData *triggered_assertions = NULL; 50 51#ifndef SDL_THREADS_DISABLED 52static SDL_Mutex *assertion_mutex = NULL; 53#endif 54 55static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion; 56static void *assertion_userdata = NULL; 57 58#ifdef __GNUC__ 59static void debug_print(const char *fmt, ...) __attribute__((format(printf, 1, 2))); 60#endif 61 62static void debug_print(const char *fmt, ...) 63{ 64 va_list ap; 65 va_start(ap, fmt); 66 SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap); 67 va_end(ap); 68} 69 70static void SDL_AddAssertionToReport(SDL_AssertData *data) 71{ 72 /* (data) is always a static struct defined with the assert macros, so 73 we don't have to worry about copying or allocating them. */ 74 data->trigger_count++; 75 if (data->trigger_count == 1) { // not yet added? 76 data->next = triggered_assertions; 77 triggered_assertions = data; 78 } 79} 80 81#if defined(SDL_PLATFORM_WINDOWS) 82#define ENDLINE "\r\n" 83#else 84#define ENDLINE "\n" 85#endif 86 87static int SDL_RenderAssertMessage(char *buf, size_t buf_len, const SDL_AssertData *data) 88{ 89 return SDL_snprintf(buf, buf_len, 90 "Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE " '%s'", 91 data->function, data->filename, data->linenum, 92 data->trigger_count, (data->trigger_count == 1) ? "time" : "times", 93 data->condition); 94} 95 96static void SDL_GenerateAssertionReport(void) 97{ 98 const SDL_AssertData *item = triggered_assertions; 99 100 // only do this if the app hasn't assigned an assertion handler. 101 if ((item) && (assertion_handler != SDL_PromptAssertion)) { 102 debug_print("\n\nSDL assertion report.\n"); 103 debug_print("All SDL assertions between last init/quit:\n\n"); 104 105 while (item) { 106 debug_print( 107 "'%s'\n" 108 " * %s (%s:%d)\n" 109 " * triggered %u time%s.\n" 110 " * always ignore: %s.\n", 111 item->condition, item->function, item->filename, 112 item->linenum, item->trigger_count, 113 (item->trigger_count == 1) ? "" : "s", 114 item->always_ignore ? "yes" : "no"); 115 item = item->next; 116 } 117 debug_print("\n"); 118 119 SDL_ResetAssertionReport(); 120 } 121} 122 123static SDL_NORETURN void SDL_AbortAssertion(void) 124{ 125 SDL_Quit(); 126 SDL_abort(); 127} 128 129static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata) 130{ 131 SDL_AssertState state = SDL_ASSERTION_ABORT; 132 SDL_Window *window; 133 SDL_MessageBoxData messagebox; 134 SDL_MessageBoxButtonData buttons[] = { 135 { 0, SDL_ASSERTION_RETRY, "Retry" }, 136 { 0, SDL_ASSERTION_BREAK, "Break" }, 137 { 0, SDL_ASSERTION_ABORT, "Abort" }, 138 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 139 SDL_ASSERTION_IGNORE, "Ignore" }, 140 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 141 SDL_ASSERTION_ALWAYS_IGNORE, "Always Ignore" } 142 }; 143 int selected; 144 145 char stack_buf[SDL_MAX_ASSERT_MESSAGE_STACK]; 146 char *message = stack_buf; 147 size_t buf_len = sizeof(stack_buf); 148 int len; 149 150 (void)userdata; // unused in default handler. 151 152 // Assume the output will fit... 153 len = SDL_RenderAssertMessage(message, buf_len, data); 154 155 // .. and if it didn't, try to allocate as much room as we actually need. 156 if (len >= (int)buf_len) { 157 if (SDL_size_add_check_overflow(len, 1, &buf_len)) { 158 message = (char *)SDL_malloc(buf_len); 159 if (message) { 160 len = SDL_RenderAssertMessage(message, buf_len, data); 161 } else { 162 message = stack_buf; 163 } 164 } 165 } 166 167 // Something went very wrong 168 if (len < 0) { 169 if (message != stack_buf) { 170 SDL_free(message); 171 } 172 return SDL_ASSERTION_ABORT; 173 } 174 175 debug_print("\n\n%s\n\n", message); 176 177 // let env. variable override, so unit tests won't block in a GUI. 178 const char *hint = SDL_GetHint(SDL_HINT_ASSERT); 179 if (hint) { 180 if (message != stack_buf) { 181 SDL_free(message); 182 } 183 184 if (SDL_strcmp(hint, "abort") == 0) { 185 return SDL_ASSERTION_ABORT; 186 } else if (SDL_strcmp(hint, "break") == 0) { 187 return SDL_ASSERTION_BREAK; 188 } else if (SDL_strcmp(hint, "retry") == 0) { 189 return SDL_ASSERTION_RETRY; 190 } else if (SDL_strcmp(hint, "ignore") == 0) { 191 return SDL_ASSERTION_IGNORE; 192 } else if (SDL_strcmp(hint, "always_ignore") == 0) { 193 return SDL_ASSERTION_ALWAYS_IGNORE; 194 } else { 195 return SDL_ASSERTION_ABORT; // oh well. 196 } 197 } 198 199 // Leave fullscreen mode, if possible (scary!) 200 window = SDL_GetToplevelForKeyboardFocus(); 201 if (window) { 202 if (window->fullscreen_exclusive) { 203 SDL_MinimizeWindow(window); 204 } else { 205 // !!! FIXME: ungrab the input if we're not fullscreen? 206 // No need to mess with the window 207 window = NULL; 208 } 209 } 210 211 // Show a messagebox if we can, otherwise fall back to stdio 212 SDL_zero(messagebox); 213 messagebox.flags = SDL_MESSAGEBOX_WARNING; 214 messagebox.window = window; 215 messagebox.title = "Assertion Failed"; 216 messagebox.message = message; 217 messagebox.numbuttons = SDL_arraysize(buttons); 218 messagebox.buttons = buttons; 219 220 if (SDL_ShowMessageBox(&messagebox, &selected)) { 221 if (selected == -1) { 222 state = SDL_ASSERTION_IGNORE; 223 } else { 224 state = (SDL_AssertState)selected; 225 } 226 } else { 227#ifdef SDL_PLATFORM_PRIVATE_ASSERT 228 SDL_PRIVATE_PROMPTASSERTION(); 229#elif defined(SDL_PLATFORM_EMSCRIPTEN) 230 // This is nasty, but we can't block on a custom UI. 231 for (;;) { 232 bool okay = true; 233 /* *INDENT-OFF* */ // clang-format off 234 int reply = MAIN_THREAD_EM_ASM_INT({ 235 var str = 236 UTF8ToString($0) + '\n\n' + 237 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :'; 238 var reply = window.prompt(str, "i"); 239 if (reply === null) { 240 reply = "i"; 241 } 242 return reply.length === 1 ? reply.charCodeAt(0) : -1; 243 }, message); 244 /* *INDENT-ON* */ // clang-format on 245 246 switch (reply) { 247 case 'a': 248 state = SDL_ASSERTION_ABORT; 249 break; 250#if 0 // (currently) no break functionality on Emscripten 251 case 'b': 252 state = SDL_ASSERTION_BREAK; 253 break; 254#endif 255 case 'r': 256 state = SDL_ASSERTION_RETRY; 257 break; 258 case 'i': 259 state = SDL_ASSERTION_IGNORE; 260 break; 261 case 'A': 262 state = SDL_ASSERTION_ALWAYS_IGNORE; 263 break; 264 default: 265 okay = false; 266 break; 267 } 268 269 if (okay) { 270 break; 271 } 272 } 273#elif defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_3DS) 274 // this is a little hacky. 275 for (;;) { 276 char buf[32]; 277 (void)fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : "); 278 (void)fflush(stderr); 279 if (fgets(buf, sizeof(buf), stdin) == NULL) { 280 break; 281 } 282 283 if (SDL_strncmp(buf, "a", 1) == 0) { 284 state = SDL_ASSERTION_ABORT; 285 break; 286 } else if (SDL_strncmp(buf, "b", 1) == 0) { 287 state = SDL_ASSERTION_BREAK; 288 break; 289 } else if (SDL_strncmp(buf, "r", 1) == 0) { 290 state = SDL_ASSERTION_RETRY; 291 break; 292 } else if (SDL_strncmp(buf, "i", 1) == 0) { 293 state = SDL_ASSERTION_IGNORE; 294 break; 295 } else if (SDL_strncmp(buf, "A", 1) == 0) { 296 state = SDL_ASSERTION_ALWAYS_IGNORE; 297 break; 298 } 299 } 300#else 301 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Assertion Failed", message, window); 302#endif // HAVE_STDIO_H 303 } 304 305 // Re-enter fullscreen mode 306 if (window) { 307 SDL_RestoreWindow(window); 308 } 309 310 if (message != stack_buf) { 311 SDL_free(message); 312 } 313 314 return state; 315} 316 317SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, const char *file, int line) 318{ 319 SDL_AssertState state = SDL_ASSERTION_IGNORE; 320 static int assertion_running = 0; 321 322#ifndef SDL_THREADS_DISABLED 323 static SDL_SpinLock spinlock = 0; 324 SDL_LockSpinlock(&spinlock); 325 if (!assertion_mutex) { // never called SDL_Init()? 326 assertion_mutex = SDL_CreateMutex(); 327 if (!assertion_mutex) { 328 SDL_UnlockSpinlock(&spinlock); 329 return SDL_ASSERTION_IGNORE; // oh well, I guess. 330 } 331 } 332 SDL_UnlockSpinlock(&spinlock); 333 334 SDL_LockMutex(assertion_mutex); 335#endif // !SDL_THREADS_DISABLED 336 337 // doing this because Visual C is upset over assigning in the macro. 338 if (data->trigger_count == 0) { 339 data->function = func; 340 data->filename = file; 341 data->linenum = line; 342 } 343 344 SDL_AddAssertionToReport(data); 345 346 assertion_running++; 347 if (assertion_running > 1) { // assert during assert! Abort. 348 if (assertion_running == 2) { 349 SDL_AbortAssertion(); 350 } else if (assertion_running == 3) { // Abort asserted! 351 SDL_abort(); 352 } else { 353 while (1) { // do nothing but spin; what else can you do?! 354 } 355 } 356 } 357 358 if (!data->always_ignore) { 359 state = assertion_handler(data, assertion_userdata); 360 } 361 362 switch (state) { 363 case SDL_ASSERTION_ALWAYS_IGNORE: 364 state = SDL_ASSERTION_IGNORE; 365 data->always_ignore = true; 366 break; 367 368 case SDL_ASSERTION_IGNORE: 369 case SDL_ASSERTION_RETRY: 370 case SDL_ASSERTION_BREAK: 371 break; // macro handles these. 372 373 case SDL_ASSERTION_ABORT: 374 SDL_AbortAssertion(); 375 // break; ...shouldn't return, but oh well. 376 } 377 378 assertion_running--; 379 380#ifndef SDL_THREADS_DISABLED 381 SDL_UnlockMutex(assertion_mutex); 382#endif 383 384 return state; 385} 386 387void SDL_AssertionsQuit(void) 388{ 389#if SDL_ASSERT_LEVEL > 0 390 SDL_GenerateAssertionReport(); 391#ifndef SDL_THREADS_DISABLED 392 if (assertion_mutex) { 393 SDL_DestroyMutex(assertion_mutex); 394 assertion_mutex = NULL; 395 } 396#endif 397#endif // SDL_ASSERT_LEVEL > 0 398} 399 400void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata) 401{ 402 if (handler != NULL) { 403 assertion_handler = handler; 404 assertion_userdata = userdata; 405 } else { 406 assertion_handler = SDL_PromptAssertion; 407 assertion_userdata = NULL; 408 } 409} 410 411const SDL_AssertData *SDL_GetAssertionReport(void) 412{ 413 return triggered_assertions; 414} 415 416void SDL_ResetAssertionReport(void) 417{ 418 SDL_AssertData *next = NULL; 419 SDL_AssertData *item; 420 for (item = triggered_assertions; item; item = next) { 421 next = (SDL_AssertData *)item->next; 422 item->always_ignore = false; 423 item->trigger_count = 0; 424 item->next = NULL; 425 } 426 427 triggered_assertions = NULL; 428} 429 430SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void) 431{ 432 return SDL_PromptAssertion; 433} 434 435SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata) 436{ 437 if (userdata) { 438 *userdata = assertion_userdata; 439 } 440 return assertion_handler; 441} 442
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.