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