src/SDL_assert.c
author Ryan C. Gordon <icculus@icculus.org>
Sun, 06 Nov 2011 17:05:48 -0500
changeset 6051 cb1f941ce38e
parent 5547 4ccecd0901e2
child 6138 4c64952a58fb
permissions -rw-r--r--
Mac OS X: Fixed build when compiling without Cocoa support.

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