First pass at Ryan's assertion code, minor tweaks to come.
authorSam Lantinga <slouken@libsdl.org>
Wed, 13 Jan 2010 06:47:17 +0000
changeset 3647 c5925cd41955
parent 3646 88235d40b135
child 3648 a9d830c05998
First pass at Ryan's assertion code, minor tweaks to come.
Makefile.ds
configure.in
include/SDL.h
include/SDL_assert.h
include/SDL_config.h.in
include/SDL_config_iphoneos.h
include/SDL_config_macosx.h
include/SDL_config_minimal.h
include/SDL_config_nintendods.h
include/SDL_config_pandora.h
include/SDL_config_win32.h
include/SDL_config_wiz.h
src/SDL.c
src/SDL_assert.c
src/video/cocoa/SDL_cocoavideo.m
--- a/Makefile.ds	Wed Jan 13 06:39:44 2010 +0000
+++ b/Makefile.ds	Wed Jan 13 06:47:17 2010 +0000
@@ -36,6 +36,7 @@
 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 \
--- a/configure.in	Wed Jan 13 06:39:44 2010 +0000
+++ b/configure.in	Wed Jan 13 06:47:17 2010 +0000
@@ -134,6 +134,32 @@
 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],
--- a/include/SDL.h	Wed Jan 13 06:39:44 2010 +0000
+++ b/include/SDL.h	Wed Jan 13 06:47:17 2010 +0000
@@ -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_version.h"
 #include "SDL_video.h"
-#include "SDL_version.h"
 #include "SDL_compat.h"
 
 #include "begin_code.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/SDL_assert.h	Wed Jan 13 06:47:17 2010 +0000
@@ -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 <signal.h>
+    #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: */
+
--- a/include/SDL_config.h.in	Wed Jan 13 06:39:44 2010 +0000
+++ b/include/SDL_config.h.in	Wed Jan 13 06:47:17 2010 +0000
@@ -162,6 +162,9 @@
 #include <stdarg.h>
 #endif /* HAVE_LIBC */
 
+/* SDL internal assertion support */
+#undef SDL_ASSERT_LEVEL
+
 /* Allow disabling of core subsystems */
 #undef SDL_AUDIO_DISABLED
 #undef SDL_CPUINFO_DISABLED
--- a/include/SDL_config_iphoneos.h	Wed Jan 13 06:39:44 2010 +0000
+++ b/include/SDL_config_iphoneos.h	Wed Jan 13 06:47:17 2010 +0000
@@ -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;
--- a/include/SDL_config_macosx.h	Wed Jan 13 06:39:44 2010 +0000
+++ b/include/SDL_config_macosx.h	Wed Jan 13 06:47:17 2010 +0000
@@ -28,6 +28,9 @@
 /* This gets us MAC_OS_X_VERSION_MIN_REQUIRED... */
 #include <AvailabilityMacros.h>
 
+/* SDL internal assertion support */
+#define SDL_ASSERT_LEVEL 1
+
 /* This is a set of defines to configure the SDL features */
 
 #ifdef __LP64__
--- a/include/SDL_config_minimal.h	Wed Jan 13 06:39:44 2010 +0000
+++ b/include/SDL_config_minimal.h	Wed Jan 13 06:47:17 2010 +0000
@@ -33,6 +33,9 @@
 
 #include <stdarg.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;
--- a/include/SDL_config_nintendods.h	Wed Jan 13 06:39:44 2010 +0000
+++ b/include/SDL_config_nintendods.h	Wed Jan 13 06:47:17 2010 +0000
@@ -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;
--- a/include/SDL_config_pandora.h	Wed Jan 13 06:39:44 2010 +0000
+++ b/include/SDL_config_pandora.h	Wed Jan 13 06:47:17 2010 +0000
@@ -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
 
--- a/include/SDL_config_win32.h	Wed Jan 13 06:39:44 2010 +0000
+++ b/include/SDL_config_win32.h	Wed Jan 13 06:47:17 2010 +0000
@@ -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
--- a/include/SDL_config_wiz.h	Wed Jan 13 06:39:44 2010 +0000
+++ b/include/SDL_config_wiz.h	Wed Jan 13 06:47:17 2010 +0000
@@ -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
--- a/src/SDL.c	Wed Jan 13 06:39:44 2010 +0000
+++ b/src/SDL.c	Wed Jan 13 06:47:17 2010 +0000
@@ -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_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 @@
     }
 #endif
 
+    if (SDL_AssertionsInit() < 0) {
+        return -1;
+    }
+
     /* Clear the error message */
     SDL_ClearError();
 
@@ -171,6 +180,21 @@
     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 @@
     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 @@
     /* Uninstall any parachute signal handlers */
     SDL_UninstallParachute();
 
+    SDL_AssertionsQuit();
+
 #if !SDL_THREADS_DISABLED && SDL_THREAD_PTH
     pth_kill();
 #endif
--- /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: */
+
--- a/src/video/cocoa/SDL_cocoavideo.m	Wed Jan 13 06:39:44 2010 +0000
+++ b/src/video/cocoa/SDL_cocoavideo.m	Wed Jan 13 06:47:17 2010 +0000
@@ -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: */
+