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