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