1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2021 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 |
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 | |
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 | |
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 |
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 |
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 |
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 | |
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 | |
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 | |
407 | const SDL_assert_data *SDL_GetAssertionReport(void) |
408 | { |
409 | return triggered_assertions; |
410 | } |
411 | |
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 | |
426 | SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void) |
427 | { |
428 | return SDL_PromptAssertion; |
429 | } |
430 | |
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 | |