src/SDL_assert.c
author Ryan C. Gordon
Fri, 19 May 2017 12:54:17 -0400
changeset 11015 b00c4088f687
parent 10737 3406a0f8b041
child 11017 411bf9f7f908
permissions -rw-r--r--
assert: allow assertions to work on platforms without threads.

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