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