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