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