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