1 |
/* |
2 |
SDL - Simple DirectMedia Layer |
3 |
Copyright (C) 1997-2011 Sam Lantinga |
4 |
|
5 |
This library is free software; you can redistribute it and/or |
6 |
modify it under the terms of the GNU Lesser General Public |
7 |
License as published by the Free Software Foundation; either |
8 |
version 2.1 of the License, or (at your option) any later version. |
9 |
|
10 |
This library is distributed in the hope that it will be useful, |
11 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 |
Lesser General Public License for more details. |
14 |
|
15 |
You should have received a copy of the GNU Lesser General Public |
16 |
License along with this library; if not, write to the Free Software |
17 |
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
18 |
|
19 |
Sam Lantinga |
20 |
slouken@libsdl.org |
21 |
*/ |
22 |
|
23 |
#include "SDL.h" |
24 |
#include "SDL_atomic.h" |
25 |
#include "SDL_assert.h" |
26 |
#include "SDL_assert_c.h" |
27 |
#include "video/SDL_sysvideo.h" |
28 |
|
29 |
#ifdef __WIN32__ |
30 |
#include "core/windows/SDL_windows.h" |
31 |
|
32 |
#ifndef WS_OVERLAPPEDWINDOW |
33 |
#define WS_OVERLAPPEDWINDOW 0 |
34 |
#endif |
35 |
#else /* fprintf, _exit(), etc. */ |
36 |
#include <stdio.h> |
37 |
#include <stdlib.h> |
38 |
#include <unistd.h> |
39 |
#endif |
40 |
|
41 |
static SDL_assert_state |
42 |
SDL_PromptAssertion(const SDL_assert_data *data, void *userdata); |
43 |
|
44 |
/* |
45 |
* We keep all triggered assertions in a singly-linked list so we can |
46 |
* generate a report later. |
47 |
*/ |
48 |
static SDL_assert_data assertion_list_terminator = { 0, 0, 0, 0, 0, 0, 0 }; |
49 |
static SDL_assert_data *triggered_assertions = &assertion_list_terminator; |
50 |
|
51 |
static SDL_mutex *assertion_mutex = NULL; |
52 |
static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion; |
53 |
static void *assertion_userdata = NULL; |
54 |
|
55 |
#ifdef __GNUC__ |
56 |
static void |
57 |
debug_print(const char *fmt, ...) __attribute__((format (printf, 1, 2))); |
58 |
#endif |
59 |
|
60 |
static void |
61 |
debug_print(const char *fmt, ...) |
62 |
{ |
63 |
#ifdef __WIN32__ |
64 |
/* Format into a buffer for OutputDebugStringA(). */ |
65 |
char buf[1024]; |
66 |
char *startptr; |
67 |
char *ptr; |
68 |
LPTSTR tstr; |
69 |
int len; |
70 |
va_list ap; |
71 |
va_start(ap, fmt); |
72 |
len = (int) SDL_vsnprintf(buf, sizeof (buf), fmt, ap); |
73 |
va_end(ap); |
74 |
|
75 |
/* Visual C's vsnprintf() may not null-terminate the buffer. */ |
76 |
if ((len >= sizeof (buf)) || (len < 0)) { |
77 |
buf[sizeof (buf) - 1] = '\0'; |
78 |
} |
79 |
|
80 |
/* Write it, sorting out the Unix newlines... */ |
81 |
startptr = buf; |
82 |
for (ptr = startptr; *ptr; ptr++) { |
83 |
if (*ptr == '\n') { |
84 |
*ptr = '\0'; |
85 |
tstr = WIN_UTF8ToString(startptr); |
86 |
OutputDebugString(tstr); |
87 |
SDL_free(tstr); |
88 |
OutputDebugString(TEXT("\r\n")); |
89 |
startptr = ptr+1; |
90 |
} |
91 |
} |
92 |
|
93 |
/* catch that last piece if it didn't have a newline... */ |
94 |
if (startptr != ptr) { |
95 |
tstr = WIN_UTF8ToString(startptr); |
96 |
OutputDebugString(tstr); |
97 |
SDL_free(tstr); |
98 |
} |
99 |
#else |
100 |
/* Unix has it easy. Just dump it to stderr. */ |
101 |
va_list ap; |
102 |
va_start(ap, fmt); |
103 |
vfprintf(stderr, fmt, ap); |
104 |
va_end(ap); |
105 |
fflush(stderr); |
106 |
#endif |
107 |
} |
108 |
|
109 |
|
110 |
#ifdef __WIN32__ |
111 |
static SDL_assert_state SDL_Windows_AssertChoice = SDL_ASSERTION_ABORT; |
112 |
static const SDL_assert_data *SDL_Windows_AssertData = NULL; |
113 |
|
114 |
static LRESULT CALLBACK |
115 |
SDL_Assertion_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) |
116 |
{ |
117 |
switch (msg) |
118 |
{ |
119 |
case WM_CREATE: |
120 |
{ |
121 |
/* !!! FIXME: all this code stinks. */ |
122 |
const SDL_assert_data *data = SDL_Windows_AssertData; |
123 |
char buf[1024]; |
124 |
LPTSTR tstr; |
125 |
const int w = 100; |
126 |
const int h = 25; |
127 |
const int gap = 10; |
128 |
int x = gap; |
129 |
int y = 50; |
130 |
int len; |
131 |
int i; |
132 |
static const struct { |
133 |
LPCTSTR name; |
134 |
SDL_assert_state state; |
135 |
} buttons[] = { |
136 |
{TEXT("Abort"), SDL_ASSERTION_ABORT }, |
137 |
{TEXT("Break"), SDL_ASSERTION_BREAK }, |
138 |
{TEXT("Retry"), SDL_ASSERTION_RETRY }, |
139 |
{TEXT("Ignore"), SDL_ASSERTION_IGNORE }, |
140 |
{TEXT("Always Ignore"), SDL_ASSERTION_ALWAYS_IGNORE }, |
141 |
}; |
142 |
|
143 |
len = (int) SDL_snprintf(buf, sizeof (buf), |
144 |
"Assertion failure at %s (%s:%d), triggered %u time%s:\r\n '%s'", |
145 |
data->function, data->filename, data->linenum, |
146 |
data->trigger_count, (data->trigger_count == 1) ? "" : "s", |
147 |
data->condition); |
148 |
if ((len < 0) || (len >= sizeof (buf))) { |
149 |
buf[sizeof (buf) - 1] = '\0'; |
150 |
} |
151 |
|
152 |
tstr = WIN_UTF8ToString(buf); |
153 |
CreateWindow(TEXT("STATIC"), tstr, |
154 |
WS_VISIBLE | WS_CHILD | SS_LEFT, |
155 |
x, y, 550, 100, |
156 |
hwnd, (HMENU) 1, NULL, NULL); |
157 |
SDL_free(tstr); |
158 |
y += 110; |
159 |
|
160 |
for (i = 0; i < (sizeof (buttons) / sizeof (buttons[0])); i++) { |
161 |
CreateWindow(TEXT("BUTTON"), buttons[i].name, |
162 |
WS_VISIBLE | WS_CHILD, |
163 |
x, y, w, h, |
164 |
hwnd, (HMENU) buttons[i].state, NULL, NULL); |
165 |
x += w + gap; |
166 |
} |
167 |
break; |
168 |
} |
169 |
|
170 |
case WM_COMMAND: |
171 |
SDL_Windows_AssertChoice = ((SDL_assert_state) (LOWORD(wParam))); |
172 |
SDL_Windows_AssertData = NULL; |
173 |
break; |
174 |
|
175 |
case WM_DESTROY: |
176 |
SDL_Windows_AssertData = NULL; |
177 |
break; |
178 |
} |
179 |
|
180 |
return DefWindowProc(hwnd, msg, wParam, lParam); |
181 |
} |
182 |
|
183 |
static SDL_assert_state |
184 |
SDL_PromptAssertion_windows(const SDL_assert_data *data) |
185 |
{ |
186 |
HINSTANCE hInstance = 0; /* !!! FIXME? */ |
187 |
HWND hwnd; |
188 |
MSG msg; |
189 |
WNDCLASS wc = {0}; |
190 |
|
191 |
SDL_Windows_AssertChoice = SDL_ASSERTION_ABORT; |
192 |
SDL_Windows_AssertData = data; |
193 |
|
194 |
wc.lpszClassName = TEXT("SDL_assert"); |
195 |
wc.hInstance = hInstance ; |
196 |
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); |
197 |
wc.lpfnWndProc = SDL_Assertion_WndProc; |
198 |
wc.hCursor = LoadCursor(0, IDC_ARROW); |
199 |
|
200 |
RegisterClass(&wc); |
201 |
hwnd = CreateWindow(wc.lpszClassName, TEXT("SDL assertion failure"), |
202 |
WS_OVERLAPPEDWINDOW | WS_VISIBLE, |
203 |
150, 150, 570, 260, 0, 0, hInstance, 0); |
204 |
|
205 |
while (GetMessage(&msg, NULL, 0, 0) && (SDL_Windows_AssertData != NULL)) { |
206 |
TranslateMessage(&msg); |
207 |
DispatchMessage(&msg); |
208 |
} |
209 |
|
210 |
DestroyWindow(hwnd); |
211 |
UnregisterClass(wc.lpszClassName, hInstance); |
212 |
return SDL_Windows_AssertChoice; |
213 |
} |
214 |
#endif |
215 |
|
216 |
|
217 |
static void SDL_AddAssertionToReport(SDL_assert_data *data) |
218 |
{ |
219 |
/* (data) is always a static struct defined with the assert macros, so |
220 |
we don't have to worry about copying or allocating them. */ |
221 |
if (data->next == NULL) { /* not yet added? */ |
222 |
data->next = triggered_assertions; |
223 |
triggered_assertions = data; |
224 |
} |
225 |
} |
226 |
|
227 |
|
228 |
static void SDL_GenerateAssertionReport(void) |
229 |
{ |
230 |
const SDL_assert_data *item; |
231 |
|
232 |
/* only do this if the app hasn't assigned an assertion handler. */ |
233 |
if (assertion_handler != SDL_PromptAssertion) |
234 |
return; |
235 |
|
236 |
item = SDL_GetAssertionReport(); |
237 |
if (item->condition) |
238 |
{ |
239 |
debug_print("\n\nSDL assertion report.\n"); |
240 |
debug_print("All SDL assertions between last init/quit:\n\n"); |
241 |
|
242 |
while (item->condition) { |
243 |
debug_print( |
244 |
"'%s'\n" |
245 |
" * %s (%s:%d)\n" |
246 |
" * triggered %u time%s.\n" |
247 |
" * always ignore: %s.\n", |
248 |
item->condition, item->function, item->filename, |
249 |
item->linenum, item->trigger_count, |
250 |
(item->trigger_count == 1) ? "" : "s", |
251 |
item->always_ignore ? "yes" : "no"); |
252 |
item = item->next; |
253 |
} |
254 |
debug_print("\n"); |
255 |
|
256 |
SDL_ResetAssertionReport(); |
257 |
} |
258 |
} |
259 |
|
260 |
static void SDL_ExitProcess(int exitcode) |
261 |
{ |
262 |
#ifdef __WIN32__ |
263 |
ExitProcess(42); |
264 |
#else |
265 |
_exit(42); |
266 |
#endif |
267 |
} |
268 |
|
269 |
static void SDL_AbortAssertion(void) |
270 |
{ |
271 |
SDL_Quit(); |
272 |
SDL_ExitProcess(42); |
273 |
} |
274 |
|
275 |
|
276 |
static SDL_assert_state |
277 |
SDL_PromptAssertion(const SDL_assert_data *data, void *userdata) |
278 |
{ |
279 |
const char *envr; |
280 |
SDL_assert_state state = SDL_ASSERTION_ABORT; |
281 |
SDL_Window *window; |
282 |
|
283 |
(void) userdata; /* unused in default handler. */ |
284 |
|
285 |
debug_print("\n\n" |
286 |
"Assertion failure at %s (%s:%d), triggered %u time%s:\n" |
287 |
" '%s'\n" |
288 |
"\n", |
289 |
data->function, data->filename, data->linenum, |
290 |
data->trigger_count, (data->trigger_count == 1) ? "" : "s", |
291 |
data->condition); |
292 |
|
293 |
/* let env. variable override, so unit tests won't block in a GUI. */ |
294 |
envr = SDL_getenv("SDL_ASSERT"); |
295 |
if (envr != NULL) { |
296 |
if (SDL_strcmp(envr, "abort") == 0) { |
297 |
return SDL_ASSERTION_ABORT; |
298 |
} else if (SDL_strcmp(envr, "break") == 0) { |
299 |
return SDL_ASSERTION_BREAK; |
300 |
} else if (SDL_strcmp(envr, "retry") == 0) { |
301 |
return SDL_ASSERTION_RETRY; |
302 |
} else if (SDL_strcmp(envr, "ignore") == 0) { |
303 |
return SDL_ASSERTION_IGNORE; |
304 |
} else if (SDL_strcmp(envr, "always_ignore") == 0) { |
305 |
return SDL_ASSERTION_ALWAYS_IGNORE; |
306 |
} else { |
307 |
return SDL_ASSERTION_ABORT; /* oh well. */ |
308 |
} |
309 |
} |
310 |
|
311 |
/* Leave fullscreen mode, if possible (scary!) */ |
312 |
window = SDL_GetFocusWindow(); |
313 |
if (window) { |
314 |
if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) { |
315 |
SDL_MinimizeWindow(window); |
316 |
} else { |
317 |
/* !!! FIXME: ungrab the input if we're not fullscreen? */ |
318 |
/* No need to mess with the window */ |
319 |
window = 0; |
320 |
} |
321 |
} |
322 |
|
323 |
/* platform-specific UI... */ |
324 |
|
325 |
#ifdef __WIN32__ |
326 |
state = SDL_PromptAssertion_windows(data); |
327 |
|
328 |
#elif __MACOSX__ |
329 |
/* This has to be done in an Objective-C (*.m) file, so we call out. */ |
330 |
extern SDL_assert_state SDL_PromptAssertion_cocoa(const SDL_assert_data *); |
331 |
state = SDL_PromptAssertion_cocoa(data); |
332 |
|
333 |
#else |
334 |
/* this is a little hacky. */ |
335 |
for ( ; ; ) { |
336 |
char buf[32]; |
337 |
fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : "); |
338 |
fflush(stderr); |
339 |
if (fgets(buf, sizeof (buf), stdin) == NULL) { |
340 |
break; |
341 |
} |
342 |
|
343 |
if (SDL_strcmp(buf, "a") == 0) { |
344 |
state = SDL_ASSERTION_ABORT; |
345 |
break; |
346 |
} else if (SDL_strcmp(envr, "b") == 0) { |
347 |
state = SDL_ASSERTION_BREAK; |
348 |
break; |
349 |
} else if (SDL_strcmp(envr, "r") == 0) { |
350 |
state = SDL_ASSERTION_RETRY; |
351 |
break; |
352 |
} else if (SDL_strcmp(envr, "i") == 0) { |
353 |
state = SDL_ASSERTION_IGNORE; |
354 |
break; |
355 |
} else if (SDL_strcmp(envr, "A") == 0) { |
356 |
state = SDL_ASSERTION_ALWAYS_IGNORE; |
357 |
break; |
358 |
} |
359 |
} |
360 |
#endif |
361 |
|
362 |
/* Re-enter fullscreen mode */ |
363 |
if (window) { |
364 |
SDL_RestoreWindow(window); |
365 |
} |
366 |
|
367 |
return state; |
368 |
} |
369 |
|
370 |
|
371 |
SDL_assert_state |
372 |
SDL_ReportAssertion(SDL_assert_data *data, const char *func, const char *file, |
373 |
int line) |
374 |
{ |
375 |
static int assertion_running = 0; |
376 |
static SDL_SpinLock spinlock = 0; |
377 |
SDL_assert_state state = SDL_ASSERTION_IGNORE; |
378 |
|
379 |
SDL_AtomicLock(&spinlock); |
380 |
if (assertion_mutex == NULL) { /* never called SDL_Init()? */ |
381 |
assertion_mutex = SDL_CreateMutex(); |
382 |
if (assertion_mutex == NULL) { |
383 |
SDL_AtomicUnlock(&spinlock); |
384 |
return SDL_ASSERTION_IGNORE; /* oh well, I guess. */ |
385 |
} |
386 |
} |
387 |
SDL_AtomicUnlock(&spinlock); |
388 |
|
389 |
if (SDL_LockMutex(assertion_mutex) < 0) { |
390 |
return SDL_ASSERTION_IGNORE; /* oh well, I guess. */ |
391 |
} |
392 |
|
393 |
/* doing this because Visual C is upset over assigning in the macro. */ |
394 |
if (data->trigger_count == 0) { |
395 |
data->function = func; |
396 |
data->filename = file; |
397 |
data->linenum = line; |
398 |
} |
399 |
|
400 |
SDL_AddAssertionToReport(data); |
401 |
|
402 |
data->trigger_count++; |
403 |
|
404 |
assertion_running++; |
405 |
if (assertion_running > 1) { /* assert during assert! Abort. */ |
406 |
if (assertion_running == 2) { |
407 |
SDL_AbortAssertion(); |
408 |
} else if (assertion_running == 3) { /* Abort asserted! */ |
409 |
SDL_ExitProcess(42); |
410 |
} else { |
411 |
while (1) { /* do nothing but spin; what else can you do?! */ } |
412 |
} |
413 |
} |
414 |
|
415 |
if (!data->always_ignore) { |
416 |
state = assertion_handler(data, assertion_userdata); |
417 |
} |
418 |
|
419 |
switch (state) |
420 |
{ |
421 |
case SDL_ASSERTION_ABORT: |
422 |
SDL_AbortAssertion(); |
423 |
return SDL_ASSERTION_IGNORE; /* shouldn't return, but oh well. */ |
424 |
|
425 |
case SDL_ASSERTION_ALWAYS_IGNORE: |
426 |
state = SDL_ASSERTION_IGNORE; |
427 |
data->always_ignore = 1; |
428 |
break; |
429 |
|
430 |
case SDL_ASSERTION_IGNORE: |
431 |
case SDL_ASSERTION_RETRY: |
432 |
case SDL_ASSERTION_BREAK: |
433 |
break; /* macro handles these. */ |
434 |
} |
435 |
|
436 |
assertion_running--; |
437 |
SDL_UnlockMutex(assertion_mutex); |
438 |
|
439 |
return state; |
440 |
} |
441 |
|
442 |
|
443 |
int SDL_AssertionsInit(void) |
444 |
{ |
445 |
/* this is a no-op at the moment. */ |
446 |
return 0; |
447 |
} |
448 |
|
449 |
void SDL_AssertionsQuit(void) |
450 |
{ |
451 |
SDL_GenerateAssertionReport(); |
452 |
if (assertion_mutex != NULL) { |
453 |
SDL_DestroyMutex(assertion_mutex); |
454 |
assertion_mutex = NULL; |
455 |
} |
456 |
} |
457 |
|
458 |
void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata) |
459 |
{ |
460 |
if (handler != NULL) { |
461 |
assertion_handler = handler; |
462 |
assertion_userdata = userdata; |
463 |
} else { |
464 |
assertion_handler = SDL_PromptAssertion; |
465 |
assertion_userdata = NULL; |
466 |
} |
467 |
} |
468 |
|
469 |
const SDL_assert_data *SDL_GetAssertionReport(void) |
470 |
{ |
471 |
return triggered_assertions; |
472 |
} |
473 |
|
474 |
void SDL_ResetAssertionReport(void) |
475 |
{ |
476 |
SDL_assert_data *item = triggered_assertions; |
477 |
SDL_assert_data *next = NULL; |
478 |
for (item = triggered_assertions; item->condition; item = next) { |
479 |
next = (SDL_assert_data *) item->next; |
480 |
item->always_ignore = SDL_FALSE; |
481 |
item->trigger_count = 0; |
482 |
item->next = NULL; |
483 |
} |
484 |
|
485 |
triggered_assertions = &assertion_list_terminator; |
486 |
} |
487 |
|
488 |
/* vi: set ts=4 sw=4 expandtab: */ |