src/SDL_assert.c
author Ryan C. Gordon <icculus@icculus.org>
Wed, 28 Feb 2018 01:23:49 -0500
changeset 11908 97092601ea78
parent 11811 5d94cb6b24d3
child 11911 235c5929651c
permissions -rw-r--r--
assert: Use TerminateProcess() on Windows, vs ExitProcess (thanks, Jack!).

"What I have done is use TerminateProcess rather than ExitProcess.
ExitProcess will cause Microsoft's leak detection to continue, TerminateProcess
won't. It is also technically wrong to use ExitProcess in the case of aborting
the application.

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