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