src/SDL_assert.c
author Ryan C. Gordon
Tue, 07 Apr 2020 14:03:13 -0400
changeset 13704 25edf3df6e51
parent 13422 fd6a12de91c7
permissions -rw-r--r--
emscripten: support KaiOS's Left Soft Key and Right Soft Key (thanks, pelya!).

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