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