1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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(__WIN32__)
24#include "core/windows/SDL_windows.h"
25#endif
26
27#include "SDL.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"
34
35#ifdef __WIN32__
36#ifndef WS_OVERLAPPEDWINDOW
37#define WS_OVERLAPPEDWINDOW 0
38#endif
39#else /* fprintf, etc. */
40#include <stdio.h>
41#include <stdlib.h>
42#endif
43
44#if defined(__EMSCRIPTEN__)
45#include <emscripten.h>
46#endif
47
48
49static SDL_assert_state SDLCALL
50SDL_PromptAssertion(const SDL_assert_data *data, void *userdata);
51
52/*
53 * We keep all triggered assertions in a singly-linked list so we can
54 * generate a report later.
55 */
56static SDL_assert_data *triggered_assertions = NULL;
57
58#ifndef SDL_THREADS_DISABLED
59static SDL_mutex *assertion_mutex = NULL;
60#endif
61
62static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
63static void *assertion_userdata = NULL;
64
65#ifdef __GNUC__
66static void
67debug_print(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
68#endif
69
70static void
71debug_print(const char *fmt, ...)
72{
73 va_list ap;
74 va_start(ap, fmt);
75 SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap);
76 va_end(ap);
77}
78
79
80static void SDL_AddAssertionToReport(SDL_assert_data *data)
81{
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;
88 }
89}
90
91
92static void SDL_GenerateAssertionReport(void)
93{
94 const SDL_assert_data *item = triggered_assertions;
95
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");
100
101 while (item != NULL) {
102 debug_print(
103 "'%s'\n"
104 " * %s (%s:%d)\n"
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");
111 item = item->next;
112 }
113 debug_print("\n");
114
115 SDL_ResetAssertionReport();
116 }
117}
118
119
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__)
124extern void SDL_ExitProcess(int exitcode);
125#pragma aux SDL_ExitProcess aborts;
126#endif
127extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
128
129
130#if defined(__WATCOMC__)
131static void SDL_AbortAssertion (void);
132#pragma aux SDL_AbortAssertion aborts;
133#endif
134static SDL_NORETURN void SDL_AbortAssertion(void)
135{
136 SDL_Quit();
137 SDL_ExitProcess(42);
138}
139
140
141static SDL_assert_state SDLCALL
142SDL_PromptAssertion(const SDL_assert_data *data, void *userdata)
143{
144#ifdef __WIN32__
145 #define ENDLINE "\r\n"
146#else
147 #define ENDLINE "\n"
148#endif
149
150 const char *envr;
151 SDL_assert_state state = SDL_ASSERTION_ABORT;
152 SDL_Window *window;
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" }
162 };
163 char *message;
164 int selected;
165
166 (void) userdata; /* unused in default handler. */
167
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);
170 if (!message) {
171 /* Uh oh, we're in real trouble now... */
172 return SDL_ASSERTION_ABORT;
173 }
174 SDL_snprintf(message, SDL_MAX_LOG_MESSAGE,
175 "Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE
176 " '%s'",
177 data->function, data->filename, data->linenum,
178 data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
179 data->condition);
180
181 debug_print("\n\n%s\n\n", message);
182
183 /* let env. variable override, so unit tests won't block in a GUI. */
184 envr = SDL_getenv("SDL_ASSERT");
185 if (envr != NULL) {
186 SDL_stack_free(message);
187
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;
198 } else {
199 return SDL_ASSERTION_ABORT; /* oh well. */
200 }
201 }
202
203 /* Leave fullscreen mode, if possible (scary!) */
204 window = SDL_GetFocusWindow();
205 if (window) {
206 if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
207 SDL_MinimizeWindow(window);
208 } else {
209 /* !!! FIXME: ungrab the input if we're not fullscreen? */
210 /* No need to mess with the window */
211 window = NULL;
212 }
213 }
214
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;
223
224 if (SDL_ShowMessageBox(&messagebox, &selected) == 0) {
225 if (selected == -1) {
226 state = SDL_ASSERTION_IGNORE;
227 } else {
228 state = (SDL_assert_state)selected;
229 }
230 }
231
232 else
233 {
234#if defined(__EMSCRIPTEN__)
235 /* This is nasty, but we can't block on a custom UI. */
236 for ( ; ; ) {
237 SDL_bool okay = SDL_TRUE;
238 char *buf = (char *) EM_ASM_INT({
239 var str =
240 UTF8ToString($0) + '\n\n' +
241 'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
242 var reply = window.prompt(str, "i");
243 if (reply === null) {
244 reply = "i";
245 }
246 return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL);
247 }, message);
248
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;
260 } else {
261 okay = SDL_FALSE;
262 }
263 free(buf);
264
265 if (okay) {
266 break;
267 }
268 }
269#elif defined(HAVE_STDIO_H)
270 /* this is a little hacky. */
271 for ( ; ; ) {
272 char buf[32];
273 fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
274 fflush(stderr);
275 if (fgets(buf, sizeof (buf), stdin) == NULL) {
276 break;
277 }
278
279 if (SDL_strncmp(buf, "a", 1) == 0) {
280 state = SDL_ASSERTION_ABORT;
281 break;
282 } else if (SDL_strncmp(buf, "b", 1) == 0) {
283 state = SDL_ASSERTION_BREAK;
284 break;
285 } else if (SDL_strncmp(buf, "r", 1) == 0) {
286 state = SDL_ASSERTION_RETRY;
287 break;
288 } else if (SDL_strncmp(buf, "i", 1) == 0) {
289 state = SDL_ASSERTION_IGNORE;
290 break;
291 } else if (SDL_strncmp(buf, "A", 1) == 0) {
292 state = SDL_ASSERTION_ALWAYS_IGNORE;
293 break;
294 }
295 }
296#endif /* HAVE_STDIO_H */
297 }
298
299 /* Re-enter fullscreen mode */
300 if (window) {
301 SDL_RestoreWindow(window);
302 }
303
304 SDL_stack_free(message);
305
306 return state;
307}
308
309
310SDL_assert_state
311SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file,
312 int line)
313{
314 SDL_assert_state state = SDL_ASSERTION_IGNORE;
315 static int assertion_running = 0;
316
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. */
325 }
326 }
327 SDL_AtomicUnlock(&spinlock);
328
329 if (SDL_LockMutex(assertion_mutex) < 0) {
330 return SDL_ASSERTION_IGNORE; /* oh well, I guess. */
331 }
332#endif
333
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;
339 }
340
341 SDL_AddAssertionToReport(data);
342
343 assertion_running++;
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! */
348 SDL_ExitProcess(42);
349 } else {
350 while (1) { /* do nothing but spin; what else can you do?! */ }
351 }
352 }
353
354 if (!data->always_ignore) {
355 state = assertion_handler(data, assertion_userdata);
356 }
357
358 switch (state)
359 {
360 case SDL_ASSERTION_ALWAYS_IGNORE:
361 state = SDL_ASSERTION_IGNORE;
362 data->always_ignore = 1;
363 break;
364
365 case SDL_ASSERTION_IGNORE:
366 case SDL_ASSERTION_RETRY:
367 case SDL_ASSERTION_BREAK:
368 break; /* macro handles these. */
369
370 case SDL_ASSERTION_ABORT:
371 SDL_AbortAssertion();
372 /*break; ...shouldn't return, but oh well. */
373 }
374
375 assertion_running--;
376
377#ifndef SDL_THREADS_DISABLED
378 SDL_UnlockMutex(assertion_mutex);
379#endif
380
381 return state;
382}
383
384
385void SDL_AssertionsQuit(void)
386{
387 SDL_GenerateAssertionReport();
388#ifndef SDL_THREADS_DISABLED
389 if (assertion_mutex != NULL) {
390 SDL_DestroyMutex(assertion_mutex);
391 assertion_mutex = NULL;
392 }
393#endif
394}
395
396void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
397{
398 if (handler != NULL) {
399 assertion_handler = handler;
400 assertion_userdata = userdata;
401 } else {
402 assertion_handler = SDL_PromptAssertion;
403 assertion_userdata = NULL;
404 }
405}
406
407const SDL_assert_data *SDL_GetAssertionReport(void)
408{
409 return triggered_assertions;
410}
411
412void SDL_ResetAssertionReport(void)
413{
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;
420 item->next = NULL;
421 }
422
423 triggered_assertions = NULL;
424}
425
426SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
427{
428 return SDL_PromptAssertion;
429}
430
431SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
432{
433 if (userdata != NULL) {
434 *userdata = assertion_userdata;
435 }
436 return assertion_handler;
437}
438
439/* vi: set ts=4 sw=4 expandtab: */
440