src/SDL_assert.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 11 Feb 2011 22:37:15 -0800
changeset 5262 b530ef003506
parent 5090 327f181542f1
child 5535 96594ac5fd1a
permissions -rw-r--r--
Happy 2011! :)
     1 /*
     2     SDL - Simple DirectMedia Layer
     3     Copyright (C) 1997-2011 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Lesser General Public
     7     License as published by the Free Software Foundation; either
     8     version 2.1 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Lesser General Public License for more details.
    14 
    15     You should have received a copy of the GNU Lesser General Public
    16     License along with this library; if not, write to the Free Software
    17     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 
    23 #include "SDL.h"
    24 #include "SDL_atomic.h"
    25 #include "SDL_assert.h"
    26 #include "SDL_assert_c.h"
    27 #include "video/SDL_sysvideo.h"
    28 
    29 #ifdef __WIN32__
    30 #include "core/windows/SDL_windows.h"
    31 
    32 #ifndef WS_OVERLAPPEDWINDOW
    33 #define WS_OVERLAPPEDWINDOW 0
    34 #endif
    35 #else  /* fprintf, _exit(), etc. */
    36 #include <stdio.h>
    37 #include <stdlib.h>
    38 #include <unistd.h>
    39 #endif
    40 
    41 static SDL_assert_state
    42 SDL_PromptAssertion(const SDL_assert_data *data, void *userdata);
    43 
    44 /*
    45  * We keep all triggered assertions in a singly-linked list so we can
    46  *  generate a report later.
    47  */
    48 static SDL_assert_data assertion_list_terminator = { 0, 0, 0, 0, 0, 0, 0 };
    49 static SDL_assert_data *triggered_assertions = &assertion_list_terminator;
    50 
    51 static SDL_mutex *assertion_mutex = NULL;
    52 static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
    53 static void *assertion_userdata = NULL;
    54 
    55 #ifdef __GNUC__
    56 static void
    57 debug_print(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
    58 #endif
    59 
    60 static void
    61 debug_print(const char *fmt, ...)
    62 {
    63 #ifdef __WIN32__
    64     /* Format into a buffer for OutputDebugStringA(). */
    65     char buf[1024];
    66     char *startptr;
    67     char *ptr;
    68     LPTSTR tstr;
    69     int len;
    70     va_list ap;
    71     va_start(ap, fmt);
    72     len = (int) SDL_vsnprintf(buf, sizeof (buf), fmt, ap);
    73     va_end(ap);
    74 
    75     /* Visual C's vsnprintf() may not null-terminate the buffer. */
    76     if ((len >= sizeof (buf)) || (len < 0)) {
    77         buf[sizeof (buf) - 1] = '\0';
    78     }
    79 
    80     /* Write it, sorting out the Unix newlines... */
    81     startptr = buf;
    82     for (ptr = startptr; *ptr; ptr++) {
    83         if (*ptr == '\n') {
    84             *ptr = '\0';
    85             tstr = WIN_UTF8ToString(startptr);
    86             OutputDebugString(tstr);
    87             SDL_free(tstr);
    88             OutputDebugString(TEXT("\r\n"));
    89             startptr = ptr+1;
    90         }
    91     }
    92 
    93     /* catch that last piece if it didn't have a newline... */
    94     if (startptr != ptr) {
    95         tstr = WIN_UTF8ToString(startptr);
    96         OutputDebugString(tstr);
    97         SDL_free(tstr);
    98     }
    99 #else
   100     /* Unix has it easy. Just dump it to stderr. */
   101     va_list ap;
   102     va_start(ap, fmt);
   103     vfprintf(stderr, fmt, ap);
   104     va_end(ap);
   105     fflush(stderr);
   106 #endif
   107 }
   108 
   109 
   110 #ifdef __WIN32__
   111 static SDL_assert_state SDL_Windows_AssertChoice = SDL_ASSERTION_ABORT;
   112 static const SDL_assert_data *SDL_Windows_AssertData = NULL;
   113 
   114 static LRESULT CALLBACK
   115 SDL_Assertion_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
   116 {
   117     switch (msg)
   118     {
   119         case WM_CREATE:
   120         {
   121             /* !!! FIXME: all this code stinks. */
   122             const SDL_assert_data *data = SDL_Windows_AssertData;
   123             char buf[1024];
   124             LPTSTR tstr;
   125             const int w = 100;
   126             const int h = 25;
   127             const int gap = 10;
   128             int x = gap;
   129             int y = 50;
   130             int len;
   131             int i;
   132             static const struct { 
   133                 LPCTSTR name;
   134                 SDL_assert_state state;
   135             } buttons[] = {
   136                 {TEXT("Abort"), SDL_ASSERTION_ABORT },
   137                 {TEXT("Break"), SDL_ASSERTION_BREAK },
   138                 {TEXT("Retry"), SDL_ASSERTION_RETRY },
   139                 {TEXT("Ignore"), SDL_ASSERTION_IGNORE },
   140                 {TEXT("Always Ignore"), SDL_ASSERTION_ALWAYS_IGNORE },
   141             };
   142 
   143             len = (int) SDL_snprintf(buf, sizeof (buf), 
   144                          "Assertion failure at %s (%s:%d), triggered %u time%s:\r\n  '%s'",
   145                          data->function, data->filename, data->linenum,
   146                          data->trigger_count, (data->trigger_count == 1) ? "" : "s",
   147                          data->condition);
   148             if ((len < 0) || (len >= sizeof (buf))) {
   149                 buf[sizeof (buf) - 1] = '\0';
   150             }
   151 
   152             tstr = WIN_UTF8ToString(buf);
   153             CreateWindow(TEXT("STATIC"), tstr,
   154                          WS_VISIBLE | WS_CHILD | SS_LEFT,
   155                          x, y, 550, 100,
   156                          hwnd, (HMENU) 1, NULL, NULL);
   157             SDL_free(tstr);
   158             y += 110;
   159 
   160             for (i = 0; i < (sizeof (buttons) / sizeof (buttons[0])); i++) {
   161                 CreateWindow(TEXT("BUTTON"), buttons[i].name,
   162                          WS_VISIBLE | WS_CHILD,
   163                          x, y, w, h,
   164                          hwnd, (HMENU) buttons[i].state, NULL, NULL);
   165                 x += w + gap;
   166             }
   167             break;
   168         }
   169 
   170         case WM_COMMAND:
   171             SDL_Windows_AssertChoice = ((SDL_assert_state) (LOWORD(wParam)));
   172             SDL_Windows_AssertData = NULL;
   173             break;
   174 
   175         case WM_DESTROY:
   176             SDL_Windows_AssertData = NULL;
   177             break;
   178     }
   179 
   180     return DefWindowProc(hwnd, msg, wParam, lParam);
   181 }
   182 
   183 static SDL_assert_state
   184 SDL_PromptAssertion_windows(const SDL_assert_data *data)
   185 {
   186     HINSTANCE hInstance = 0;  /* !!! FIXME? */
   187     HWND hwnd;
   188     MSG msg;
   189     WNDCLASS wc = {0};
   190 
   191     SDL_Windows_AssertChoice = SDL_ASSERTION_ABORT;
   192     SDL_Windows_AssertData = data;
   193 
   194     wc.lpszClassName = TEXT("SDL_assert");
   195     wc.hInstance = hInstance ;
   196     wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
   197     wc.lpfnWndProc = SDL_Assertion_WndProc;
   198     wc.hCursor = LoadCursor(0, IDC_ARROW);
   199   
   200     RegisterClass(&wc);
   201     hwnd = CreateWindow(wc.lpszClassName, TEXT("SDL assertion failure"),
   202                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
   203                  150, 150, 570, 260, 0, 0, hInstance, 0);  
   204 
   205     while (GetMessage(&msg, NULL, 0, 0) && (SDL_Windows_AssertData != NULL)) {
   206         TranslateMessage(&msg);
   207         DispatchMessage(&msg);
   208     }
   209 
   210     DestroyWindow(hwnd);
   211     UnregisterClass(wc.lpszClassName, hInstance);
   212     return SDL_Windows_AssertChoice;
   213 }
   214 #endif
   215 
   216 
   217 static void SDL_AddAssertionToReport(SDL_assert_data *data)
   218 {
   219     /* (data) is always a static struct defined with the assert macros, so
   220        we don't have to worry about copying or allocating them. */
   221     if (data->next == NULL) {  /* not yet added? */
   222         data->next = triggered_assertions;
   223         triggered_assertions = data;
   224     }
   225 }
   226 
   227 
   228 static void SDL_GenerateAssertionReport(void)
   229 {
   230     const SDL_assert_data *item;
   231 
   232     /* only do this if the app hasn't assigned an assertion handler. */
   233     if (assertion_handler != SDL_PromptAssertion)
   234         return;
   235 
   236     item = SDL_GetAssertionReport();
   237     if (item->condition)
   238     {
   239         debug_print("\n\nSDL assertion report.\n");
   240         debug_print("All SDL assertions between last init/quit:\n\n");
   241 
   242         while (item->condition) {
   243             debug_print(
   244                 "'%s'\n"
   245                 "    * %s (%s:%d)\n"
   246                 "    * triggered %u time%s.\n"
   247                 "    * always ignore: %s.\n",
   248                 item->condition, item->function, item->filename,
   249                 item->linenum, item->trigger_count,
   250                 (item->trigger_count == 1) ? "" : "s",
   251                 item->always_ignore ? "yes" : "no");
   252             item = item->next;
   253         }
   254         debug_print("\n");
   255 
   256         SDL_ResetAssertionReport();
   257     }
   258 }
   259 
   260 static void SDL_ExitProcess(int exitcode)
   261 {
   262 #ifdef __WIN32__
   263     ExitProcess(42);
   264 #else
   265     _exit(42);
   266 #endif
   267 }
   268 
   269 static void SDL_AbortAssertion(void)
   270 {
   271     SDL_Quit();
   272     SDL_ExitProcess(42);
   273 }
   274 
   275 
   276 static SDL_assert_state
   277 SDL_PromptAssertion(const SDL_assert_data *data, void *userdata)
   278 {
   279     const char *envr;
   280     SDL_assert_state state = SDL_ASSERTION_ABORT;
   281     SDL_Window *window;
   282 
   283     (void) userdata;  /* unused in default handler. */
   284 
   285     debug_print("\n\n"
   286                 "Assertion failure at %s (%s:%d), triggered %u time%s:\n"
   287                 "  '%s'\n"
   288                 "\n",
   289                 data->function, data->filename, data->linenum,
   290                 data->trigger_count, (data->trigger_count == 1) ? "" : "s",
   291                 data->condition);
   292 
   293     /* let env. variable override, so unit tests won't block in a GUI. */
   294     envr = SDL_getenv("SDL_ASSERT");
   295     if (envr != NULL) {
   296         if (SDL_strcmp(envr, "abort") == 0) {
   297             return SDL_ASSERTION_ABORT;
   298         } else if (SDL_strcmp(envr, "break") == 0) {
   299             return SDL_ASSERTION_BREAK;
   300         } else if (SDL_strcmp(envr, "retry") == 0) {
   301             return SDL_ASSERTION_RETRY;
   302         } else if (SDL_strcmp(envr, "ignore") == 0) {
   303             return SDL_ASSERTION_IGNORE;
   304         } else if (SDL_strcmp(envr, "always_ignore") == 0) {
   305             return SDL_ASSERTION_ALWAYS_IGNORE;
   306         } else {
   307             return SDL_ASSERTION_ABORT;  /* oh well. */
   308         }
   309     }
   310 
   311     /* Leave fullscreen mode, if possible (scary!) */
   312     window = SDL_GetFocusWindow();
   313     if (window) {
   314         if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
   315             SDL_MinimizeWindow(window);
   316         } else {
   317             /* !!! FIXME: ungrab the input if we're not fullscreen? */
   318             /* No need to mess with the window */
   319             window = 0;
   320         }
   321     }
   322 
   323     /* platform-specific UI... */
   324 
   325 #ifdef __WIN32__
   326     state = SDL_PromptAssertion_windows(data);
   327 
   328 #elif __MACOSX__
   329     /* This has to be done in an Objective-C (*.m) file, so we call out. */
   330     extern SDL_assert_state SDL_PromptAssertion_cocoa(const SDL_assert_data *);
   331     state = SDL_PromptAssertion_cocoa(data);
   332 
   333 #else
   334     /* this is a little hacky. */
   335     for ( ; ; ) {
   336         char buf[32];
   337         fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
   338         fflush(stderr);
   339         if (fgets(buf, sizeof (buf), stdin) == NULL) {
   340             break;
   341         }
   342 
   343         if (SDL_strcmp(buf, "a") == 0) {
   344             state = SDL_ASSERTION_ABORT;
   345             break;
   346         } else if (SDL_strcmp(envr, "b") == 0) {
   347             state = SDL_ASSERTION_BREAK;
   348             break;
   349         } else if (SDL_strcmp(envr, "r") == 0) {
   350             state = SDL_ASSERTION_RETRY;
   351             break;
   352         } else if (SDL_strcmp(envr, "i") == 0) {
   353             state = SDL_ASSERTION_IGNORE;
   354             break;
   355         } else if (SDL_strcmp(envr, "A") == 0) {
   356             state = SDL_ASSERTION_ALWAYS_IGNORE;
   357             break;
   358         }
   359     }
   360 #endif
   361 
   362     /* Re-enter fullscreen mode */
   363     if (window) {
   364         SDL_RestoreWindow(window);
   365     }
   366 
   367     return state;
   368 }
   369 
   370 
   371 SDL_assert_state
   372 SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file,
   373                     int line)
   374 {
   375     static int assertion_running = 0;
   376     static SDL_SpinLock spinlock = 0;
   377     SDL_assert_state state = SDL_ASSERTION_IGNORE;
   378 
   379     SDL_AtomicLock(&spinlock);
   380     if (assertion_mutex == NULL) { /* never called SDL_Init()? */
   381         assertion_mutex = SDL_CreateMutex();
   382         if (assertion_mutex == NULL) {
   383             SDL_AtomicUnlock(&spinlock);
   384             return SDL_ASSERTION_IGNORE;   /* oh well, I guess. */
   385         }
   386     }
   387     SDL_AtomicUnlock(&spinlock);
   388 
   389     if (SDL_LockMutex(assertion_mutex) < 0) {
   390         return SDL_ASSERTION_IGNORE;   /* oh well, I guess. */
   391     }
   392 
   393     /* doing this because Visual C is upset over assigning in the macro. */
   394     if (data->trigger_count == 0) {
   395         data->function = func;
   396         data->filename = file;
   397         data->linenum = line;
   398     }
   399 
   400     SDL_AddAssertionToReport(data);
   401 
   402     data->trigger_count++;
   403 
   404     assertion_running++;
   405     if (assertion_running > 1) {   /* assert during assert! Abort. */
   406         if (assertion_running == 2) {
   407             SDL_AbortAssertion();
   408         } else if (assertion_running == 3) {  /* Abort asserted! */
   409             SDL_ExitProcess(42);
   410         } else {
   411             while (1) { /* do nothing but spin; what else can you do?! */ }
   412         }
   413     }
   414 
   415     if (!data->always_ignore) {
   416         state = assertion_handler(data, assertion_userdata);
   417     }
   418 
   419     switch (state)
   420     {
   421         case SDL_ASSERTION_ABORT:
   422             SDL_AbortAssertion();
   423             return SDL_ASSERTION_IGNORE;  /* shouldn't return, but oh well. */
   424 
   425         case SDL_ASSERTION_ALWAYS_IGNORE:
   426             state = SDL_ASSERTION_IGNORE;
   427             data->always_ignore = 1;
   428             break;
   429 
   430         case SDL_ASSERTION_IGNORE:
   431         case SDL_ASSERTION_RETRY:
   432         case SDL_ASSERTION_BREAK:
   433             break;  /* macro handles these. */
   434     }
   435 
   436     assertion_running--;
   437     SDL_UnlockMutex(assertion_mutex);
   438 
   439     return state;
   440 }
   441 
   442 
   443 int SDL_AssertionsInit(void)
   444 {
   445     /* this is a no-op at the moment. */
   446     return 0;
   447 }
   448 
   449 void SDL_AssertionsQuit(void)
   450 {
   451     SDL_GenerateAssertionReport();
   452     if (assertion_mutex != NULL) {
   453         SDL_DestroyMutex(assertion_mutex);
   454         assertion_mutex = NULL;
   455     }
   456 }
   457 
   458 void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
   459 {
   460     if (handler != NULL) {
   461         assertion_handler = handler;
   462         assertion_userdata = userdata;
   463     } else {
   464         assertion_handler = SDL_PromptAssertion;
   465         assertion_userdata = NULL;
   466     }
   467 }
   468 
   469 const SDL_assert_data *SDL_GetAssertionReport(void)
   470 {
   471     return triggered_assertions;
   472 }
   473 
   474 void SDL_ResetAssertionReport(void)
   475 {
   476     SDL_assert_data *item = triggered_assertions;
   477     SDL_assert_data *next = NULL;
   478     for (item = triggered_assertions; item->condition; item = next) {
   479         next = (SDL_assert_data *) item->next;
   480         item->always_ignore = SDL_FALSE;
   481         item->trigger_count = 0;
   482         item->next = NULL;
   483     }
   484 
   485     triggered_assertions = &assertion_list_terminator;
   486 }
   487 
   488 /* vi: set ts=4 sw=4 expandtab: */