src/SDL_assert.c
author Sam Lantinga
Wed, 12 Jun 2019 10:38:49 -0700
changeset 12848 a9a7c1e48f0b
parent 12841 8a0e446a4cf9
child 12849 5cd9ce1d28f2
permissions -rw-r--r--
Better patch to make it more clear what's going on
     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 #if defined(__WATCOMC__)
   124 #pragma aux SDL_ExitProcess aborts;
   125 #endif
   126 static SDL_NORETURN void SDL_ExitProcess(int exitcode)
   127 {
   128 #ifdef __WIN32__
   129     /* "if you do not know the state of all threads in your process, it is
   130        better to call TerminateProcess than ExitProcess"
   131        https://msdn.microsoft.com/en-us/library/windows/desktop/ms682658(v=vs.85).aspx */
   132     TerminateProcess(GetCurrentProcess(), exitcode);
   133     /* MingW doesn't have TerminateProcess marked as noreturn, so add an
   134        ExitProcess here that will never be reached but make MingW happy. */
   135     ExitProcess(exitcode);
   136 #elif defined(__EMSCRIPTEN__)
   137     emscripten_cancel_main_loop();  /* this should "kill" the app. */
   138     emscripten_force_exit(exitcode);  /* this should "kill" the app. */
   139     exit(exitcode);
   140 #else
   141 #ifdef HAVE__EXIT /* Upper case _Exit() */
   142     _Exit(exitcode);
   143 #else
   144     _exit(exitcode);
   145 #endif
   146 #endif
   147 }
   148 
   149 
   150 #if defined(__WATCOMC__)
   151 #pragma aux SDL_AbortAssertion aborts;
   152 #endif
   153 static void SDL_AbortAssertion(void)
   154 {
   155     SDL_Quit();
   156     SDL_ExitProcess(42);
   157 }
   158 
   159 
   160 static SDL_assert_state SDLCALL
   161 SDL_PromptAssertion(const SDL_assert_data *data, void *userdata)
   162 {
   163 #ifdef __WIN32__
   164     #define ENDLINE "\r\n"
   165 #else
   166     #define ENDLINE "\n"
   167 #endif
   168 
   169     const char *envr;
   170     SDL_assert_state state = SDL_ASSERTION_ABORT;
   171     SDL_Window *window;
   172     SDL_MessageBoxData messagebox;
   173     SDL_MessageBoxButtonData buttons[] = {
   174         {   0,  SDL_ASSERTION_RETRY,            "Retry" },
   175         {   0,  SDL_ASSERTION_BREAK,            "Break" },
   176         {   0,  SDL_ASSERTION_ABORT,            "Abort" },
   177         {   SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
   178                 SDL_ASSERTION_IGNORE,           "Ignore" },
   179         {   SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
   180                 SDL_ASSERTION_ALWAYS_IGNORE,    "Always Ignore" }
   181     };
   182     char *message;
   183     int selected;
   184 
   185     (void) userdata;  /* unused in default handler. */
   186 
   187     /* !!! FIXME: why is this using SDL_stack_alloc and not just "char message[SDL_MAX_LOG_MESSAGE];" ? */
   188     message = SDL_stack_alloc(char, SDL_MAX_LOG_MESSAGE);
   189     if (!message) {
   190         /* Uh oh, we're in real trouble now... */
   191         return SDL_ASSERTION_ABORT;
   192     }
   193     SDL_snprintf(message, SDL_MAX_LOG_MESSAGE,
   194                  "Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE
   195                     "  '%s'",
   196                  data->function, data->filename, data->linenum,
   197                  data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
   198                  data->condition);
   199 
   200     debug_print("\n\n%s\n\n", message);
   201 
   202     /* let env. variable override, so unit tests won't block in a GUI. */
   203     envr = SDL_getenv("SDL_ASSERT");
   204     if (envr != NULL) {
   205         SDL_stack_free(message);
   206 
   207         if (SDL_strcmp(envr, "abort") == 0) {
   208             return SDL_ASSERTION_ABORT;
   209         } else if (SDL_strcmp(envr, "break") == 0) {
   210             return SDL_ASSERTION_BREAK;
   211         } else if (SDL_strcmp(envr, "retry") == 0) {
   212             return SDL_ASSERTION_RETRY;
   213         } else if (SDL_strcmp(envr, "ignore") == 0) {
   214             return SDL_ASSERTION_IGNORE;
   215         } else if (SDL_strcmp(envr, "always_ignore") == 0) {
   216             return SDL_ASSERTION_ALWAYS_IGNORE;
   217         } else {
   218             return SDL_ASSERTION_ABORT;  /* oh well. */
   219         }
   220     }
   221 
   222     /* Leave fullscreen mode, if possible (scary!) */
   223     window = SDL_GetFocusWindow();
   224     if (window) {
   225         if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
   226             SDL_MinimizeWindow(window);
   227         } else {
   228             /* !!! FIXME: ungrab the input if we're not fullscreen? */
   229             /* No need to mess with the window */
   230             window = NULL;
   231         }
   232     }
   233 
   234     /* Show a messagebox if we can, otherwise fall back to stdio */
   235     SDL_zero(messagebox);
   236     messagebox.flags = SDL_MESSAGEBOX_WARNING;
   237     messagebox.window = window;
   238     messagebox.title = "Assertion Failed";
   239     messagebox.message = message;
   240     messagebox.numbuttons = SDL_arraysize(buttons);
   241     messagebox.buttons = buttons;
   242 
   243     if (SDL_ShowMessageBox(&messagebox, &selected) == 0) {
   244         if (selected == -1) {
   245             state = SDL_ASSERTION_IGNORE;
   246         } else {
   247             state = (SDL_assert_state)selected;
   248         }
   249     }
   250 
   251     else
   252     {
   253 #if defined(__EMSCRIPTEN__)
   254         /* This is nasty, but we can't block on a custom UI. */
   255         for ( ; ; ) {
   256             SDL_bool okay = SDL_TRUE;
   257             char *buf = (char *) EM_ASM_INT({
   258                 var str =
   259                     UTF8ToString($0) + '\n\n' +
   260                     'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
   261                 var reply = window.prompt(str, "i");
   262                 if (reply === null) {
   263                     reply = "i";
   264                 }
   265                 return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL);
   266             }, message);
   267 
   268             if (SDL_strcmp(buf, "a") == 0) {
   269                 state = SDL_ASSERTION_ABORT;
   270             /* (currently) no break functionality on Emscripten
   271             } else if (SDL_strcmp(buf, "b") == 0) {
   272                 state = SDL_ASSERTION_BREAK; */
   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 = SDL_FALSE;
   281             }
   282             free(buf);
   283 
   284             if (okay) {
   285                 break;
   286             }
   287         }
   288 #elif defined(HAVE_STDIO_H)
   289         /* this is a little hacky. */
   290         for ( ; ; ) {
   291             char buf[32];
   292             fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
   293             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 #endif /* HAVE_STDIO_H */
   316     }
   317 
   318     /* Re-enter fullscreen mode */
   319     if (window) {
   320         SDL_RestoreWindow(window);
   321     }
   322 
   323     SDL_stack_free(message);
   324 
   325     return state;
   326 }
   327 
   328 
   329 SDL_assert_state
   330 SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file,
   331                     int line)
   332 {
   333     SDL_assert_state state = SDL_ASSERTION_IGNORE;
   334     static int assertion_running = 0;
   335 
   336 #ifndef SDL_THREADS_DISABLED
   337     static SDL_SpinLock spinlock = 0;
   338     SDL_AtomicLock(&spinlock);
   339     if (assertion_mutex == NULL) { /* never called SDL_Init()? */
   340         assertion_mutex = SDL_CreateMutex();
   341         if (assertion_mutex == NULL) {
   342             SDL_AtomicUnlock(&spinlock);
   343             return SDL_ASSERTION_IGNORE;   /* oh well, I guess. */
   344         }
   345     }
   346     SDL_AtomicUnlock(&spinlock);
   347 
   348     if (SDL_LockMutex(assertion_mutex) < 0) {
   349         return SDL_ASSERTION_IGNORE;   /* oh well, I guess. */
   350     }
   351 #endif
   352 
   353     /* doing this because Visual C is upset over assigning in the macro. */
   354     if (data->trigger_count == 0) {
   355         data->function = func;
   356         data->filename = file;
   357         data->linenum = line;
   358     }
   359 
   360     SDL_AddAssertionToReport(data);
   361 
   362     assertion_running++;
   363     if (assertion_running > 1) {   /* assert during assert! Abort. */
   364         if (assertion_running == 2) {
   365             SDL_AbortAssertion();
   366         } else if (assertion_running == 3) {  /* Abort asserted! */
   367             SDL_ExitProcess(42);
   368         } else {
   369             while (1) { /* do nothing but spin; what else can you do?! */ }
   370         }
   371     }
   372 
   373     if (!data->always_ignore) {
   374         state = assertion_handler(data, assertion_userdata);
   375     }
   376 
   377     switch (state)
   378     {
   379         case SDL_ASSERTION_ABORT:
   380             SDL_AbortAssertion();
   381             return SDL_ASSERTION_IGNORE;  /* shouldn't return, but oh well. */
   382 
   383         case SDL_ASSERTION_ALWAYS_IGNORE:
   384             state = SDL_ASSERTION_IGNORE;
   385             data->always_ignore = 1;
   386             break;
   387 
   388         case SDL_ASSERTION_IGNORE:
   389         case SDL_ASSERTION_RETRY:
   390         case SDL_ASSERTION_BREAK:
   391             break;  /* macro handles these. */
   392     }
   393 
   394     assertion_running--;
   395 
   396 #ifndef SDL_THREADS_DISABLED
   397     SDL_UnlockMutex(assertion_mutex);
   398 #endif
   399 
   400     return state;
   401 }
   402 
   403 
   404 void SDL_AssertionsQuit(void)
   405 {
   406     SDL_GenerateAssertionReport();
   407 #ifndef SDL_THREADS_DISABLED
   408     if (assertion_mutex != NULL) {
   409         SDL_DestroyMutex(assertion_mutex);
   410         assertion_mutex = NULL;
   411     }
   412 #endif
   413 }
   414 
   415 void 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 
   426 const SDL_assert_data *SDL_GetAssertionReport(void)
   427 {
   428     return triggered_assertions;
   429 }
   430 
   431 void SDL_ResetAssertionReport(void)
   432 {
   433     SDL_assert_data *next = NULL;
   434     SDL_assert_data *item;
   435     for (item = triggered_assertions; item != NULL; item = next) {
   436         next = (SDL_assert_data *) item->next;
   437         item->always_ignore = SDL_FALSE;
   438         item->trigger_count = 0;
   439         item->next = NULL;
   440     }
   441 
   442     triggered_assertions = NULL;
   443 }
   444 
   445 SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
   446 {
   447     return SDL_PromptAssertion;
   448 }
   449 
   450 SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
   451 {
   452     if (userdata != NULL) {
   453         *userdata = assertion_userdata;
   454     }
   455     return assertion_handler;
   456 }
   457 
   458 /* vi: set ts=4 sw=4 expandtab: */