sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

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: */