1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
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.
8
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:
12
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.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_PLATFORM_WINDOWS)
24#include "core/windows/SDL_windows.h"
25#endif
26
27#include "SDL_assert_c.h"
28#include "video/SDL_sysvideo.h"
29
30#if defined(SDL_PLATFORM_WINDOWS)
31#ifndef WS_OVERLAPPEDWINDOW
32#define WS_OVERLAPPEDWINDOW 0
33#endif
34#endif
35
36#ifdef SDL_PLATFORM_EMSCRIPTEN
37 #include <emscripten.h>
38 // older Emscriptens don't have this, but we need to for wasm64 compatibility.
39 #ifndef MAIN_THREAD_EM_ASM_PTR
40 #ifdef __wasm64__
41 #error You need to upgrade your Emscripten compiler to support wasm64
42 #else
43 #define MAIN_THREAD_EM_ASM_PTR MAIN_THREAD_EM_ASM_INT
44 #endif
45 #endif
46#endif
47
48// The size of the stack buffer to use for rendering assert messages.
49#define SDL_MAX_ASSERT_MESSAGE_STACK 256
50
51static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata);
52
53/*
54 * We keep all triggered assertions in a singly-linked list so we can
55 * generate a report later.
56 */
57static SDL_AssertData *triggered_assertions = NULL;
58
59#ifndef SDL_THREADS_DISABLED
60static SDL_Mutex *assertion_mutex = NULL;
61#endif
62
63static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
64static void *assertion_userdata = NULL;
65
66#ifdef __GNUC__
67static void debug_print(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
68#endif
69
70static void debug_print(const char *fmt, ...)
71{
72 va_list ap;
73 va_start(ap, fmt);
74 SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap);
75 va_end(ap);
76}
77
78static void SDL_AddAssertionToReport(SDL_AssertData *data)
79{
80 /* (data) is always a static struct defined with the assert macros, so
81 we don't have to worry about copying or allocating them. */
82 data->trigger_count++;
83 if (data->trigger_count == 1) { // not yet added?
84 data->next = triggered_assertions;
85 triggered_assertions = data;
86 }
87}
88
89#if defined(SDL_PLATFORM_WINDOWS)
90#define ENDLINE "\r\n"
91#else
92#define ENDLINE "\n"
93#endif
94
95static int SDL_RenderAssertMessage(char *buf, size_t buf_len, const SDL_AssertData *data)
96{
97 return SDL_snprintf(buf, buf_len,
98 "Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE " '%s'",
99 data->function, data->filename, data->linenum,
100 data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
101 data->condition);
102}
103
104static void SDL_GenerateAssertionReport(void)
105{
106 const SDL_AssertData *item = triggered_assertions;
107
108 // only do this if the app hasn't assigned an assertion handler.
109 if ((item) && (assertion_handler != SDL_PromptAssertion)) {
110 debug_print("\n\nSDL assertion report.\n");
111 debug_print("All SDL assertions between last init/quit:\n\n");
112
113 while (item) {
114 debug_print(
115 "'%s'\n"
116 " * %s (%s:%d)\n"
117 " * triggered %u time%s.\n"
118 " * always ignore: %s.\n",
119 item->condition, item->function, item->filename,
120 item->linenum, item->trigger_count,
121 (item->trigger_count == 1) ? "" : "s",
122 item->always_ignore ? "yes" : "no");
123 item = item->next;
124 }
125 debug_print("\n");
126
127 SDL_ResetAssertionReport();
128 }
129}
130
131/* This is not declared in any header, although it is shared between some
132 parts of SDL, because we don't want anything calling it without an
133 extremely good reason. */
134#ifdef __WATCOMC__
135extern void SDL_ExitProcess(int exitcode);
136#pragma aux SDL_ExitProcess aborts;
137#endif
138extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
139
140#ifdef __WATCOMC__
141static void SDL_AbortAssertion(void);
142#pragma aux SDL_AbortAssertion aborts;
143#endif
144static SDL_NORETURN void SDL_AbortAssertion(void)
145{
146 SDL_Quit();
147 SDL_ExitProcess(42);
148}
149
150static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata)
151{
152 SDL_AssertState state = SDL_ASSERTION_ABORT;
153 SDL_Window *window;
154 SDL_MessageBoxData messagebox;
155 SDL_MessageBoxButtonData buttons[] = {
156 { 0, SDL_ASSERTION_RETRY, "Retry" },
157 { 0, SDL_ASSERTION_BREAK, "Break" },
158 { 0, SDL_ASSERTION_ABORT, "Abort" },
159 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
160 SDL_ASSERTION_IGNORE, "Ignore" },
161 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
162 SDL_ASSERTION_ALWAYS_IGNORE, "Always Ignore" }
163 };
164 int selected;
165
166 char stack_buf[SDL_MAX_ASSERT_MESSAGE_STACK];
167 char *message = stack_buf;
168 size_t buf_len = sizeof(stack_buf);
169 int len;
170
171 (void)userdata; // unused in default handler.
172
173 // Assume the output will fit...
174 len = SDL_RenderAssertMessage(message, buf_len, data);
175
176 // .. and if it didn't, try to allocate as much room as we actually need.
177 if (len >= (int)buf_len) {
178 if (SDL_size_add_check_overflow(len, 1, &buf_len)) {
179 message = (char *)SDL_malloc(buf_len);
180 if (message) {
181 len = SDL_RenderAssertMessage(message, buf_len, data);
182 } else {
183 message = stack_buf;
184 }
185 }
186 }
187
188 // Something went very wrong
189 if (len < 0) {
190 if (message != stack_buf) {
191 SDL_free(message);
192 }
193 return SDL_ASSERTION_ABORT;
194 }
195
196 debug_print("\n\n%s\n\n", message);
197
198 // let env. variable override, so unit tests won't block in a GUI.
199 const char *hint = SDL_GetHint(SDL_HINT_ASSERT);
200 if (hint) {
201 if (message != stack_buf) {
202 SDL_free(message);
203 }
204
205 if (SDL_strcmp(hint, "abort") == 0) {
206 return SDL_ASSERTION_ABORT;
207 } else if (SDL_strcmp(hint, "break") == 0) {
208 return SDL_ASSERTION_BREAK;
209 } else if (SDL_strcmp(hint, "retry") == 0) {
210 return SDL_ASSERTION_RETRY;
211 } else if (SDL_strcmp(hint, "ignore") == 0) {
212 return SDL_ASSERTION_IGNORE;
213 } else if (SDL_strcmp(hint, "always_ignore") == 0) {
214 return SDL_ASSERTION_ALWAYS_IGNORE;
215 } else {
216 return SDL_ASSERTION_ABORT; // oh well.
217 }
218 }
219
220 // Leave fullscreen mode, if possible (scary!)
221 window = SDL_GetToplevelForKeyboardFocus();
222 if (window) {
223 if (window->fullscreen_exclusive) {
224 SDL_MinimizeWindow(window);
225 } else {
226 // !!! FIXME: ungrab the input if we're not fullscreen?
227 // No need to mess with the window
228 window = NULL;
229 }
230 }
231
232 // Show a messagebox if we can, otherwise fall back to stdio
233 SDL_zero(messagebox);
234 messagebox.flags = SDL_MESSAGEBOX_WARNING;
235 messagebox.window = window;
236 messagebox.title = "Assertion Failed";
237 messagebox.message = message;
238 messagebox.numbuttons = SDL_arraysize(buttons);
239 messagebox.buttons = buttons;
240
241 if (SDL_ShowMessageBox(&messagebox, &selected)) {
242 if (selected == -1) {
243 state = SDL_ASSERTION_IGNORE;
244 } else {
245 state = (SDL_AssertState)selected;
246 }
247 } else {
248#ifdef SDL_PLATFORM_PRIVATE_ASSERT
249 SDL_PRIVATE_PROMPTASSERTION();
250#elif defined(SDL_PLATFORM_EMSCRIPTEN)
251 // This is nasty, but we can't block on a custom UI.
252 for (;;) {
253 bool okay = true;
254 /* *INDENT-OFF* */ // clang-format off
255 char *buf = (char *) MAIN_THREAD_EM_ASM_PTR({
256 var str =
257 UTF8ToString($0) + '\n\n' +
258 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
259 var reply = window.prompt(str, "i");
260 if (reply === null) {
261 reply = "i";
262 }
263 return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL);
264 }, message);
265 /* *INDENT-ON* */ // clang-format on
266
267 if (SDL_strcmp(buf, "a") == 0) {
268 state = SDL_ASSERTION_ABORT;
269#if 0 // (currently) no break functionality on Emscripten
270 } else if (SDL_strcmp(buf, "b") == 0) {
271 state = SDL_ASSERTION_BREAK;
272#endif
273 } else if (SDL_strcmp(buf, "r") == 0) {
274 state = SDL_ASSERTION_RETRY;
275 } else if (SDL_strcmp(buf, "i") == 0) {
276 state = SDL_ASSERTION_IGNORE;
277 } else if (SDL_strcmp(buf, "A") == 0) {
278 state = SDL_ASSERTION_ALWAYS_IGNORE;
279 } else {
280 okay = false;
281 }
282 free(buf); // This should NOT be SDL_free()
283
284 if (okay) {
285 break;
286 }
287 }
288#elif defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_3DS)
289 // this is a little hacky.
290 for (;;) {
291 char buf[32];
292 (void)fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
293 (void)fflush(stderr);
294 if (fgets(buf, sizeof(buf), stdin) == NULL) {
295 break;
296 }
297
298 if (SDL_strncmp(buf, "a", 1) == 0) {
299 state = SDL_ASSERTION_ABORT;
300 break;
301 } else if (SDL_strncmp(buf, "b", 1) == 0) {
302 state = SDL_ASSERTION_BREAK;
303 break;
304 } else if (SDL_strncmp(buf, "r", 1) == 0) {
305 state = SDL_ASSERTION_RETRY;
306 break;
307 } else if (SDL_strncmp(buf, "i", 1) == 0) {
308 state = SDL_ASSERTION_IGNORE;
309 break;
310 } else if (SDL_strncmp(buf, "A", 1) == 0) {
311 state = SDL_ASSERTION_ALWAYS_IGNORE;
312 break;
313 }
314 }
315#else
316 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Assertion Failed", message, window);
317#endif // HAVE_STDIO_H
318 }
319
320 // Re-enter fullscreen mode
321 if (window) {
322 SDL_RestoreWindow(window);
323 }
324
325 if (message != stack_buf) {
326 SDL_free(message);
327 }
328
329 return state;
330}
331
332SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, const char *file, int line)
333{
334 SDL_AssertState state = SDL_ASSERTION_IGNORE;
335 static int assertion_running = 0;
336
337#ifndef SDL_THREADS_DISABLED
338 static SDL_SpinLock spinlock = 0;
339 SDL_LockSpinlock(&spinlock);
340 if (!assertion_mutex) { // never called SDL_Init()?
341 assertion_mutex = SDL_CreateMutex();
342 if (!assertion_mutex) {
343 SDL_UnlockSpinlock(&spinlock);
344 return SDL_ASSERTION_IGNORE; // oh well, I guess.
345 }
346 }
347 SDL_UnlockSpinlock(&spinlock);
348
349 SDL_LockMutex(assertion_mutex);
350#endif // !SDL_THREADS_DISABLED
351
352 // doing this because Visual C is upset over assigning in the macro.
353 if (data->trigger_count == 0) {
354 data->function = func;
355 data->filename = file;
356 data->linenum = line;
357 }
358
359 SDL_AddAssertionToReport(data);
360
361 assertion_running++;
362 if (assertion_running > 1) { // assert during assert! Abort.
363 if (assertion_running == 2) {
364 SDL_AbortAssertion();
365 } else if (assertion_running == 3) { // Abort asserted!
366 SDL_ExitProcess(42);
367 } else {
368 while (1) { // do nothing but spin; what else can you do?!
369 }
370 }
371 }
372
373 if (!data->always_ignore) {
374 state = assertion_handler(data, assertion_userdata);
375 }
376
377 switch (state) {
378 case SDL_ASSERTION_ALWAYS_IGNORE:
379 state = SDL_ASSERTION_IGNORE;
380 data->always_ignore = true;
381 break;
382
383 case SDL_ASSERTION_IGNORE:
384 case SDL_ASSERTION_RETRY:
385 case SDL_ASSERTION_BREAK:
386 break; // macro handles these.
387
388 case SDL_ASSERTION_ABORT:
389 SDL_AbortAssertion();
390 // break; ...shouldn't return, but oh well.
391 }
392
393 assertion_running--;
394
395#ifndef SDL_THREADS_DISABLED
396 SDL_UnlockMutex(assertion_mutex);
397#endif
398
399 return state;
400}
401
402void SDL_AssertionsQuit(void)
403{
404#if SDL_ASSERT_LEVEL > 0
405 SDL_GenerateAssertionReport();
406#ifndef SDL_THREADS_DISABLED
407 if (assertion_mutex) {
408 SDL_DestroyMutex(assertion_mutex);
409 assertion_mutex = NULL;
410 }
411#endif
412#endif // SDL_ASSERT_LEVEL > 0
413}
414
415void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
416{
417 if (handler != NULL) {
418 assertion_handler = handler;
419 assertion_userdata = userdata;
420 } else {
421 assertion_handler = SDL_PromptAssertion;
422 assertion_userdata = NULL;
423 }
424}
425
426const SDL_AssertData *SDL_GetAssertionReport(void)
427{
428 return triggered_assertions;
429}
430
431void SDL_ResetAssertionReport(void)
432{
433 SDL_AssertData *next = NULL;
434 SDL_AssertData *item;
435 for (item = triggered_assertions; item; item = next) {
436 next = (SDL_AssertData *)item->next;
437 item->always_ignore = false;
438 item->trigger_count = 0;
439 item->next = NULL;
440 }
441
442 triggered_assertions = NULL;
443}
444
445SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
446{
447 return SDL_PromptAssertion;
448}
449
450SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
451{
452 if (userdata) {
453 *userdata = assertion_userdata;
454 }
455 return assertion_handler;
456}
457