1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2018 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, _exit(), etc. */
40 #include <stdio.h>
41 #include <stdlib.h>
42 #if ! defined(__WINRT__)
43 #include <unistd.h>
44 #endif
45 #endif
46 
47 #if defined(__EMSCRIPTEN__)
48 #include <emscripten.h>
49 #endif
50 
51 
52 static SDL_assert_state SDLCALL
53 SDL_PromptAssertion(const SDL_assert_data *data, void *userdata);
54 
55 /*
56  * We keep all triggered assertions in a singly-linked list so we can
57  *  generate a report later.
58  */
59 static SDL_assert_data *triggered_assertions = NULL;
60 
61 #ifndef SDL_THREADS_DISABLED
62 static SDL_mutex *assertion_mutex = NULL;
63 #endif
64 
65 static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
66 static void *assertion_userdata = NULL;
67 
68 #ifdef __GNUC__
69 static void
70 debug_print(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
71 #endif
72 
73 static void
debug_print(const char * fmt,...)74 debug_print(const char *fmt, ...)
75 {
76     va_list ap;
77     va_start(ap, fmt);
78     SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap);
79     va_end(ap);
80 }
81 
82 
SDL_AddAssertionToReport(SDL_assert_data * data)83 static void SDL_AddAssertionToReport(SDL_assert_data *data)
84 {
85     /* (data) is always a static struct defined with the assert macros, so
86        we don't have to worry about copying or allocating them. */
87     data->trigger_count++;
88     if (data->trigger_count == 1) {  /* not yet added? */
89         data->next = triggered_assertions;
90         triggered_assertions = data;
91     }
92 }
93 
94 
SDL_GenerateAssertionReport(void)95 static void SDL_GenerateAssertionReport(void)
96 {
97     const SDL_assert_data *item = triggered_assertions;
98 
99     /* only do this if the app hasn't assigned an assertion handler. */
100     if ((item != NULL) && (assertion_handler != SDL_PromptAssertion)) {
101         debug_print("\n\nSDL assertion report.\n");
102         debug_print("All SDL assertions between last init/quit:\n\n");
103 
104         while (item != NULL) {
105             debug_print(
106                 "'%s'\n"
107                 "    * %s (%s:%d)\n"
108                 "    * triggered %u time%s.\n"
109                 "    * always ignore: %s.\n",
110                 item->condition, item->function, item->filename,
111                 item->linenum, item->trigger_count,
112                 (item->trigger_count == 1) ? "" : "s",
113                 item->always_ignore ? "yes" : "no");
114             item = item->next;
115         }
116         debug_print("\n");
117 
118         SDL_ResetAssertionReport();
119     }
120 }
121 
122 
SDL_ExitProcess(int exitcode)123 static SDL_NORETURN void SDL_ExitProcess(int exitcode)
124 {
125 #ifdef __WIN32__
126     ExitProcess(exitcode);
127 #elif defined(__EMSCRIPTEN__)
128     emscripten_cancel_main_loop();  /* this should "kill" the app. */
129     emscripten_force_exit(exitcode);  /* this should "kill" the app. */
130     exit(exitcode);
131 #else
132     _exit(exitcode);
133 #endif
134 }
135 
136 
SDL_AbortAssertion(void)137 static SDL_NORETURN void SDL_AbortAssertion(void)
138 {
139     SDL_Quit();
140     SDL_ExitProcess(42);
141 }
142 
143 
144 static SDL_assert_state SDLCALL
SDL_PromptAssertion(const SDL_assert_data * data,void * userdata)145 SDL_PromptAssertion(const SDL_assert_data *data, void *userdata)
146 {
147 #ifdef __WIN32__
148     #define ENDLINE "\r\n"
149 #else
150     #define ENDLINE "\n"
151 #endif
152 
153     const char *envr;
154     SDL_assert_state state = SDL_ASSERTION_ABORT;
155     SDL_Window *window;
156     SDL_MessageBoxData messagebox;
157     SDL_MessageBoxButtonData buttons[] = {
158         {   0,  SDL_ASSERTION_RETRY,            "Retry" },
159         {   0,  SDL_ASSERTION_BREAK,            "Break" },
160         {   0,  SDL_ASSERTION_ABORT,            "Abort" },
161         {   SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
162                 SDL_ASSERTION_IGNORE,           "Ignore" },
163         {   SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
164                 SDL_ASSERTION_ALWAYS_IGNORE,    "Always Ignore" }
165     };
166     char *message;
167     int selected;
168 
169     (void) userdata;  /* unused in default handler. */
170 
171     message = SDL_stack_alloc(char, SDL_MAX_LOG_MESSAGE);
172     if (!message) {
173         /* Uh oh, we're in real trouble now... */
174         return SDL_ASSERTION_ABORT;
175     }
176     SDL_snprintf(message, SDL_MAX_LOG_MESSAGE,
177                  "Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE
178                     "  '%s'",
179                  data->function, data->filename, data->linenum,
180                  data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
181                  data->condition);
182 
183     debug_print("\n\n%s\n\n", message);
184 
185     /* let env. variable override, so unit tests won't block in a GUI. */
186     envr = SDL_getenv("SDL_ASSERT");
187     if (envr != NULL) {
188         SDL_stack_free(message);
189 
190         if (SDL_strcmp(envr, "abort") == 0) {
191             return SDL_ASSERTION_ABORT;
192         } else if (SDL_strcmp(envr, "break") == 0) {
193             return SDL_ASSERTION_BREAK;
194         } else if (SDL_strcmp(envr, "retry") == 0) {
195             return SDL_ASSERTION_RETRY;
196         } else if (SDL_strcmp(envr, "ignore") == 0) {
197             return SDL_ASSERTION_IGNORE;
198         } else if (SDL_strcmp(envr, "always_ignore") == 0) {
199             return SDL_ASSERTION_ALWAYS_IGNORE;
200         } else {
201             return SDL_ASSERTION_ABORT;  /* oh well. */
202         }
203     }
204 
205     /* Leave fullscreen mode, if possible (scary!) */
206     window = SDL_GetFocusWindow();
207     if (window) {
208         if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
209             SDL_MinimizeWindow(window);
210         } else {
211             /* !!! FIXME: ungrab the input if we're not fullscreen? */
212             /* No need to mess with the window */
213             window = NULL;
214         }
215     }
216 
217     /* Show a messagebox if we can, otherwise fall back to stdio */
218     SDL_zero(messagebox);
219     messagebox.flags = SDL_MESSAGEBOX_WARNING;
220     messagebox.window = window;
221     messagebox.title = "Assertion Failed";
222     messagebox.message = message;
223     messagebox.numbuttons = SDL_arraysize(buttons);
224     messagebox.buttons = buttons;
225 
226     if (SDL_ShowMessageBox(&messagebox, &selected) == 0) {
227         if (selected == -1) {
228             state = SDL_ASSERTION_IGNORE;
229         } else {
230             state = (SDL_assert_state)selected;
231         }
232     }
233 
234     else
235     {
236 #if defined(__EMSCRIPTEN__)
237         /* This is nasty, but we can't block on a custom UI. */
238         for ( ; ; ) {
239             SDL_bool okay = SDL_TRUE;
240             char *buf = (char *) EM_ASM_INT({
241                 var str =
242                     Pointer_stringify($0) + '\n\n' +
243                     'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
244                 var reply = window.prompt(str, "i");
245                 if (reply === null) {
246                     reply = "i";
247                 }
248                 return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL);
249             }, message);
250 
251             if (SDL_strcmp(buf, "a") == 0) {
252                 state = SDL_ASSERTION_ABORT;
253             /* (currently) no break functionality on Emscripten
254             } else if (SDL_strcmp(buf, "b") == 0) {
255                 state = SDL_ASSERTION_BREAK; */
256             } else if (SDL_strcmp(buf, "r") == 0) {
257                 state = SDL_ASSERTION_RETRY;
258             } else if (SDL_strcmp(buf, "i") == 0) {
259                 state = SDL_ASSERTION_IGNORE;
260             } else if (SDL_strcmp(buf, "A") == 0) {
261                 state = SDL_ASSERTION_ALWAYS_IGNORE;
262             } else {
263                 okay = SDL_FALSE;
264             }
265             free(buf);
266 
267             if (okay) {
268                 break;
269             }
270         }
271 #elif defined(HAVE_STDIO_H)
272         /* this is a little hacky. */
273         for ( ; ; ) {
274             char buf[32];
275             fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
276             fflush(stderr);
277             if (fgets(buf, sizeof (buf), stdin) == NULL) {
278                 break;
279             }
280 
281             if (SDL_strncmp(buf, "a", 1) == 0) {
282                 state = SDL_ASSERTION_ABORT;
283                 break;
284             } else if (SDL_strncmp(buf, "b", 1) == 0) {
285                 state = SDL_ASSERTION_BREAK;
286                 break;
287             } else if (SDL_strncmp(buf, "r", 1) == 0) {
288                 state = SDL_ASSERTION_RETRY;
289                 break;
290             } else if (SDL_strncmp(buf, "i", 1) == 0) {
291                 state = SDL_ASSERTION_IGNORE;
292                 break;
293             } else if (SDL_strncmp(buf, "A", 1) == 0) {
294                 state = SDL_ASSERTION_ALWAYS_IGNORE;
295                 break;
296             }
297         }
298 #endif /* HAVE_STDIO_H */
299     }
300 
301     /* Re-enter fullscreen mode */
302     if (window) {
303         SDL_RestoreWindow(window);
304     }
305 
306     SDL_stack_free(message);
307 
308     return state;
309 }
310 
311 
312 SDL_assert_state
SDL_ReportAssertion(SDL_assert_data * data,const char * func,const char * file,int line)313 SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file,
314                     int line)
315 {
316     SDL_assert_state state = SDL_ASSERTION_IGNORE;
317     static int assertion_running = 0;
318 
319 #ifndef SDL_THREADS_DISABLED
320     static SDL_SpinLock spinlock = 0;
321     SDL_AtomicLock(&spinlock);
322     if (assertion_mutex == NULL) { /* never called SDL_Init()? */
323         assertion_mutex = SDL_CreateMutex();
324         if (assertion_mutex == NULL) {
325             SDL_AtomicUnlock(&spinlock);
326             return SDL_ASSERTION_IGNORE;   /* oh well, I guess. */
327         }
328     }
329     SDL_AtomicUnlock(&spinlock);
330 
331     if (SDL_LockMutex(assertion_mutex) < 0) {
332         return SDL_ASSERTION_IGNORE;   /* oh well, I guess. */
333     }
334 #endif
335 
336     /* doing this because Visual C is upset over assigning in the macro. */
337     if (data->trigger_count == 0) {
338         data->function = func;
339         data->filename = file;
340         data->linenum = line;
341     }
342 
343     SDL_AddAssertionToReport(data);
344 
345     assertion_running++;
346     if (assertion_running > 1) {   /* assert during assert! Abort. */
347         if (assertion_running == 2) {
348             SDL_AbortAssertion();
349         } else if (assertion_running == 3) {  /* Abort asserted! */
350             SDL_ExitProcess(42);
351         } else {
352             while (1) { /* do nothing but spin; what else can you do?! */ }
353         }
354     }
355 
356     if (!data->always_ignore) {
357         state = assertion_handler(data, assertion_userdata);
358     }
359 
360     switch (state)
361     {
362         case SDL_ASSERTION_ABORT:
363             SDL_AbortAssertion();
364             return SDL_ASSERTION_IGNORE;  /* shouldn't return, but oh well. */
365 
366         case SDL_ASSERTION_ALWAYS_IGNORE:
367             state = SDL_ASSERTION_IGNORE;
368             data->always_ignore = 1;
369             break;
370 
371         case SDL_ASSERTION_IGNORE:
372         case SDL_ASSERTION_RETRY:
373         case SDL_ASSERTION_BREAK:
374             break;  /* macro handles these. */
375     }
376 
377     assertion_running--;
378 
379 #ifndef SDL_THREADS_DISABLED
380     SDL_UnlockMutex(assertion_mutex);
381 #endif
382 
383     return state;
384 }
385 
386 
SDL_AssertionsQuit(void)387 void SDL_AssertionsQuit(void)
388 {
389     SDL_GenerateAssertionReport();
390 #ifndef SDL_THREADS_DISABLED
391     if (assertion_mutex != NULL) {
392         SDL_DestroyMutex(assertion_mutex);
393         assertion_mutex = NULL;
394     }
395 #endif
396 }
397 
SDL_SetAssertionHandler(SDL_AssertionHandler handler,void * userdata)398 void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
399 {
400     if (handler != NULL) {
401         assertion_handler = handler;
402         assertion_userdata = userdata;
403     } else {
404         assertion_handler = SDL_PromptAssertion;
405         assertion_userdata = NULL;
406     }
407 }
408 
SDL_GetAssertionReport(void)409 const SDL_assert_data *SDL_GetAssertionReport(void)
410 {
411     return triggered_assertions;
412 }
413 
SDL_ResetAssertionReport(void)414 void SDL_ResetAssertionReport(void)
415 {
416     SDL_assert_data *next = NULL;
417     SDL_assert_data *item;
418     for (item = triggered_assertions; item != NULL; item = next) {
419         next = (SDL_assert_data *) item->next;
420         item->always_ignore = SDL_FALSE;
421         item->trigger_count = 0;
422         item->next = NULL;
423     }
424 
425     triggered_assertions = NULL;
426 }
427 
SDL_GetDefaultAssertionHandler(void)428 SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
429 {
430     return SDL_PromptAssertion;
431 }
432 
SDL_GetAssertionHandler(void ** userdata)433 SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
434 {
435     if (userdata != NULL) {
436         *userdata = assertion_userdata;
437     }
438     return assertion_handler;
439 }
440 
441 /* vi: set ts=4 sw=4 expandtab: */
442