123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #include "./SDL_internal.h"
- #if defined(__WIN32__) || defined(__GDK__)
- #include "core/windows/SDL_windows.h"
- #endif
- #include "SDL.h"
- #include "SDL_atomic.h"
- #include "SDL_messagebox.h"
- #include "SDL_video.h"
- #include "SDL_assert.h"
- #include "SDL_assert_c.h"
- #include "video/SDL_sysvideo.h"
- #if defined(__WIN32__) || defined(__GDK__)
- #ifndef WS_OVERLAPPEDWINDOW
- #define WS_OVERLAPPEDWINDOW 0
- #endif
- #else /* fprintf, etc. */
- #include <stdio.h>
- #include <stdlib.h>
- #endif
- #if defined(__EMSCRIPTEN__)
- #include <emscripten.h>
- /* older Emscriptens don't have this, but we need to for wasm64 compatibility. */
- #ifndef MAIN_THREAD_EM_ASM_PTR
- #ifdef __wasm64__
- #error You need to upgrade your Emscripten compiler to support wasm64
- #else
- #define MAIN_THREAD_EM_ASM_PTR MAIN_THREAD_EM_ASM_INT
- #endif
- #endif
- #endif
- /* The size of the stack buffer to use for rendering assert messages. */
- #define SDL_MAX_ASSERT_MESSAGE_STACK 256
- static SDL_assert_state SDLCALL 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 *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_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. */
- data->trigger_count++;
- if (data->trigger_count == 1) { /* not yet added? */
- data->next = triggered_assertions;
- triggered_assertions = data;
- }
- }
- #if defined(__WIN32__) || defined(__GDK__)
- #define ENDLINE "\r\n"
- #else
- #define ENDLINE "\n"
- #endif
- static int SDL_RenderAssertMessage(char *buf, size_t buf_len, const SDL_assert_data *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_assert_data *item = triggered_assertions;
- /* only do this if the app hasn't assigned an assertion handler. */
- 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();
- }
- }
- /* This is not declared in any header, although it is shared between some
- parts of SDL, because we don't want anything calling it without an
- extremely good reason. */
- #if defined(__WATCOMC__)
- extern void SDL_ExitProcess(int exitcode);
- #pragma aux SDL_ExitProcess aborts;
- #endif
- extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
- #if defined(__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_assert_state SDLCALL SDL_PromptAssertion(const SDL_assert_data *data, void *userdata)
- {
- const char *envr;
- SDL_assert_state 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; /* unused in default handler. */
- /* Assume the output will fit... */
- len = SDL_RenderAssertMessage(message, buf_len, data);
- /* .. and if it didn't, try to allocate as much room as we actually need. */
- if (len >= (int)buf_len) {
- if (SDL_size_add_overflow(len, 1, &buf_len) == 0) {
- message = (char *)SDL_malloc(buf_len);
- if (message) {
- len = SDL_RenderAssertMessage(message, buf_len, data);
- } else {
- message = stack_buf;
- }
- }
- }
- /* Something went very wrong */
- if (len < 0) {
- if (message != stack_buf) {
- SDL_free(message);
- }
- return SDL_ASSERTION_ABORT;
- }
- debug_print("\n\n%s\n\n", message);
- /* let env. variable override, so unit tests won't block in a GUI. */
- envr = SDL_getenv("SDL_ASSERT");
- if (envr) {
- if (message != stack_buf) {
- SDL_free(message);
- }
- 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 = NULL;
- }
- }
- /* Show a messagebox if we can, otherwise fall back to stdio */
- 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 (SDL_ShowMessageBox(&messagebox, &selected) == 0) {
- if (selected == -1) {
- state = SDL_ASSERTION_IGNORE;
- } else {
- state = (SDL_assert_state)selected;
- }
- } else {
- #if defined(__EMSCRIPTEN__)
- /* This is nasty, but we can't block on a custom UI. */
- for (;;) {
- SDL_bool okay = SDL_TRUE;
- /* *INDENT-OFF* */ /* clang-format off */
- char *buf = (char *) MAIN_THREAD_EM_ASM_PTR({
- var str =
- UTF8ToString($0) + '\n\n' +
- 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
- var reply = window.prompt(str, "i");
- if (reply === null) {
- reply = "i";
- }
- return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL);
- }, message);
- /* *INDENT-ON* */ /* clang-format on */
- if (SDL_strcmp(buf, "a") == 0) {
- state = SDL_ASSERTION_ABORT;
- #if 0 /* (currently) no break functionality on Emscripten */
- } else if (SDL_strcmp(buf, "b") == 0) {
- state = SDL_ASSERTION_BREAK;
- #endif
- } else if (SDL_strcmp(buf, "r") == 0) {
- state = SDL_ASSERTION_RETRY;
- } else if (SDL_strcmp(buf, "i") == 0) {
- state = SDL_ASSERTION_IGNORE;
- } else if (SDL_strcmp(buf, "A") == 0) {
- state = SDL_ASSERTION_ALWAYS_IGNORE;
- } else {
- okay = SDL_FALSE;
- }
- free(buf);
- if (okay) {
- break;
- }
- }
- #elif defined(HAVE_STDIO_H) && !defined(__3DS__)
- /* this is a little hacky. */
- 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
- SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Assertion Failed", message, window);
- #endif /* HAVE_STDIO_H */
- }
- /* Re-enter fullscreen mode */
- if (window) {
- SDL_RestoreWindow(window);
- }
- if (message != stack_buf) {
- SDL_free(message);
- }
- return state;
- }
- SDL_assert_state SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file, int line)
- {
- SDL_assert_state state = SDL_ASSERTION_IGNORE;
- static int assertion_running = 0;
- #ifndef SDL_THREADS_DISABLED
- static SDL_SpinLock spinlock = 0;
- SDL_AtomicLock(&spinlock);
- if (!assertion_mutex) { /* never called SDL_Init()? */
- assertion_mutex = SDL_CreateMutex();
- if (!assertion_mutex) {
- SDL_AtomicUnlock(&spinlock);
- return SDL_ASSERTION_IGNORE; /* oh well, I guess. */
- }
- }
- SDL_AtomicUnlock(&spinlock);
- SDL_LockMutex(assertion_mutex);
- #endif /* !SDL_THREADS_DISABLED */
- /* 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);
- 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_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. */
- case SDL_ASSERTION_ABORT:
- SDL_AbortAssertion();
- /*break; ...shouldn't return, but oh well. */
- }
- 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 /* SDL_ASSERT_LEVEL > 0 */
- }
- 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 *next = NULL;
- SDL_assert_data *item;
- for (item = triggered_assertions; item; item = next) {
- next = (SDL_assert_data *)item->next;
- item->always_ignore = SDL_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;
- }
- /* vi: set ts=4 sw=4 expandtab: */
|