| /* |
| SDL - Simple DirectMedia Layer |
| Copyright (C) 1997-2010 Sam Lantinga |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with this library; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| |
| Sam Lantinga |
| slouken@libsdl.org |
| */ |
| |
| #include "SDL.h" |
| #include "SDL_assert.h" |
| #include "video/SDL_sysvideo.h" |
| |
| #ifdef _WINDOWS |
| #define WIN32_LEAN_AND_MEAN 1 |
| #include <windows.h> |
| #else /* fprintf, _exit(), etc. */ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #endif |
| |
| static SDL_assert_state |
| SDL_PromptAssertion(const SDL_assert_data *data, void *userdata); |
| |
| /* |
| * We keep all triggered assertions in a singly-linked list so we can |
| * generate a report later. |
| */ |
| static SDL_assert_data assertion_list_terminator = { 0, 0, 0, 0, 0, 0, 0 }; |
| static SDL_assert_data *triggered_assertions = &assertion_list_terminator; |
| |
| static SDL_mutex *assertion_mutex = NULL; |
| static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion; |
| static void *assertion_userdata = NULL; |
| |
| #ifdef __GNUC__ |
| static void |
| debug_print(const char *fmt, ...) __attribute__((format (printf, 1, 2))); |
| #endif |
| |
| static void |
| debug_print(const char *fmt, ...) |
| { |
| #ifdef _WINDOWS |
| /* Format into a buffer for OutputDebugStringA(). */ |
| char buf[1024]; |
| char *startptr; |
| char *ptr; |
| int len; |
| va_list ap; |
| va_start(ap, fmt); |
| len = (int) SDL_vsnprintf(buf, sizeof (buf), fmt, ap); |
| va_end(ap); |
| |
| /* Visual C's vsnprintf() may not null-terminate the buffer. */ |
| if ((len >= sizeof (buf)) || (len < 0)) { |
| buf[sizeof (buf) - 1] = '\0'; |
| } |
| |
| /* Write it, sorting out the Unix newlines... */ |
| startptr = buf; |
| for (ptr = startptr; *ptr; ptr++) { |
| if (*ptr == '\n') { |
| *ptr = '\0'; |
| OutputDebugStringA(startptr); |
| OutputDebugStringA("\r\n"); |
| startptr = ptr+1; |
| } |
| } |
| |
| /* catch that last piece if it didn't have a newline... */ |
| if (startptr != ptr) { |
| OutputDebugStringA(startptr); |
| } |
| #else |
| /* Unix has it easy. Just dump it to stderr. */ |
| va_list ap; |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| va_end(ap); |
| fflush(stderr); |
| #endif |
| } |
| |
| |
| #ifdef _WINDOWS |
| static SDL_assert_state SDL_Windows_AssertChoice = SDL_ASSERTION_ABORT; |
| static const SDL_assert_data *SDL_Windows_AssertData = NULL; |
| |
| static LRESULT CALLBACK |
| SDL_Assertion_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| switch (msg) |
| { |
| case WM_CREATE: |
| { |
| /* !!! FIXME: all this code stinks. */ |
| const SDL_assert_data *data = SDL_Windows_AssertData; |
| char buf[1024]; |
| const int w = 100; |
| const int h = 25; |
| const int gap = 10; |
| int x = gap; |
| int y = 50; |
| int len; |
| int i; |
| static const struct { |
| const char *name; |
| SDL_assert_state state; |
| } buttons[] = { |
| {"Abort", SDL_ASSERTION_ABORT }, |
| {"Break", SDL_ASSERTION_BREAK }, |
| {"Retry", SDL_ASSERTION_RETRY }, |
| {"Ignore", SDL_ASSERTION_IGNORE }, |
| {"Always Ignore", SDL_ASSERTION_ALWAYS_IGNORE }, |
| }; |
| |
| len = (int) SDL_snprintf(buf, sizeof (buf), |
| "Assertion failure at %s (%s:%d), triggered %u time%s:\r\n '%s'", |
| data->function, data->filename, data->linenum, |
| data->trigger_count, (data->trigger_count == 1) ? "" : "s", |
| data->condition); |
| if ((len < 0) || (len >= sizeof (buf))) { |
| buf[sizeof (buf) - 1] = '\0'; |
| } |
| |
| CreateWindowA("STATIC", buf, |
| WS_VISIBLE | WS_CHILD | SS_LEFT, |
| x, y, 550, 100, |
| hwnd, (HMENU) 1, NULL, NULL); |
| y += 110; |
| |
| for (i = 0; i < (sizeof (buttons) / sizeof (buttons[0])); i++) { |
| CreateWindowA("BUTTON", buttons[i].name, |
| WS_VISIBLE | WS_CHILD, |
| x, y, w, h, |
| hwnd, (HMENU) buttons[i].state, NULL, NULL); |
| x += w + gap; |
| } |
| break; |
| } |
| |
| case WM_COMMAND: |
| SDL_Windows_AssertChoice = ((SDL_assert_state) (LOWORD(wParam))); |
| SDL_Windows_AssertData = NULL; |
| break; |
| |
| case WM_DESTROY: |
| SDL_Windows_AssertData = NULL; |
| break; |
| } |
| |
| return DefWindowProc(hwnd, msg, wParam, lParam); |
| } |
| |
| static SDL_assert_state |
| SDL_PromptAssertion_windows(const SDL_assert_data *data) |
| { |
| HINSTANCE hInstance = 0; /* !!! FIXME? */ |
| HWND hwnd; |
| MSG msg; |
| WNDCLASS wc = {0}; |
| |
| SDL_Windows_AssertChoice = SDL_ASSERTION_ABORT; |
| SDL_Windows_AssertData = data; |
| |
| wc.lpszClassName = TEXT("SDL_assert"); |
| wc.hInstance = hInstance ; |
| wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); |
| wc.lpfnWndProc = SDL_Assertion_WndProc; |
| wc.hCursor = LoadCursor(0, IDC_ARROW); |
| |
| RegisterClass(&wc); |
| hwnd = CreateWindow(wc.lpszClassName, TEXT("SDL assertion failure"), |
| WS_OVERLAPPEDWINDOW | WS_VISIBLE, |
| 150, 150, 570, 260, 0, 0, hInstance, 0); |
| |
| while (GetMessage(&msg, NULL, 0, 0) && (SDL_Windows_AssertData != NULL)) { |
| TranslateMessage(&msg); |
| DispatchMessage(&msg); |
| } |
| |
| DestroyWindow(hwnd); |
| UnregisterClass(wc.lpszClassName, hInstance); |
| return SDL_Windows_AssertChoice; |
| } |
| #endif |
| |
| |
| static void SDL_AddAssertionToReport(SDL_assert_data *data) |
| { |
| /* (data) is always a static struct defined with the assert macros, so |
| we don't have to worry about copying or allocating them. */ |
| if (data->next == NULL) { /* not yet added? */ |
| data->next = triggered_assertions; |
| triggered_assertions = data; |
| } |
| } |
| |
| |
| static void SDL_GenerateAssertionReport(void) |
| { |
| const SDL_assert_data *item; |
| |
| /* only do this if the app hasn't assigned an assertion handler. */ |
| if (assertion_handler != SDL_PromptAssertion) |
| return; |
| |
| item = SDL_GetAssertionReport(); |
| if (item->condition) |
| { |
| debug_print("\n\nSDL assertion report.\n"); |
| debug_print("All SDL assertions between last init/quit:\n\n"); |
| |
| while (item->condition) { |
| debug_print( |
| "'%s'\n" |
| " * %s (%s:%d)\n" |
| " * triggered %u time%s.\n" |
| " * always ignore: %s.\n", |
| item->condition, item->function, item->filename, |
| item->linenum, item->trigger_count, |
| (item->trigger_count == 1) ? "" : "s", |
| item->always_ignore ? "yes" : "no"); |
| item = item->next; |
| } |
| debug_print("\n"); |
| |
| SDL_ResetAssertionReport(); |
| } |
| } |
| |
| static void SDL_ExitProcess(int exitcode) |
| { |
| #ifdef _WINDOWS |
| ExitProcess(42); |
| #else |
| _exit(42); |
| #endif |
| } |
| |
| static void SDL_AbortAssertion(void) |
| { |
| SDL_Quit(); |
| SDL_ExitProcess(42); |
| } |
| |
| |
| static SDL_assert_state |
| SDL_PromptAssertion(const SDL_assert_data *data, void *userdata) |
| { |
| const char *envr; |
| SDL_assert_state state = SDL_ASSERTION_ABORT; |
| SDL_Window *window; |
| |
| (void) userdata; /* unused in default handler. */ |
| |
| debug_print("\n\n" |
| "Assertion failure at %s (%s:%d), triggered %u time%s:\n" |
| " '%s'\n" |
| "\n", |
| data->function, data->filename, data->linenum, |
| data->trigger_count, (data->trigger_count == 1) ? "" : "s", |
| data->condition); |
| |
| /* let env. variable override, so unit tests won't block in a GUI. */ |
| envr = SDL_getenv("SDL_ASSERT"); |
| if (envr != NULL) { |
| if (SDL_strcmp(envr, "abort") == 0) { |
| return SDL_ASSERTION_ABORT; |
| } else if (SDL_strcmp(envr, "break") == 0) { |
| return SDL_ASSERTION_BREAK; |
| } else if (SDL_strcmp(envr, "retry") == 0) { |
| return SDL_ASSERTION_RETRY; |
| } else if (SDL_strcmp(envr, "ignore") == 0) { |
| return SDL_ASSERTION_IGNORE; |
| } else if (SDL_strcmp(envr, "always_ignore") == 0) { |
| return SDL_ASSERTION_ALWAYS_IGNORE; |
| } else { |
| return SDL_ASSERTION_ABORT; /* oh well. */ |
| } |
| } |
| |
| /* Leave fullscreen mode, if possible (scary!) */ |
| window = SDL_GetFocusWindow(); |
| if (window) { |
| if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) { |
| SDL_MinimizeWindow(window); |
| } else { |
| /* !!! FIXME: ungrab the input if we're not fullscreen? */ |
| /* No need to mess with the window */ |
| window = 0; |
| } |
| } |
| |
| /* platform-specific UI... */ |
| |
| #ifdef _WINDOWS |
| state = SDL_PromptAssertion_windows(data); |
| |
| #elif __MACOSX__ |
| /* This has to be done in an Objective-C (*.m) file, so we call out. */ |
| extern SDL_assert_state SDL_PromptAssertion_cocoa(const SDL_assert_data *); |
| state = SDL_PromptAssertion_cocoa(data); |
| |
| #else |
| /* this is a little hacky. */ |
| for ( ; ; ) { |
| char buf[32]; |
| fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : "); |
| fflush(stderr); |
| if (fgets(buf, sizeof (buf), stdin) == NULL) { |
| break; |
| } |
| |
| if (SDL_strcmp(buf, "a") == 0) { |
| state = SDL_ASSERTION_ABORT; |
| break; |
| } else if (SDL_strcmp(envr, "b") == 0) { |
| state = SDL_ASSERTION_BREAK; |
| break; |
| } else if (SDL_strcmp(envr, "r") == 0) { |
| state = SDL_ASSERTION_RETRY; |
| break; |
| } else if (SDL_strcmp(envr, "i") == 0) { |
| state = SDL_ASSERTION_IGNORE; |
| break; |
| } else if (SDL_strcmp(envr, "A") == 0) { |
| state = SDL_ASSERTION_ALWAYS_IGNORE; |
| break; |
| } |
| } |
| #endif |
| |
| /* Re-enter fullscreen mode */ |
| if (window) { |
| SDL_RestoreWindow(window); |
| } |
| |
| return state; |
| } |
| |
| |
| SDL_assert_state |
| SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file, |
| int line) |
| { |
| static int assertion_running = 0; |
| static SDL_SpinLock spinlock = 0; |
| SDL_assert_state state = SDL_ASSERTION_IGNORE; |
| |
| SDL_AtomicLock(&spinlock); |
| if (assertion_mutex == NULL) { /* never called SDL_Init()? */ |
| assertion_mutex = SDL_CreateMutex(); |
| if (assertion_mutex == NULL) { |
| SDL_AtomicUnlock(&spinlock); |
| return SDL_ASSERTION_IGNORE; /* oh well, I guess. */ |
| } |
| } |
| SDL_AtomicUnlock(&spinlock); |
| |
| if (SDL_LockMutex(assertion_mutex) < 0) { |
| return SDL_ASSERTION_IGNORE; /* oh well, I guess. */ |
| } |
| |
| /* doing this because Visual C is upset over assigning in the macro. */ |
| if (data->trigger_count == 0) { |
| data->function = func; |
| data->filename = file; |
| data->linenum = line; |
| } |
| |
| SDL_AddAssertionToReport(data); |
| |
| data->trigger_count++; |
| |
| assertion_running++; |
| if (assertion_running > 1) { /* assert during assert! Abort. */ |
| if (assertion_running == 2) { |
| SDL_AbortAssertion(); |
| } else if (assertion_running == 3) { /* Abort asserted! */ |
| SDL_ExitProcess(42); |
| } else { |
| while (1) { /* do nothing but spin; what else can you do?! */ } |
| } |
| } |
| |
| if (!data->always_ignore) { |
| state = assertion_handler(data, assertion_userdata); |
| } |
| |
| switch (state) |
| { |
| case SDL_ASSERTION_ABORT: |
| SDL_AbortAssertion(); |
| return SDL_ASSERTION_IGNORE; /* shouldn't return, but oh well. */ |
| |
| case SDL_ASSERTION_ALWAYS_IGNORE: |
| state = SDL_ASSERTION_IGNORE; |
| data->always_ignore = 1; |
| break; |
| |
| case SDL_ASSERTION_IGNORE: |
| case SDL_ASSERTION_RETRY: |
| case SDL_ASSERTION_BREAK: |
| break; /* macro handles these. */ |
| } |
| |
| assertion_running--; |
| SDL_UnlockMutex(assertion_mutex); |
| |
| return state; |
| } |
| |
| |
| int SDL_AssertionsInit(void) |
| { |
| /* this is a no-op at the moment. */ |
| return 0; |
| } |
| |
| void SDL_AssertionsQuit(void) |
| { |
| SDL_GenerateAssertionReport(); |
| if (assertion_mutex != NULL) { |
| SDL_DestroyMutex(assertion_mutex); |
| assertion_mutex = NULL; |
| } |
| } |
| |
| void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata) |
| { |
| if (handler != NULL) { |
| assertion_handler = handler; |
| assertion_userdata = userdata; |
| } else { |
| assertion_handler = SDL_PromptAssertion; |
| assertion_userdata = NULL; |
| } |
| } |
| |
| const SDL_assert_data *SDL_GetAssertionReport(void) |
| { |
| return triggered_assertions; |
| } |
| |
| void SDL_ResetAssertionReport(void) |
| { |
| SDL_assert_data *item = triggered_assertions; |
| SDL_assert_data *next = NULL; |
| for (item = triggered_assertions; item->condition; item = next) { |
| next = (SDL_assert_data *) item->next; |
| item->always_ignore = SDL_FALSE; |
| item->trigger_count = 0; |
| item->next = NULL; |
| } |
| |
| triggered_assertions = &assertion_list_terminator; |
| } |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |