src/SDL_assert.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 20 Jan 2011 18:42:41 -0800
changeset 5064 eae20af0b983
parent 5007 adf6f0c8ec35
child 5086 c2539ff054c8
permissions -rw-r--r--
Fixed bug #929

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