src/SDL_assert.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 16 Jan 2011 17:45:42 -0800
changeset 5006 8e8876e4aec6
parent 4472 791b3256fb22
child 5007 adf6f0c8ec35
permissions -rw-r--r--
Include windows.h in SDL_atomic.h by default, but don't include the atomic API in SDL.h
This allows all SDL code to take advantage of the atomic intrinsics on Windows, but doesn't cause applications just including SDL.h to pull in windows.h
     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 1
    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: */