src/SDL_assert.c
author Ryan C. Gordon <icculus@icculus.org>
Thu, 21 Apr 2016 03:16:44 -0400
changeset 11729 d1ce8396c356
parent 11626 2eaf345a2a30
child 11811 5d94cb6b24d3
permissions -rw-r--r--
Initial shot at a renderer target for Apple's Metal API.

This isn't complete, but is enough to run testsprite2. It's currently
Mac-only; with a little work to figure out how to properly glue in a Metal
layer to a UIView, this will likely work on iOS, too.

This is only wired up to the configure script right now, and disabled by
default. CMake and Xcode still need their bits filled in as appropriate.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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
    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 
    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 
    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 
   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 
   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
   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
   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 
   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 
   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 
   409 const SDL_assert_data *SDL_GetAssertionReport(void)
   410 {
   411     return triggered_assertions;
   412 }
   413 
   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 
   428 SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
   429 {
   430     return SDL_PromptAssertion;
   431 }
   432 
   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: */