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