From 160d24a10b889b1a7a28166fdd0679252adcd1e7 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 13 Jan 2010 06:47:17 +0000 Subject: [PATCH] First pass at Ryan's assertion code, minor tweaks to come. --- Makefile.ds | 1 + configure.in | 26 ++ include/SDL.h | 3 +- include/SDL_assert.h | 151 ++++++++++++ include/SDL_config.h.in | 3 + include/SDL_config_iphoneos.h | 3 + include/SDL_config_macosx.h | 3 + include/SDL_config_minimal.h | 3 + include/SDL_config_nintendods.h | 3 + include/SDL_config_pandora.h | 3 + include/SDL_config_win32.h | 3 + include/SDL_config_wiz.h | 3 + src/SDL.c | 27 +++ src/SDL_assert.c | 396 +++++++++++++++++++++++++++++++ src/video/cocoa/SDL_cocoavideo.m | 40 ++++ 15 files changed, 667 insertions(+), 1 deletion(-) create mode 100644 include/SDL_assert.h create mode 100644 src/SDL_assert.c diff --git a/Makefile.ds b/Makefile.ds index c44647ac9..eccc78326 100644 --- a/Makefile.ds +++ b/Makefile.ds @@ -36,6 +36,7 @@ src/SDL.c \ src/SDL_compat.c \ src/SDL_error.c \ src/SDL_fatal.c \ +src/SDL_assert.c \ src/audio/nds/SDL_ndsaudio.c \ src/audio/SDL_audio.c \ src/audio/SDL_audiocvt.c \ diff --git a/configure.in b/configure.in index 19f064970..cb3b6ce75 100644 --- a/configure.in +++ b/configure.in @@ -134,6 +134,32 @@ AC_C_CONST AC_C_INLINE AC_C_VOLATILE +dnl See whether we want assertions for debugging/sanity checking SDL itself. +AC_ARG_ENABLE(assertions, +AC_HELP_STRING([--enable-assertions], + [Enable internal sanity checks (yes/no/release/paranoid) [[default=release]]]), + , enable_assertions=release) +sdl_valid_assertion_level=no +if test x$enable_assertions = xno; then + sdl_valid_assertion_level=yes + AC_DEFINE(SDL_ASSERT_LEVEL, 0) +fi +if test x$enable_assertions = xrelease; then + sdl_valid_assertion_level=yes + AC_DEFINE(SDL_ASSERT_LEVEL, 1) +fi +if test x$enable_assertions = xyes; then + sdl_valid_assertion_level=yes + AC_DEFINE(SDL_ASSERT_LEVEL, 2) +fi +if test x$enable_assertions = xparanoid; then + sdl_valid_assertion_level=yes + AC_DEFINE(SDL_ASSERT_LEVEL, 3) +fi +if test x$sdl_valid_assertion_level = xno; then + AC_MSG_ERROR([*** unknown assertion level. stop.]) +fi + dnl See whether we can use gcc style dependency tracking AC_ARG_ENABLE(dependency-tracking, AC_HELP_STRING([--enable-dependency-tracking], diff --git a/include/SDL.h b/include/SDL.h index b7f888459..9831e831b 100644 --- a/include/SDL.h +++ b/include/SDL.h @@ -77,6 +77,7 @@ #include "SDL_main.h" #include "SDL_stdinc.h" +#include "SDL_assert.h" #include "SDL_atomic.h" #include "SDL_audio.h" #include "SDL_cpuinfo.h" @@ -89,8 +90,8 @@ #include "SDL_rwops.h" #include "SDL_thread.h" #include "SDL_timer.h" -#include "SDL_video.h" #include "SDL_version.h" +#include "SDL_video.h" #include "SDL_compat.h" #include "begin_code.h" diff --git a/include/SDL_assert.h b/include/SDL_assert.h new file mode 100644 index 000000000..0bc03186e --- /dev/null +++ b/include/SDL_assert.h @@ -0,0 +1,151 @@ +/* + 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_config.h" + +/* This is an assert macro for SDL's internal use. Not for the public API! */ + +#ifndef _SDL_assert_h +#define _SDL_assert_h + +#ifndef SDL_ASSERT_LEVEL +#error SDL_ASSERT_LEVEL is not defined. Please fix your SDL_config.h. +#endif + +/* +sizeof (x) makes the compiler still parse the expression even without +assertions enabled, so the code is always checked at compile time, but +doesn't actually generate code for it, so there are no side effects or +expensive checks at run time, just the constant size of what x WOULD be, +which presumably gets optimized out as unused. +This also solves the problem of... + + int somevalue = blah(); + SDL_assert(somevalue == 1); + +...which would cause compiles to complain that somevalue is unused if we +disable assertions. +*/ + +#define SDL_disabled_assert(condition) \ + do { (void) sizeof ((condition)); } while (0) + +#if (SDL_ASSERT_LEVEL > 0) + +/* +These are macros and not first class functions so that the debugger breaks +on the assertion line and not in some random guts of SDL, and so each +macro can have unique static variables associated with it. +*/ + +#if (defined(_MSC_VER) && ((_M_IX86) || (_M_X64))) + #define SDL_TriggerBreakpoint() __asm { int 3 } +#elif (defined(__GNUC__) && ((__i386__) || (__x86_64__))) + #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" ) +#elif defined(unix) + #include + #define SDL_TriggerBreakpoint() raise(SIGTRAP) +#else + #error Please define your platform or set SDL_ASSERT_LEVEL to 0. +#endif + +#if (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */ +# define SDL_FUNCTION __func__ +#elif ((__GNUC__ >= 2) || defined(_MSC_VER)) +# define SDL_FUNCTION __FUNCTION__ +#else +# define SDL_FUNCTION "???" +#endif + +typedef enum +{ + SDL_ASSERTION_RETRY, /**< Retry the assert immediately. */ + SDL_ASSERTION_BREAK, /**< Make the debugger trigger a breakpoint. */ + SDL_ASSERTION_ABORT, /**< Terminate the program. */ + SDL_ASSERTION_IGNORE, /**< Ignore the assert. */ + SDL_ASSERTION_ALWAYS_IGNORE, /**< Ignore the assert from now on. */ +} SDL_assert_state; + +typedef struct SDL_assert_data +{ + int always_ignore; + unsigned int trigger_count; + const char *condition; + const char *filename; + int linenum; + const char *function; + struct SDL_assert_data *next; +} SDL_assert_data; + +SDL_assert_state SDL_ReportAssertion(SDL_assert_data *, const char *, int); + +/* the do {} while(0) avoids dangling else problems: + if (x) SDL_assert(y); else blah(); + ... without the do/while, the "else" could attach to this macro's "if". + We try to handle just the minimum we need here in a macro...the loop, + the static vars, and break points. The heavy lifting is handled in + SDL_ReportAssertion(), in SDL_assert.c. +*/ +#define SDL_enabled_assert(condition) \ + do { \ + while ( !(condition) ) { \ + static struct SDL_assert_data assert_data = { \ + 0, 0, #condition, __FILE__, 0, 0, 0 \ + }; \ + const SDL_assert_state state = SDL_ReportAssertion(&assert_data, \ + SDL_FUNCTION, \ + __LINE__); \ + if (state == SDL_ASSERTION_RETRY) { \ + continue; /* go again. */ \ + } else if (state == SDL_ASSERTION_BREAK) { \ + SDL_TriggerBreakpoint(); \ + } \ + break; /* not retrying. */ \ + } \ + } while (0) + +#endif /* enabled assertions support code */ + +/* Enable various levels of assertions. */ +#if SDL_ASSERT_LEVEL == 0 /* assertions disabled */ +# define SDL_assert(condition) SDL_disabled_assert(condition) +# define SDL_assert_release(condition) SDL_disabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 1 /* release settings. */ +# define SDL_assert(condition) SDL_enabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_enabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 2 /* normal settings. */ +# define SDL_assert(condition) SDL_enabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 3 /* paranoid settings. */ +# define SDL_assert(condition) SDL_enabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_enabled_assert(condition) +#else +# error Unknown assertion level. Please fix your SDL_config.h. +#endif + +#endif /* _SDL_assert_h */ + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/include/SDL_config.h.in b/include/SDL_config.h.in index 7b8fb66ce..f5e6f40b6 100644 --- a/include/SDL_config.h.in +++ b/include/SDL_config.h.in @@ -162,6 +162,9 @@ #include #endif /* HAVE_LIBC */ +/* SDL internal assertion support */ +#undef SDL_ASSERT_LEVEL + /* Allow disabling of core subsystems */ #undef SDL_AUDIO_DISABLED #undef SDL_CPUINFO_DISABLED diff --git a/include/SDL_config_iphoneos.h b/include/SDL_config_iphoneos.h index 9248c3c1b..66b2bd437 100644 --- a/include/SDL_config_iphoneos.h +++ b/include/SDL_config_iphoneos.h @@ -25,6 +25,9 @@ #include "SDL_platform.h" +/* SDL internal assertion support */ +#define SDL_ASSERT_LEVEL 1 + #if !defined(_STDINT_H_) && (!defined(HAVE_STDINT_H) || !_HAVE_STDINT_H) typedef signed char int8_t; typedef unsigned char uint8_t; diff --git a/include/SDL_config_macosx.h b/include/SDL_config_macosx.h index be44f3325..66733b0d0 100644 --- a/include/SDL_config_macosx.h +++ b/include/SDL_config_macosx.h @@ -28,6 +28,9 @@ /* This gets us MAC_OS_X_VERSION_MIN_REQUIRED... */ #include +/* SDL internal assertion support */ +#define SDL_ASSERT_LEVEL 1 + /* This is a set of defines to configure the SDL features */ #ifdef __LP64__ diff --git a/include/SDL_config_minimal.h b/include/SDL_config_minimal.h index 92c6a595b..6fc0829fc 100644 --- a/include/SDL_config_minimal.h +++ b/include/SDL_config_minimal.h @@ -33,6 +33,9 @@ #include +/* SDL internal assertion support */ +#define SDL_ASSERT_LEVEL 1 + #if !defined(_STDINT_H_) && (!defined(HAVE_STDINT_H) || !_HAVE_STDINT_H) typedef signed char int8_t; typedef unsigned char uint8_t; diff --git a/include/SDL_config_nintendods.h b/include/SDL_config_nintendods.h index 5f71b98d5..9e3ea314f 100644 --- a/include/SDL_config_nintendods.h +++ b/include/SDL_config_nintendods.h @@ -27,6 +27,9 @@ /* This is a set of defines to configure the SDL features */ +/* SDL internal assertion support */ +#define SDL_ASSERT_LEVEL 1 + #if !defined(_STDINT_H_) && (!defined(HAVE_STDINT_H) || !_HAVE_STDINT_H) typedef signed char int8_t; typedef unsigned char uint8_t; diff --git a/include/SDL_config_pandora.h b/include/SDL_config_pandora.h index da05654b8..58d1aec54 100644 --- a/include/SDL_config_pandora.h +++ b/include/SDL_config_pandora.h @@ -28,6 +28,9 @@ /* General platform specific identifiers */ #include "SDL_platform.h" +/* SDL internal assertion support */ +#define SDL_ASSERT_LEVEL 1 + #define SDL_HAS_64BIT_TYPE 1 #define SDL_BYTEORDER 1234 diff --git a/include/SDL_config_win32.h b/include/SDL_config_win32.h index 9dc1645b0..3b23364f6 100644 --- a/include/SDL_config_win32.h +++ b/include/SDL_config_win32.h @@ -27,6 +27,9 @@ /* This is a set of defines to configure the SDL features */ +/* SDL internal assertion support */ +#define SDL_ASSERT_LEVEL 1 + #if !defined(_STDINT_H_) && (!defined(HAVE_STDINT_H) || !_HAVE_STDINT_H) #if defined(__GNUC__) || defined(__DMC__) || defined(__WATCOMC__) #define HAVE_STDINT_H 1 diff --git a/include/SDL_config_wiz.h b/include/SDL_config_wiz.h index 1b4b4ab33..fc28afed5 100644 --- a/include/SDL_config_wiz.h +++ b/include/SDL_config_wiz.h @@ -28,6 +28,9 @@ /* General platform specific identifiers */ #include "SDL_platform.h" +/* SDL internal assertion support */ +#define SDL_ASSERT_LEVEL 1 + /* Make sure that this isn't included by Visual C++ */ #ifdef _MSC_VER #error You should copy include/SDL_config.h.default to include/SDL_config.h diff --git a/src/SDL.c b/src/SDL.c index b6bace98c..0c52fc627 100644 --- a/src/SDL.c +++ b/src/SDL.c @@ -25,6 +25,8 @@ #include "SDL.h" #include "SDL_fatal.h" +#include "SDL_assert.h" + #if !SDL_VIDEO_DISABLED #include "video/SDL_leaks.h" #endif @@ -52,6 +54,9 @@ extern int SDL_HelperWindowCreate(void); extern int SDL_HelperWindowDestroy(void); #endif +extern int SDL_AssertionsInit(void); +extern void SDL_AssertionsQuit(void); + /* The initialized subsystems */ static Uint32 SDL_initialized = 0; static Uint32 ticks_started = 0; @@ -153,6 +158,10 @@ SDL_Init(Uint32 flags) } #endif + if (SDL_AssertionsInit() < 0) { + return -1; + } + /* Clear the error message */ SDL_ClearError(); @@ -171,6 +180,21 @@ SDL_Init(Uint32 flags) if (!(flags & SDL_INIT_NOPARACHUTE)) { SDL_InstallParachute(); } + + /* brief sanity checks for the sanity checks. :) */ + SDL_assert(1); + SDL_assert_release(1); + SDL_assert_paranoid(1); + SDL_assert(0 || 1); + SDL_assert_release(0 || 1); + SDL_assert_paranoid(0 || 1); + +#if 0 /* enable this to test assertion failures. */ + SDL_assert_release(1 == 2); + SDL_assert_release(5 < 4); + SDL_assert_release(0 && "This is a test"); +#endif + return (0); } @@ -239,6 +263,7 @@ SDL_Quit(void) fflush(stdout); #endif + /* !!! FIXME: make this an assertion. */ /* Print the number of surfaces not freed */ if (surfaces_allocated != 0) { fprintf(stderr, "SDL Warning: %d SDL surfaces extant\n", @@ -253,6 +278,8 @@ SDL_Quit(void) /* Uninstall any parachute signal handlers */ SDL_UninstallParachute(); + SDL_AssertionsQuit(); + #if !SDL_THREADS_DISABLED && SDL_THREAD_PTH pth_kill(); #endif diff --git a/src/SDL_assert.c b/src/SDL_assert.c new file mode 100644 index 000000000..3799e61e7 --- /dev/null +++ b/src/SDL_assert.c @@ -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 +#else /* fprintf, _exit(), etc. */ +#include +#include +#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: */ + diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 0377c7d33..e7a09806d 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -135,4 +135,44 @@ Cocoa_QuitMouse(_this); } + +/* + * Mac OS X assertion support. + * + * This doesn't really have aything to do with the interfaces of the SDL video + * subsystem, but we need to stuff this into an Objective-C source code file. + */ + +SDL_assert_state +SDL_PromptAssertion_cocoa(const SDL_assert_data *data) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *msg = [NSString stringWithFormat: + @"Assertion failure at %s (%s:%d), triggered %u time%s:\n '%s'", + data->function, data->filename, data->linenum, + data->trigger_count, (data->trigger_count == 1) ? "" : "s", + data->condition]; + + NSLog(msg); + + /* + * !!! FIXME: this code needs to deal with fullscreen modes: + * !!! FIXME: reset to default desktop, runModal, reset to current? + */ + + NSAlert* alert = [[NSAlert alloc] init]; + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert setMessageText:msg]; + [alert addButtonWithTitle:@"Retry"]; + [alert addButtonWithTitle:@"Break"]; + [alert addButtonWithTitle:@"Abort"]; + [alert addButtonWithTitle:@"Ignore"]; + [alert addButtonWithTitle:@"Always Ignore"]; + const NSInteger clicked = [alert runModal]; + [pool release]; + return (SDL_assert_state) (clicked - NSAlertFirstButtonReturn); +} + /* vim: set ts=4 sw=4 expandtab: */ +