src/SDL_assert.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 08 Apr 2011 13:03:26 -0700
changeset 5535 96594ac5fd1a
parent 5262 b530ef003506
child 5541 b63f1383f8c9
permissions -rw-r--r--
SDL 1.3 is now under the zlib license.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2011 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 
    22 #include "SDL.h"
    23 #include "SDL_atomic.h"
    24 #include "SDL_assert.h"
    25 #include "SDL_assert_c.h"
    26 #include "video/SDL_sysvideo.h"
    27 
    28 #ifdef __WIN32__
    29 #include "core/windows/SDL_windows.h"
    30 
    31 #ifndef WS_OVERLAPPEDWINDOW
    32 #define WS_OVERLAPPEDWINDOW 0
    33 #endif
    34 #else  /* fprintf, _exit(), etc. */
    35 #include <stdio.h>
    36 #include <stdlib.h>
    37 #include <unistd.h>
    38 #endif
    39 
    40 static SDL_assert_state
    41 SDL_PromptAssertion(const SDL_assert_data *data, void *userdata);
    42 
    43 /*
    44  * We keep all triggered assertions in a singly-linked list so we can
    45  *  generate a report later.
    46  */
    47 static SDL_assert_data assertion_list_terminator = { 0, 0, 0, 0, 0, 0, 0 };
    48 static SDL_assert_data *triggered_assertions = &assertion_list_terminator;
    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     if (data->next == NULL) {  /* not yet added? */
   221         data->next = triggered_assertions;
   222         triggered_assertions = data;
   223     }
   224 }
   225 
   226 
   227 static void SDL_GenerateAssertionReport(void)
   228 {
   229     const SDL_assert_data *item;
   230 
   231     /* only do this if the app hasn't assigned an assertion handler. */
   232     if (assertion_handler != SDL_PromptAssertion)
   233         return;
   234 
   235     item = SDL_GetAssertionReport();
   236     if (item->condition)
   237     {
   238         debug_print("\n\nSDL assertion report.\n");
   239         debug_print("All SDL assertions between last init/quit:\n\n");
   240 
   241         while (item->condition) {
   242             debug_print(
   243                 "'%s'\n"
   244                 "    * %s (%s:%d)\n"
   245                 "    * triggered %u time%s.\n"
   246                 "    * always ignore: %s.\n",
   247                 item->condition, item->function, item->filename,
   248                 item->linenum, item->trigger_count,
   249                 (item->trigger_count == 1) ? "" : "s",
   250                 item->always_ignore ? "yes" : "no");
   251             item = item->next;
   252         }
   253         debug_print("\n");
   254 
   255         SDL_ResetAssertionReport();
   256     }
   257 }
   258 
   259 static void SDL_ExitProcess(int exitcode)
   260 {
   261 #ifdef __WIN32__
   262     ExitProcess(42);
   263 #else
   264     _exit(42);
   265 #endif
   266 }
   267 
   268 static void SDL_AbortAssertion(void)
   269 {
   270     SDL_Quit();
   271     SDL_ExitProcess(42);
   272 }
   273 
   274 
   275 static SDL_assert_state
   276 SDL_PromptAssertion(const SDL_assert_data *data, void *userdata)
   277 {
   278     const char *envr;
   279     SDL_assert_state state = SDL_ASSERTION_ABORT;
   280     SDL_Window *window;
   281 
   282     (void) userdata;  /* unused in default handler. */
   283 
   284     debug_print("\n\n"
   285                 "Assertion failure at %s (%s:%d), triggered %u time%s:\n"
   286                 "  '%s'\n"
   287                 "\n",
   288                 data->function, data->filename, data->linenum,
   289                 data->trigger_count, (data->trigger_count == 1) ? "" : "s",
   290                 data->condition);
   291 
   292     /* let env. variable override, so unit tests won't block in a GUI. */
   293     envr = SDL_getenv("SDL_ASSERT");
   294     if (envr != NULL) {
   295         if (SDL_strcmp(envr, "abort") == 0) {
   296             return SDL_ASSERTION_ABORT;
   297         } else if (SDL_strcmp(envr, "break") == 0) {
   298             return SDL_ASSERTION_BREAK;
   299         } else if (SDL_strcmp(envr, "retry") == 0) {
   300             return SDL_ASSERTION_RETRY;
   301         } else if (SDL_strcmp(envr, "ignore") == 0) {
   302             return SDL_ASSERTION_IGNORE;
   303         } else if (SDL_strcmp(envr, "always_ignore") == 0) {
   304             return SDL_ASSERTION_ALWAYS_IGNORE;
   305         } else {
   306             return SDL_ASSERTION_ABORT;  /* oh well. */
   307         }
   308     }
   309 
   310     /* Leave fullscreen mode, if possible (scary!) */
   311     window = SDL_GetFocusWindow();
   312     if (window) {
   313         if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
   314             SDL_MinimizeWindow(window);
   315         } else {
   316             /* !!! FIXME: ungrab the input if we're not fullscreen? */
   317             /* No need to mess with the window */
   318             window = 0;
   319         }
   320     }
   321 
   322     /* platform-specific UI... */
   323 
   324 #ifdef __WIN32__
   325     state = SDL_PromptAssertion_windows(data);
   326 
   327 #elif __MACOSX__
   328     /* This has to be done in an Objective-C (*.m) file, so we call out. */
   329     extern SDL_assert_state SDL_PromptAssertion_cocoa(const SDL_assert_data *);
   330     state = SDL_PromptAssertion_cocoa(data);
   331 
   332 #else
   333     /* this is a little hacky. */
   334     for ( ; ; ) {
   335         char buf[32];
   336         fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
   337         fflush(stderr);
   338         if (fgets(buf, sizeof (buf), stdin) == NULL) {
   339             break;
   340         }
   341 
   342         if (SDL_strcmp(buf, "a") == 0) {
   343             state = SDL_ASSERTION_ABORT;
   344             break;
   345         } else if (SDL_strcmp(envr, "b") == 0) {
   346             state = SDL_ASSERTION_BREAK;
   347             break;
   348         } else if (SDL_strcmp(envr, "r") == 0) {
   349             state = SDL_ASSERTION_RETRY;
   350             break;
   351         } else if (SDL_strcmp(envr, "i") == 0) {
   352             state = SDL_ASSERTION_IGNORE;
   353             break;
   354         } else if (SDL_strcmp(envr, "A") == 0) {
   355             state = SDL_ASSERTION_ALWAYS_IGNORE;
   356             break;
   357         }
   358     }
   359 #endif
   360 
   361     /* Re-enter fullscreen mode */
   362     if (window) {
   363         SDL_RestoreWindow(window);
   364     }
   365 
   366     return state;
   367 }
   368 
   369 
   370 SDL_assert_state
   371 SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file,
   372                     int line)
   373 {
   374     static int assertion_running = 0;
   375     static SDL_SpinLock spinlock = 0;
   376     SDL_assert_state state = SDL_ASSERTION_IGNORE;
   377 
   378     SDL_AtomicLock(&spinlock);
   379     if (assertion_mutex == NULL) { /* never called SDL_Init()? */
   380         assertion_mutex = SDL_CreateMutex();
   381         if (assertion_mutex == NULL) {
   382             SDL_AtomicUnlock(&spinlock);
   383             return SDL_ASSERTION_IGNORE;   /* oh well, I guess. */
   384         }
   385     }
   386     SDL_AtomicUnlock(&spinlock);
   387 
   388     if (SDL_LockMutex(assertion_mutex) < 0) {
   389         return SDL_ASSERTION_IGNORE;   /* oh well, I guess. */
   390     }
   391 
   392     /* doing this because Visual C is upset over assigning in the macro. */
   393     if (data->trigger_count == 0) {
   394         data->function = func;
   395         data->filename = file;
   396         data->linenum = line;
   397     }
   398 
   399     SDL_AddAssertionToReport(data);
   400 
   401     data->trigger_count++;
   402 
   403     assertion_running++;
   404     if (assertion_running > 1) {   /* assert during assert! Abort. */
   405         if (assertion_running == 2) {
   406             SDL_AbortAssertion();
   407         } else if (assertion_running == 3) {  /* Abort asserted! */
   408             SDL_ExitProcess(42);
   409         } else {
   410             while (1) { /* do nothing but spin; what else can you do?! */ }
   411         }
   412     }
   413 
   414     if (!data->always_ignore) {
   415         state = assertion_handler(data, assertion_userdata);
   416     }
   417 
   418     switch (state)
   419     {
   420         case SDL_ASSERTION_ABORT:
   421             SDL_AbortAssertion();
   422             return SDL_ASSERTION_IGNORE;  /* shouldn't return, but oh well. */
   423 
   424         case SDL_ASSERTION_ALWAYS_IGNORE:
   425             state = SDL_ASSERTION_IGNORE;
   426             data->always_ignore = 1;
   427             break;
   428 
   429         case SDL_ASSERTION_IGNORE:
   430         case SDL_ASSERTION_RETRY:
   431         case SDL_ASSERTION_BREAK:
   432             break;  /* macro handles these. */
   433     }
   434 
   435     assertion_running--;
   436     SDL_UnlockMutex(assertion_mutex);
   437 
   438     return state;
   439 }
   440 
   441 
   442 int SDL_AssertionsInit(void)
   443 {
   444     /* this is a no-op at the moment. */
   445     return 0;
   446 }
   447 
   448 void SDL_AssertionsQuit(void)
   449 {
   450     SDL_GenerateAssertionReport();
   451     if (assertion_mutex != NULL) {
   452         SDL_DestroyMutex(assertion_mutex);
   453         assertion_mutex = NULL;
   454     }
   455 }
   456 
   457 void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
   458 {
   459     if (handler != NULL) {
   460         assertion_handler = handler;
   461         assertion_userdata = userdata;
   462     } else {
   463         assertion_handler = SDL_PromptAssertion;
   464         assertion_userdata = NULL;
   465     }
   466 }
   467 
   468 const SDL_assert_data *SDL_GetAssertionReport(void)
   469 {
   470     return triggered_assertions;
   471 }
   472 
   473 void SDL_ResetAssertionReport(void)
   474 {
   475     SDL_assert_data *item = triggered_assertions;
   476     SDL_assert_data *next = NULL;
   477     for (item = triggered_assertions; item->condition; item = next) {
   478         next = (SDL_assert_data *) item->next;
   479         item->always_ignore = SDL_FALSE;
   480         item->trigger_count = 0;
   481         item->next = NULL;
   482     }
   483 
   484     triggered_assertions = &assertion_list_terminator;
   485 }
   486 
   487 /* vi: set ts=4 sw=4 expandtab: */