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