2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
21 #include "./SDL_internal.h"
23 #if defined(__WIN32__)
24 #include "core/windows/SDL_windows.h"
28 #include "SDL_atomic.h"
29 #include "SDL_messagebox.h"
30 #include "SDL_video.h"
31 #include "SDL_assert.h"
32 #include "SDL_assert_c.h"
33 #include "video/SDL_sysvideo.h"
36 #ifndef WS_OVERLAPPEDWINDOW
37 #define WS_OVERLAPPEDWINDOW 0
39 #else /* fprintf, etc. */
44 #if defined(__EMSCRIPTEN__)
45 #include <emscripten.h>
49 static SDL_assert_state SDLCALL
50 SDL_PromptAssertion(const SDL_assert_data *data, void *userdata);
53 * We keep all triggered assertions in a singly-linked list so we can
54 * generate a report later.
56 static SDL_assert_data *triggered_assertions = NULL;
58 #ifndef SDL_THREADS_DISABLED
59 static SDL_mutex *assertion_mutex = NULL;
62 static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
63 static void *assertion_userdata = NULL;
67 debug_print(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
71 debug_print(const char *fmt, ...)
75 SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap);
80 static void SDL_AddAssertionToReport(SDL_assert_data *data)
82 /* (data) is always a static struct defined with the assert macros, so
83 we don't have to worry about copying or allocating them. */
84 data->trigger_count++;
85 if (data->trigger_count == 1) { /* not yet added? */
86 data->next = triggered_assertions;
87 triggered_assertions = data;
92 static void SDL_GenerateAssertionReport(void)
94 const SDL_assert_data *item = triggered_assertions;
96 /* only do this if the app hasn't assigned an assertion handler. */
97 if ((item != NULL) && (assertion_handler != SDL_PromptAssertion)) {
98 debug_print("\n\nSDL assertion report.\n");
99 debug_print("All SDL assertions between last init/quit:\n\n");
101 while (item != NULL) {
105 " * triggered %u time%s.\n"
106 " * always ignore: %s.\n",
107 item->condition, item->function, item->filename,
108 item->linenum, item->trigger_count,
109 (item->trigger_count == 1) ? "" : "s",
110 item->always_ignore ? "yes" : "no");
115 SDL_ResetAssertionReport();
120 /* This is not declared in any header, although it is shared between some
121 parts of SDL, because we don't want anything calling it without an
122 extremely good reason. */
123 #if defined(__WATCOMC__)
124 extern void SDL_ExitProcess(int exitcode);
125 #pragma aux SDL_ExitProcess aborts;
127 extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
130 #if defined(__WATCOMC__)
131 static void SDL_AbortAssertion (void);
132 #pragma aux SDL_AbortAssertion aborts;
134 static SDL_NORETURN void SDL_AbortAssertion(void)
141 static SDL_assert_state SDLCALL
142 SDL_PromptAssertion(const SDL_assert_data *data, void *userdata)
145 #define ENDLINE "\r\n"
151 SDL_assert_state state = SDL_ASSERTION_ABORT;
153 SDL_MessageBoxData messagebox;
154 SDL_MessageBoxButtonData buttons[] = {
155 { 0, SDL_ASSERTION_RETRY, "Retry" },
156 { 0, SDL_ASSERTION_BREAK, "Break" },
157 { 0, SDL_ASSERTION_ABORT, "Abort" },
158 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
159 SDL_ASSERTION_IGNORE, "Ignore" },
160 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
161 SDL_ASSERTION_ALWAYS_IGNORE, "Always Ignore" }
166 (void) userdata; /* unused in default handler. */
168 /* !!! FIXME: why is this using SDL_stack_alloc and not just "char message[SDL_MAX_LOG_MESSAGE];" ? */
169 message = SDL_stack_alloc(char, SDL_MAX_LOG_MESSAGE);
171 /* Uh oh, we're in real trouble now... */
172 return SDL_ASSERTION_ABORT;
174 SDL_snprintf(message, SDL_MAX_LOG_MESSAGE,
175 "Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE
177 data->function, data->filename, data->linenum,
178 data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
181 debug_print("\n\n%s\n\n", message);
183 /* let env. variable override, so unit tests won't block in a GUI. */
184 envr = SDL_getenv("SDL_ASSERT");
186 SDL_stack_free(message);
188 if (SDL_strcmp(envr, "abort") == 0) {
189 return SDL_ASSERTION_ABORT;
190 } else if (SDL_strcmp(envr, "break") == 0) {
191 return SDL_ASSERTION_BREAK;
192 } else if (SDL_strcmp(envr, "retry") == 0) {
193 return SDL_ASSERTION_RETRY;
194 } else if (SDL_strcmp(envr, "ignore") == 0) {
195 return SDL_ASSERTION_IGNORE;
196 } else if (SDL_strcmp(envr, "always_ignore") == 0) {
197 return SDL_ASSERTION_ALWAYS_IGNORE;
199 return SDL_ASSERTION_ABORT; /* oh well. */
203 /* Leave fullscreen mode, if possible (scary!) */
204 window = SDL_GetFocusWindow();
206 if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
207 SDL_MinimizeWindow(window);
209 /* !!! FIXME: ungrab the input if we're not fullscreen? */
210 /* No need to mess with the window */
215 /* Show a messagebox if we can, otherwise fall back to stdio */
216 SDL_zero(messagebox);
217 messagebox.flags = SDL_MESSAGEBOX_WARNING;
218 messagebox.window = window;
219 messagebox.title = "Assertion Failed";
220 messagebox.message = message;
221 messagebox.numbuttons = SDL_arraysize(buttons);
222 messagebox.buttons = buttons;
224 if (SDL_ShowMessageBox(&messagebox, &selected) == 0) {
225 if (selected == -1) {
226 state = SDL_ASSERTION_IGNORE;
228 state = (SDL_assert_state)selected;
234 #if defined(__EMSCRIPTEN__)
235 /* This is nasty, but we can't block on a custom UI. */
237 SDL_bool okay = SDL_TRUE;
238 char *buf = (char *) EM_ASM_INT({
240 UTF8ToString($0) + '\n\n' +
241 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
242 var reply = window.prompt(str, "i");
243 if (reply === null) {
246 return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL);
249 if (SDL_strcmp(buf, "a") == 0) {
250 state = SDL_ASSERTION_ABORT;
251 /* (currently) no break functionality on Emscripten
252 } else if (SDL_strcmp(buf, "b") == 0) {
253 state = SDL_ASSERTION_BREAK; */
254 } else if (SDL_strcmp(buf, "r") == 0) {
255 state = SDL_ASSERTION_RETRY;
256 } else if (SDL_strcmp(buf, "i") == 0) {
257 state = SDL_ASSERTION_IGNORE;
258 } else if (SDL_strcmp(buf, "A") == 0) {
259 state = SDL_ASSERTION_ALWAYS_IGNORE;
269 #elif defined(HAVE_STDIO_H)
270 /* this is a little hacky. */
273 fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
275 if (fgets(buf, sizeof (buf), stdin) == NULL) {
279 if (SDL_strncmp(buf, "a", 1) == 0) {
280 state = SDL_ASSERTION_ABORT;
282 } else if (SDL_strncmp(buf, "b", 1) == 0) {
283 state = SDL_ASSERTION_BREAK;
285 } else if (SDL_strncmp(buf, "r", 1) == 0) {
286 state = SDL_ASSERTION_RETRY;
288 } else if (SDL_strncmp(buf, "i", 1) == 0) {
289 state = SDL_ASSERTION_IGNORE;
291 } else if (SDL_strncmp(buf, "A", 1) == 0) {
292 state = SDL_ASSERTION_ALWAYS_IGNORE;
296 #endif /* HAVE_STDIO_H */
299 /* Re-enter fullscreen mode */
301 SDL_RestoreWindow(window);
304 SDL_stack_free(message);
311 SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file,
314 SDL_assert_state state = SDL_ASSERTION_IGNORE;
315 static int assertion_running = 0;
317 #ifndef SDL_THREADS_DISABLED
318 static SDL_SpinLock spinlock = 0;
319 SDL_AtomicLock(&spinlock);
320 if (assertion_mutex == NULL) { /* never called SDL_Init()? */
321 assertion_mutex = SDL_CreateMutex();
322 if (assertion_mutex == NULL) {
323 SDL_AtomicUnlock(&spinlock);
324 return SDL_ASSERTION_IGNORE; /* oh well, I guess. */
327 SDL_AtomicUnlock(&spinlock);
329 if (SDL_LockMutex(assertion_mutex) < 0) {
330 return SDL_ASSERTION_IGNORE; /* oh well, I guess. */
334 /* doing this because Visual C is upset over assigning in the macro. */
335 if (data->trigger_count == 0) {
336 data->function = func;
337 data->filename = file;
338 data->linenum = line;
341 SDL_AddAssertionToReport(data);
344 if (assertion_running > 1) { /* assert during assert! Abort. */
345 if (assertion_running == 2) {
346 SDL_AbortAssertion();
347 } else if (assertion_running == 3) { /* Abort asserted! */
350 while (1) { /* do nothing but spin; what else can you do?! */ }
354 if (!data->always_ignore) {
355 state = assertion_handler(data, assertion_userdata);
360 case SDL_ASSERTION_ALWAYS_IGNORE:
361 state = SDL_ASSERTION_IGNORE;
362 data->always_ignore = 1;
365 case SDL_ASSERTION_IGNORE:
366 case SDL_ASSERTION_RETRY:
367 case SDL_ASSERTION_BREAK:
368 break; /* macro handles these. */
370 case SDL_ASSERTION_ABORT:
371 SDL_AbortAssertion();
372 /*break; ...shouldn't return, but oh well. */
377 #ifndef SDL_THREADS_DISABLED
378 SDL_UnlockMutex(assertion_mutex);
385 void SDL_AssertionsQuit(void)
387 SDL_GenerateAssertionReport();
388 #ifndef SDL_THREADS_DISABLED
389 if (assertion_mutex != NULL) {
390 SDL_DestroyMutex(assertion_mutex);
391 assertion_mutex = NULL;
396 void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
398 if (handler != NULL) {
399 assertion_handler = handler;
400 assertion_userdata = userdata;
402 assertion_handler = SDL_PromptAssertion;
403 assertion_userdata = NULL;
407 const SDL_assert_data *SDL_GetAssertionReport(void)
409 return triggered_assertions;
412 void SDL_ResetAssertionReport(void)
414 SDL_assert_data *next = NULL;
415 SDL_assert_data *item;
416 for (item = triggered_assertions; item != NULL; item = next) {
417 next = (SDL_assert_data *) item->next;
418 item->always_ignore = SDL_FALSE;
419 item->trigger_count = 0;
423 triggered_assertions = NULL;
426 SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
428 return SDL_PromptAssertion;
431 SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
433 if (userdata != NULL) {
434 *userdata = assertion_userdata;
436 return assertion_handler;
439 /* vi: set ts=4 sw=4 expandtab: */