Mercurial > sdl-ios-xcode
view src/SDL_assert.c @ 5081:f2d8e0b59cca
Trying to close Andreas' branch again.
author | Sam Lantinga <slouken@libsdl.org> |
---|---|
date | Sun, 23 Jan 2011 22:34:19 -0800 |
parents | 791b3256fb22 |
children | 8e8876e4aec6 |
line wrap: on
line source
/* 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 "SDL_assert_c.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: */