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