#include "SDL_internal.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "core/windows/SDL_windows.h"
#endif
#include "SDL_assert_c.h"
#if defined(SDL_PLATFORM_WINDOWS)
#ifndef WS_OVERLAPPEDWINDOW
#define WS_OVERLAPPEDWINDOW 0
#endif
#endif
#ifdef SDL_PLATFORM_EMSCRIPTEN
#include <emscripten.h>
#endif
#define SDL_MAX_ASSERT_MESSAGE_STACK 256
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata);
static SDL_AssertData *triggered_assertions = NULL;
#ifndef SDL_THREADS_DISABLED
static SDL_Mutex *assertion_mutex = NULL;
#endif
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, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap);
va_end(ap);
}
static void SDL_AddAssertionToReport(SDL_AssertData *data)
{
data->trigger_count++;
if (data->trigger_count == 1) {
data->next = triggered_assertions;
triggered_assertions = data;
}
}
#if defined(SDL_PLATFORM_WINDOWS)
#define ENDLINE "\r\n"
#else
#define ENDLINE "\n"
#endif
static int SDL_RenderAssertMessage(char *buf, size_t buf_len, const SDL_AssertData *data)
{
return SDL_snprintf(buf, buf_len,
"Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE " '%s'",
data->function, data->filename, data->linenum,
data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
data->condition);
}
static void SDL_GenerateAssertionReport(void)
{
const SDL_AssertData *item = triggered_assertions;
if ((item) && (assertion_handler != SDL_PromptAssertion)) {
debug_print("\n\nSDL assertion report.\n");
debug_print("All SDL assertions between last init/quit:\n\n");
while (item) {
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();
}
}
#ifdef __WATCOMC__
extern void SDL_ExitProcess(int exitcode);
#pragma aux SDL_ExitProcess aborts;
#endif
extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
#ifdef __WATCOMC__
static void SDL_AbortAssertion(void);
#pragma aux SDL_AbortAssertion aborts;
#endif
static SDL_NORETURN void SDL_AbortAssertion(void)
{
SDL_Quit();
SDL_ExitProcess(42);
}
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata)
{
SDL_AssertState state = SDL_ASSERTION_ABORT;
SDL_Window *window;
SDL_MessageBoxData messagebox;
SDL_MessageBoxButtonData buttons[] = {
{ 0, SDL_ASSERTION_RETRY, "Retry" },
{ 0, SDL_ASSERTION_BREAK, "Break" },
{ 0, SDL_ASSERTION_ABORT, "Abort" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
SDL_ASSERTION_IGNORE, "Ignore" },
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
SDL_ASSERTION_ALWAYS_IGNORE, "Always Ignore" }
};
int selected;
char stack_buf[SDL_MAX_ASSERT_MESSAGE_STACK];
char *message = stack_buf;
size_t buf_len = sizeof(stack_buf);
int len;
(void)userdata;
len = SDL_RenderAssertMessage(message, buf_len, data);
if (len >= (int)buf_len) {
if (SDL_size_add_check_overflow(len, 1, &buf_len)) {
message = (char *)SDL_malloc(buf_len);
if (message) {
len = SDL_RenderAssertMessage(message, buf_len, data);
} else {
message = stack_buf;
}
}
}
if (len < 0) {
if (message != stack_buf) {
SDL_free(message);
}
return SDL_ASSERTION_ABORT;
}
debug_print("\n\n%s\n\n", message);
const char *hint = SDL_GetHint(SDL_HINT_ASSERT);
if (hint) {
if (message != stack_buf) {
SDL_free(message);
}
if (SDL_strcmp(hint, "abort") == 0) {
return SDL_ASSERTION_ABORT;
} else if (SDL_strcmp(hint, "break") == 0) {
return SDL_ASSERTION_BREAK;
} else if (SDL_strcmp(hint, "retry") == 0) {
return SDL_ASSERTION_RETRY;
} else if (SDL_strcmp(hint, "ignore") == 0) {
return SDL_ASSERTION_IGNORE;
} else if (SDL_strcmp(hint, "always_ignore") == 0) {
return SDL_ASSERTION_ALWAYS_IGNORE;
} else {
return SDL_ASSERTION_ABORT;
}
}
SDL_zero(messagebox);
messagebox.flags = SDL_MESSAGEBOX_WARNING;
messagebox.window = window;
messagebox.title = "Assertion Failed";
messagebox.message = message;
messagebox.numbuttons = SDL_arraysize(buttons);
messagebox.buttons = buttons;
if (false) {
if (selected == -1) {
state = SDL_ASSERTION_IGNORE;
} else {
state = (SDL_AssertState)selected;
}
} else {
#ifdef SDL_PLATFORM_PRIVATE_ASSERT
SDL_PRIVATE_PROMPTASSERTION();
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
for (;;) {
bool okay = true;
int reply = MAIN_THREAD_EM_ASM_INT({
var str =
UTF8ToString($0) + '\n\n' +
'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
var reply = window.prompt(str, "i");
if (reply === null) {
reply = "i";
}
return reply.length === 1 ? reply.charCodeAt(0) : -1;
}, message);
switch (reply) {
case 'a':
state = SDL_ASSERTION_ABORT;
break;
#if 0
case 'b':
state = SDL_ASSERTION_BREAK;
break;
#endif
case 'r':
state = SDL_ASSERTION_RETRY;
break;
case 'i':
state = SDL_ASSERTION_IGNORE;
break;
case 'A':
state = SDL_ASSERTION_ALWAYS_IGNORE;
break;
default:
okay = false;
break;
}
if (okay) {
break;
}
}
#elif defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_3DS)
for (;;) {
char buf[32];
(void)fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
(void)fflush(stderr);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
break;
}
if (SDL_strncmp(buf, "a", 1) == 0) {
state = SDL_ASSERTION_ABORT;
break;
} else if (SDL_strncmp(buf, "b", 1) == 0) {
state = SDL_ASSERTION_BREAK;
break;
} else if (SDL_strncmp(buf, "r", 1) == 0) {
state = SDL_ASSERTION_RETRY;
break;
} else if (SDL_strncmp(buf, "i", 1) == 0) {
state = SDL_ASSERTION_IGNORE;
break;
} else if (SDL_strncmp(buf, "A", 1) == 0) {
state = SDL_ASSERTION_ALWAYS_IGNORE;
break;
}
}
#else
#endif
}
if (window) {
}
if (message != stack_buf) {
SDL_free(message);
}
return state;
}
SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, const char *file, int line)
{
SDL_AssertState state = SDL_ASSERTION_IGNORE;
static int assertion_running = 0;
#ifndef SDL_THREADS_DISABLED
static SDL_SpinLock spinlock = 0;
SDL_LockSpinlock(&spinlock);
if (!assertion_mutex) {
assertion_mutex = SDL_CreateMutex();
if (!assertion_mutex) {
SDL_UnlockSpinlock(&spinlock);
return SDL_ASSERTION_IGNORE;
}
}
SDL_UnlockSpinlock(&spinlock);
SDL_LockMutex(assertion_mutex);
#endif
if (data->trigger_count == 0) {
data->function = func;
data->filename = file;
data->linenum = line;
}
SDL_AddAssertionToReport(data);
assertion_running++;
if (assertion_running > 1) {
if (assertion_running == 2) {
SDL_AbortAssertion();
} else if (assertion_running == 3) {
SDL_ExitProcess(42);
} else {
while (1) {
}
}
}
if (!data->always_ignore) {
state = assertion_handler(data, assertion_userdata);
}
switch (state) {
case SDL_ASSERTION_ALWAYS_IGNORE:
state = SDL_ASSERTION_IGNORE;
data->always_ignore = true;
break;
case SDL_ASSERTION_IGNORE:
case SDL_ASSERTION_RETRY:
case SDL_ASSERTION_BREAK:
break;
case SDL_ASSERTION_ABORT:
SDL_AbortAssertion();
}
assertion_running--;
#ifndef SDL_THREADS_DISABLED
SDL_UnlockMutex(assertion_mutex);
#endif
return state;
}
void SDL_AssertionsQuit(void)
{
#if SDL_ASSERT_LEVEL > 0
SDL_GenerateAssertionReport();
#ifndef SDL_THREADS_DISABLED
if (assertion_mutex) {
SDL_DestroyMutex(assertion_mutex);
assertion_mutex = NULL;
}
#endif
#endif
}
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_AssertData *SDL_GetAssertionReport(void)
{
return triggered_assertions;
}
void SDL_ResetAssertionReport(void)
{
SDL_AssertData *next = NULL;
SDL_AssertData *item;
for (item = triggered_assertions; item; item = next) {
next = (SDL_AssertData *)item->next;
item->always_ignore = false;
item->trigger_count = 0;
item->next = NULL;
}
triggered_assertions = NULL;
}
SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
{
return SDL_PromptAssertion;
}
SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
{
if (userdata) {
*userdata = assertion_userdata;
}
return assertion_handler;
}