1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 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 
49 static SDL_assert_state SDLCALL
50 SDL_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  */
56 static SDL_assert_data *triggered_assertions = NULL;
57 
58 #ifndef SDL_THREADS_DISABLED
59 static SDL_mutex *assertion_mutex = NULL;
60 #endif
61 
62 static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
63 static void *assertion_userdata = NULL;
64 
65 #ifdef __GNUC__
66 static void
67 debug_print(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
68 #endif
69 
70 static void
debug_print(const char * fmt,...)71 debug_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 
SDL_AddAssertionToReport(SDL_assert_data * data)80 static 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 
SDL_GenerateAssertionReport(void)92 static 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__)
124 extern void SDL_ExitProcess(int exitcode);
125 #pragma aux SDL_ExitProcess aborts;
126 #endif
127 extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
128 
129 
130 #if defined(__WATCOMC__)
131 static void SDL_AbortAssertion (void);
132 #pragma aux SDL_AbortAssertion aborts;
133 #endif
SDL_AbortAssertion(void)134 static SDL_NORETURN void SDL_AbortAssertion(void)
135 {
136     SDL_Quit();
137     SDL_ExitProcess(42);
138 }
139 
140 
141 static SDL_assert_state SDLCALL
SDL_PromptAssertion(const SDL_assert_data * data,void * userdata)142 SDL_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 
310 SDL_assert_state
SDL_ReportAssertion(SDL_assert_data * data,const char * func,const char * file,int line)311 SDL_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 
SDL_AssertionsQuit(void)385 void 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 
SDL_SetAssertionHandler(SDL_AssertionHandler handler,void * userdata)396 void 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 
SDL_GetAssertionReport(void)407 const SDL_assert_data *SDL_GetAssertionReport(void)
408 {
409     return triggered_assertions;
410 }
411 
SDL_ResetAssertionReport(void)412 void 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 
SDL_GetDefaultAssertionHandler(void)426 SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
427 {
428     return SDL_PromptAssertion;
429 }
430 
SDL_GetAssertionHandler(void ** userdata)431 SDL_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