Change std:vector to eina_array
[platform/upstream/SDL.git] / src / SDL_assert.c
1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 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: */