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