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