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