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