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