src/SDL_assert.c
author Sam Lantinga <slouken@libsdl.org>
Wed, 04 Jun 2014 10:56:56 -0700
changeset 8820 0e935d5b193a
parent 8583 fb2933ca805f
child 9619 b94b6d0bff0f
permissions -rw-r--r--
Added annotations to help code analysis tools

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