Simple Directmedia Layer
at main 14 kB view raw
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org> 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 // older Emscriptens don't have this, but we need to for wasm64 compatibility. 39 #ifndef MAIN_THREAD_EM_ASM_PTR 40 #ifdef __wasm64__ 41 #error You need to upgrade your Emscripten compiler to support wasm64 42 #else 43 #define MAIN_THREAD_EM_ASM_PTR MAIN_THREAD_EM_ASM_INT 44 #endif 45 #endif 46#endif 47 48// The size of the stack buffer to use for rendering assert messages. 49#define SDL_MAX_ASSERT_MESSAGE_STACK 256 50 51static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata); 52 53/* 54 * We keep all triggered assertions in a singly-linked list so we can 55 * generate a report later. 56 */ 57static SDL_AssertData *triggered_assertions = NULL; 58 59#ifndef SDL_THREADS_DISABLED 60static SDL_Mutex *assertion_mutex = NULL; 61#endif 62 63static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion; 64static void *assertion_userdata = NULL; 65 66#ifdef __GNUC__ 67static void debug_print(const char *fmt, ...) __attribute__((format(printf, 1, 2))); 68#endif 69 70static void debug_print(const char *fmt, ...) 71{ 72 va_list ap; 73 va_start(ap, fmt); 74 SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap); 75 va_end(ap); 76} 77 78static void SDL_AddAssertionToReport(SDL_AssertData *data) 79{ 80 /* (data) is always a static struct defined with the assert macros, so 81 we don't have to worry about copying or allocating them. */ 82 data->trigger_count++; 83 if (data->trigger_count == 1) { // not yet added? 84 data->next = triggered_assertions; 85 triggered_assertions = data; 86 } 87} 88 89#if defined(SDL_PLATFORM_WINDOWS) 90#define ENDLINE "\r\n" 91#else 92#define ENDLINE "\n" 93#endif 94 95static int SDL_RenderAssertMessage(char *buf, size_t buf_len, const SDL_AssertData *data) 96{ 97 return SDL_snprintf(buf, buf_len, 98 "Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE " '%s'", 99 data->function, data->filename, data->linenum, 100 data->trigger_count, (data->trigger_count == 1) ? "time" : "times", 101 data->condition); 102} 103 104static void SDL_GenerateAssertionReport(void) 105{ 106 const SDL_AssertData *item = triggered_assertions; 107 108 // only do this if the app hasn't assigned an assertion handler. 109 if ((item) && (assertion_handler != SDL_PromptAssertion)) { 110 debug_print("\n\nSDL assertion report.\n"); 111 debug_print("All SDL assertions between last init/quit:\n\n"); 112 113 while (item) { 114 debug_print( 115 "'%s'\n" 116 " * %s (%s:%d)\n" 117 " * triggered %u time%s.\n" 118 " * always ignore: %s.\n", 119 item->condition, item->function, item->filename, 120 item->linenum, item->trigger_count, 121 (item->trigger_count == 1) ? "" : "s", 122 item->always_ignore ? "yes" : "no"); 123 item = item->next; 124 } 125 debug_print("\n"); 126 127 SDL_ResetAssertionReport(); 128 } 129} 130 131/* This is not declared in any header, although it is shared between some 132 parts of SDL, because we don't want anything calling it without an 133 extremely good reason. */ 134#ifdef __WATCOMC__ 135extern void SDL_ExitProcess(int exitcode); 136#pragma aux SDL_ExitProcess aborts; 137#endif 138extern SDL_NORETURN void SDL_ExitProcess(int exitcode); 139 140#ifdef __WATCOMC__ 141static void SDL_AbortAssertion(void); 142#pragma aux SDL_AbortAssertion aborts; 143#endif 144static SDL_NORETURN void SDL_AbortAssertion(void) 145{ 146 SDL_Quit(); 147 SDL_ExitProcess(42); 148} 149 150static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata) 151{ 152 SDL_AssertState state = SDL_ASSERTION_ABORT; 153 SDL_Window *window; 154 SDL_MessageBoxData messagebox; 155 SDL_MessageBoxButtonData buttons[] = { 156 { 0, SDL_ASSERTION_RETRY, "Retry" }, 157 { 0, SDL_ASSERTION_BREAK, "Break" }, 158 { 0, SDL_ASSERTION_ABORT, "Abort" }, 159 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 160 SDL_ASSERTION_IGNORE, "Ignore" }, 161 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 162 SDL_ASSERTION_ALWAYS_IGNORE, "Always Ignore" } 163 }; 164 int selected; 165 166 char stack_buf[SDL_MAX_ASSERT_MESSAGE_STACK]; 167 char *message = stack_buf; 168 size_t buf_len = sizeof(stack_buf); 169 int len; 170 171 (void)userdata; // unused in default handler. 172 173 // Assume the output will fit... 174 len = SDL_RenderAssertMessage(message, buf_len, data); 175 176 // .. and if it didn't, try to allocate as much room as we actually need. 177 if (len >= (int)buf_len) { 178 if (SDL_size_add_check_overflow(len, 1, &buf_len)) { 179 message = (char *)SDL_malloc(buf_len); 180 if (message) { 181 len = SDL_RenderAssertMessage(message, buf_len, data); 182 } else { 183 message = stack_buf; 184 } 185 } 186 } 187 188 // Something went very wrong 189 if (len < 0) { 190 if (message != stack_buf) { 191 SDL_free(message); 192 } 193 return SDL_ASSERTION_ABORT; 194 } 195 196 debug_print("\n\n%s\n\n", message); 197 198 // let env. variable override, so unit tests won't block in a GUI. 199 const char *hint = SDL_GetHint(SDL_HINT_ASSERT); 200 if (hint) { 201 if (message != stack_buf) { 202 SDL_free(message); 203 } 204 205 if (SDL_strcmp(hint, "abort") == 0) { 206 return SDL_ASSERTION_ABORT; 207 } else if (SDL_strcmp(hint, "break") == 0) { 208 return SDL_ASSERTION_BREAK; 209 } else if (SDL_strcmp(hint, "retry") == 0) { 210 return SDL_ASSERTION_RETRY; 211 } else if (SDL_strcmp(hint, "ignore") == 0) { 212 return SDL_ASSERTION_IGNORE; 213 } else if (SDL_strcmp(hint, "always_ignore") == 0) { 214 return SDL_ASSERTION_ALWAYS_IGNORE; 215 } else { 216 return SDL_ASSERTION_ABORT; // oh well. 217 } 218 } 219 220 // Leave fullscreen mode, if possible (scary!) 221 window = SDL_GetToplevelForKeyboardFocus(); 222 if (window) { 223 if (window->fullscreen_exclusive) { 224 SDL_MinimizeWindow(window); 225 } else { 226 // !!! FIXME: ungrab the input if we're not fullscreen? 227 // No need to mess with the window 228 window = NULL; 229 } 230 } 231 232 // Show a messagebox if we can, otherwise fall back to stdio 233 SDL_zero(messagebox); 234 messagebox.flags = SDL_MESSAGEBOX_WARNING; 235 messagebox.window = window; 236 messagebox.title = "Assertion Failed"; 237 messagebox.message = message; 238 messagebox.numbuttons = SDL_arraysize(buttons); 239 messagebox.buttons = buttons; 240 241 if (SDL_ShowMessageBox(&messagebox, &selected)) { 242 if (selected == -1) { 243 state = SDL_ASSERTION_IGNORE; 244 } else { 245 state = (SDL_AssertState)selected; 246 } 247 } else { 248#ifdef SDL_PLATFORM_PRIVATE_ASSERT 249 SDL_PRIVATE_PROMPTASSERTION(); 250#elif defined(SDL_PLATFORM_EMSCRIPTEN) 251 // This is nasty, but we can't block on a custom UI. 252 for (;;) { 253 bool okay = true; 254 /* *INDENT-OFF* */ // clang-format off 255 char *buf = (char *) MAIN_THREAD_EM_ASM_PTR({ 256 var str = 257 UTF8ToString($0) + '\n\n' + 258 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :'; 259 var reply = window.prompt(str, "i"); 260 if (reply === null) { 261 reply = "i"; 262 } 263 return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL); 264 }, message); 265 /* *INDENT-ON* */ // clang-format on 266 267 if (SDL_strcmp(buf, "a") == 0) { 268 state = SDL_ASSERTION_ABORT; 269#if 0 // (currently) no break functionality on Emscripten 270 } else if (SDL_strcmp(buf, "b") == 0) { 271 state = SDL_ASSERTION_BREAK; 272#endif 273 } else if (SDL_strcmp(buf, "r") == 0) { 274 state = SDL_ASSERTION_RETRY; 275 } else if (SDL_strcmp(buf, "i") == 0) { 276 state = SDL_ASSERTION_IGNORE; 277 } else if (SDL_strcmp(buf, "A") == 0) { 278 state = SDL_ASSERTION_ALWAYS_IGNORE; 279 } else { 280 okay = false; 281 } 282 free(buf); // This should NOT be SDL_free() 283 284 if (okay) { 285 break; 286 } 287 } 288#elif defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_3DS) 289 // this is a little hacky. 290 for (;;) { 291 char buf[32]; 292 (void)fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : "); 293 (void)fflush(stderr); 294 if (fgets(buf, sizeof(buf), stdin) == NULL) { 295 break; 296 } 297 298 if (SDL_strncmp(buf, "a", 1) == 0) { 299 state = SDL_ASSERTION_ABORT; 300 break; 301 } else if (SDL_strncmp(buf, "b", 1) == 0) { 302 state = SDL_ASSERTION_BREAK; 303 break; 304 } else if (SDL_strncmp(buf, "r", 1) == 0) { 305 state = SDL_ASSERTION_RETRY; 306 break; 307 } else if (SDL_strncmp(buf, "i", 1) == 0) { 308 state = SDL_ASSERTION_IGNORE; 309 break; 310 } else if (SDL_strncmp(buf, "A", 1) == 0) { 311 state = SDL_ASSERTION_ALWAYS_IGNORE; 312 break; 313 } 314 } 315#else 316 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Assertion Failed", message, window); 317#endif // HAVE_STDIO_H 318 } 319 320 // Re-enter fullscreen mode 321 if (window) { 322 SDL_RestoreWindow(window); 323 } 324 325 if (message != stack_buf) { 326 SDL_free(message); 327 } 328 329 return state; 330} 331 332SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, const char *file, int line) 333{ 334 SDL_AssertState state = SDL_ASSERTION_IGNORE; 335 static int assertion_running = 0; 336 337#ifndef SDL_THREADS_DISABLED 338 static SDL_SpinLock spinlock = 0; 339 SDL_LockSpinlock(&spinlock); 340 if (!assertion_mutex) { // never called SDL_Init()? 341 assertion_mutex = SDL_CreateMutex(); 342 if (!assertion_mutex) { 343 SDL_UnlockSpinlock(&spinlock); 344 return SDL_ASSERTION_IGNORE; // oh well, I guess. 345 } 346 } 347 SDL_UnlockSpinlock(&spinlock); 348 349 SDL_LockMutex(assertion_mutex); 350#endif // !SDL_THREADS_DISABLED 351 352 // doing this because Visual C is upset over assigning in the macro. 353 if (data->trigger_count == 0) { 354 data->function = func; 355 data->filename = file; 356 data->linenum = line; 357 } 358 359 SDL_AddAssertionToReport(data); 360 361 assertion_running++; 362 if (assertion_running > 1) { // assert during assert! Abort. 363 if (assertion_running == 2) { 364 SDL_AbortAssertion(); 365 } else if (assertion_running == 3) { // Abort asserted! 366 SDL_ExitProcess(42); 367 } else { 368 while (1) { // do nothing but spin; what else can you do?! 369 } 370 } 371 } 372 373 if (!data->always_ignore) { 374 state = assertion_handler(data, assertion_userdata); 375 } 376 377 switch (state) { 378 case SDL_ASSERTION_ALWAYS_IGNORE: 379 state = SDL_ASSERTION_IGNORE; 380 data->always_ignore = true; 381 break; 382 383 case SDL_ASSERTION_IGNORE: 384 case SDL_ASSERTION_RETRY: 385 case SDL_ASSERTION_BREAK: 386 break; // macro handles these. 387 388 case SDL_ASSERTION_ABORT: 389 SDL_AbortAssertion(); 390 // break; ...shouldn't return, but oh well. 391 } 392 393 assertion_running--; 394 395#ifndef SDL_THREADS_DISABLED 396 SDL_UnlockMutex(assertion_mutex); 397#endif 398 399 return state; 400} 401 402void SDL_AssertionsQuit(void) 403{ 404#if SDL_ASSERT_LEVEL > 0 405 SDL_GenerateAssertionReport(); 406#ifndef SDL_THREADS_DISABLED 407 if (assertion_mutex) { 408 SDL_DestroyMutex(assertion_mutex); 409 assertion_mutex = NULL; 410 } 411#endif 412#endif // SDL_ASSERT_LEVEL > 0 413} 414 415void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata) 416{ 417 if (handler != NULL) { 418 assertion_handler = handler; 419 assertion_userdata = userdata; 420 } else { 421 assertion_handler = SDL_PromptAssertion; 422 assertion_userdata = NULL; 423 } 424} 425 426const SDL_AssertData *SDL_GetAssertionReport(void) 427{ 428 return triggered_assertions; 429} 430 431void SDL_ResetAssertionReport(void) 432{ 433 SDL_AssertData *next = NULL; 434 SDL_AssertData *item; 435 for (item = triggered_assertions; item; item = next) { 436 next = (SDL_AssertData *)item->next; 437 item->always_ignore = false; 438 item->trigger_count = 0; 439 item->next = NULL; 440 } 441 442 triggered_assertions = NULL; 443} 444 445SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void) 446{ 447 return SDL_PromptAssertion; 448} 449 450SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata) 451{ 452 if (userdata) { 453 *userdata = assertion_userdata; 454 } 455 return assertion_handler; 456}