Mercurial > sdl-ios-xcode
diff src/SDL_assert.c @ 3647:c5925cd41955
First pass at Ryan's assertion code, minor tweaks to come.
author | Sam Lantinga <slouken@libsdl.org> |
---|---|
date | Wed, 13 Jan 2010 06:47:17 +0000 |
parents | |
children | a9d830c05998 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SDL_assert.c Wed Jan 13 06:47:17 2010 +0000 @@ -0,0 +1,396 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 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_assert.h" +#include "SDL.h" + +#if (SDL_ASSERT_LEVEL > 0) + +#ifdef _WINDOWS +#define WIN32_LEAN_AND_MEAN 1 +#include <windows.h> +#else /* fprintf, _exit(), etc. */ +#include <stdio.h> +#include <stdlib.h> +#endif + +/* We can keep all triggered assertions in a singly-linked list so we can + * generate a report later. + */ +#if !SDL_ASSERTION_REPORT_DISABLED +static SDL_assert_data assertion_list_terminator = { 0, 0, 0, 0, 0, 0, 0 }; +static SDL_assert_data *triggered_assertions = &assertion_list_terminator; +#endif + +static void +debug_print(const char *fmt, ...) +//#ifdef __GNUC__ +//__attribute__((format (printf, 1, 2))) +//#endif +{ +#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); + fprintf(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) +{ +#if !SDL_ASSERTION_REPORT_DISABLED + /* (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; + } +#endif +} + +static void SDL_GenerateAssertionReport(void) +{ +#if !SDL_ASSERTION_REPORT_DISABLED + if (triggered_assertions != &assertion_list_terminator) + { + SDL_assert_data *item = triggered_assertions; + + debug_print("\n\nSDL assertion report.\n"); + debug_print("All SDL assertions between last init/quit:\n\n"); + + while (item != &assertion_list_terminator) { + 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"); + + triggered_assertions = &assertion_list_terminator; + } +#endif +} + + +static void SDL_AbortAssertion(void) +{ + SDL_Quit(); +#ifdef _WINDOWS + ExitProcess(42); +#elif unix || __APPLE__ + _exit(42); +#else + #error Please define your platform or set SDL_ASSERT_LEVEL to 0. +#endif +} + + +static SDL_assert_state SDL_PromptAssertion(const SDL_assert_data *data) +{ + const char *envr; + + 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. */ + } + } + + /* platform-specific UI... */ + +#ifdef _WINDOWS + return SDL_PromptAssertion_windows(data); + +#elif __APPLE__ + /* 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 *); + return SDL_PromptAssertion_cocoa(data); + +#elif unix + /* 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) { + return SDL_ASSERTION_ABORT; + } + + if (SDL_strcmp(buf, "a") == 0) { + return SDL_ASSERTION_ABORT; + } else if (SDL_strcmp(envr, "b") == 0) { + return SDL_ASSERTION_BREAK; + } else if (SDL_strcmp(envr, "r") == 0) { + return SDL_ASSERTION_RETRY; + } else if (SDL_strcmp(envr, "i") == 0) { + return SDL_ASSERTION_IGNORE; + } else if (SDL_strcmp(envr, "A") == 0) { + return SDL_ASSERTION_ALWAYS_IGNORE; + } + } + +#else + #error Please define your platform or set SDL_ASSERT_LEVEL to 0. +#endif + + return SDL_ASSERTION_ABORT; +} + + +static SDL_mutex *assertion_mutex = NULL; + +SDL_assert_state +SDL_ReportAssertion(SDL_assert_data *data, const char *func, int line) +{ + SDL_assert_state state; + + 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->linenum = line; + } + + SDL_AddAssertionToReport(data); + + data->trigger_count++; + if (data->always_ignore) { + SDL_UnlockMutex(assertion_mutex); + return SDL_ASSERTION_IGNORE; + } + + state = SDL_PromptAssertion(data); + + switch (state) + { + case SDL_ASSERTION_ABORT: + SDL_UnlockMutex(assertion_mutex); /* in case we assert in quit. */ + 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. */ + } + + SDL_UnlockMutex(assertion_mutex); + + return state; +} + +#endif /* SDL_ASSERT_LEVEL > 0 */ + + +int SDL_AssertionsInit(void) +{ +#if (SDL_ASSERT_LEVEL > 0) + assertion_mutex = SDL_CreateMutex(); + if (assertion_mutex == NULL) { + return -1; + } +#endif + return 0; +} + +void SDL_AssertionsQuit(void) +{ +#if (SDL_ASSERT_LEVEL > 0) + SDL_GenerateAssertionReport(); + SDL_DestroyMutex(assertion_mutex); + assertion_mutex = NULL; +#endif +} + +/* vi: set ts=4 sw=4 expandtab: */ +