SDL: dynapi: Optionally log every call into the SDL API.

From 17322e2be649eefaf6a02e2fcaefc444722ccfbb Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 10 Oct 2022 13:07:52 -0400
Subject: [PATCH] dynapi: Optionally log every call into the SDL API.

This will only log things going through dynapi, which means it won't
do anything if dynapi is disabled for a given build, but also things
that call the `*_REAL` version of an API won't log either (which is
to say, if an internal piece of SDL calls a public API, it won't log
it, but if an application calls that same entry point, it will).

Since this just inserts a different function pointer, unless you
explicitly request this at runtime, it won't add any overhead, and,
of course, the entire thing can be turned off with a single #define
so it doesn't even add extra unused code to the shared library if
the kill switch is flipped.
---
 src/dynapi/SDL_dynapi.c | 96 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 92 insertions(+), 4 deletions(-)

diff --git a/src/dynapi/SDL_dynapi.c b/src/dynapi/SDL_dynapi.c
index d3f7f612b7d7..13b0a555113c 100644
--- a/src/dynapi/SDL_dynapi.c
+++ b/src/dynapi/SDL_dynapi.c
@@ -50,7 +50,6 @@
 
 static void SDL_InitDynamicAPI(void);
 
-
 /* BE CAREFUL CALLING ANY SDL CODE IN HERE, IT WILL BLOW UP.
    Even self-contained stuff might call SDL_Error and break everything. */
 
@@ -191,6 +190,79 @@ SDL_DYNAPI_VARARGS(,,)
 #error Write me.
 #endif
 
+#define ENABLE_SDL_CALL_LOGGING 1
+#if ENABLE_SDL_CALL_LOGGING
+static int SDLCALL SDL_SetError_LOGSDLCALLS(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) {
+    char buf[512]; /* !!! FIXME: dynamic allocation */ \
+    va_list ap;
+    SDL_Log_REAL("SDL2CALL SDL_SetError");
+    va_start(ap, fmt);
+    SDL_vsnprintf_REAL(buf, sizeof (buf), fmt, ap);
+    va_end(ap);
+    return SDL_SetError_REAL("%s", buf);
+}
+static int SDLCALL SDL_sscanf_LOGSDLCALLS(const char *buf, SDL_SCANF_FORMAT_STRING const char *fmt, ...) {
+    int retval;
+    va_list ap;
+    SDL_Log_REAL("SDL2CALL SDL_sscanf");
+    va_start(ap, fmt);
+    retval = SDL_vsscanf_REAL(buf, fmt, ap);
+    va_end(ap);
+    return retval;
+}
+static int SDLCALL SDL_snprintf_LOGSDLCALLS(SDL_OUT_Z_CAP(maxlen) char *buf, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) {
+    int retval;
+    va_list ap;
+    SDL_Log_REAL("SDL2CALL SDL_snprintf");
+    va_start(ap, fmt);
+    retval = SDL_vsnprintf_REAL(buf, maxlen, fmt, ap);
+    va_end(ap);
+    return retval;
+}
+static int SDLCALL SDL_asprintf_LOGSDLCALLS(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) {
+    int retval;
+    va_list ap;
+    SDL_Log_REAL("SDL2CALL SDL_asprintf");
+    va_start(ap, fmt);
+    retval = SDL_vasprintf_REAL(strp, fmt, ap);
+    va_end(ap);
+    return retval;
+}
+static void SDLCALL SDL_Log_LOGSDLCALLS(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) {
+    va_list ap;
+    SDL_Log_REAL("SDL2CALL SDL_Log");
+    va_start(ap, fmt);
+    SDL_LogMessageV_REAL(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap); \
+    va_end(ap);
+}
+static void SDLCALL SDL_LogMessage_LOGSDLCALLS(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) {
+    va_list ap;
+    SDL_Log_REAL("SDL2CALL SDL_LogMessage");
+    va_start(ap, fmt);
+    SDL_LogMessageV_REAL(category, priority, fmt, ap);
+    va_end(ap);
+}
+#define SDL_DYNAPI_VARARGS_LOGFN_LOGSDLCALLS(logname, prio) \
+    static void SDLCALL SDL_Log##logname##_LOGSDLCALLS(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) { \
+        va_list ap; va_start(ap, fmt); \
+        SDL_Log_REAL("SDL2CALL SDL_Log%s", #logname); \
+        SDL_LogMessageV_REAL(category, SDL_LOG_PRIORITY_##prio, fmt, ap); \
+        va_end(ap); \
+    }
+SDL_DYNAPI_VARARGS_LOGFN_LOGSDLCALLS(Verbose, VERBOSE)
+SDL_DYNAPI_VARARGS_LOGFN_LOGSDLCALLS(Debug, DEBUG)
+SDL_DYNAPI_VARARGS_LOGFN_LOGSDLCALLS(Info, INFO)
+SDL_DYNAPI_VARARGS_LOGFN_LOGSDLCALLS(Warn, WARN)
+SDL_DYNAPI_VARARGS_LOGFN_LOGSDLCALLS(Error, ERROR)
+SDL_DYNAPI_VARARGS_LOGFN_LOGSDLCALLS(Critical, CRITICAL)
+#define SDL_DYNAPI_PROC(rc,fn,params,args,ret) \
+    rc SDLCALL fn##_LOGSDLCALLS params { SDL_Log_REAL("SDL2CALL %s", #fn); ret fn##_REAL args; }
+#define SDL_DYNAPI_PROC_NO_VARARGS 1
+#include "SDL_dynapi_procs.h"
+#undef SDL_DYNAPI_PROC
+#undef SDL_DYNAPI_PROC_NO_VARARGS
+#endif
+
 /* we make this a static function so we can call the correct one without the
    system's dynamic linker resolving to the wrong version of this. */
 static Sint32
@@ -206,9 +278,25 @@ initialize_jumptable(Uint32 apiver, void *table, Uint32 tablesize)
     }
 
     /* Init our jump table first. */
-    #define SDL_DYNAPI_PROC(rc,fn,params,args,ret) jump_table.fn = fn##_REAL;
-    #include "SDL_dynapi_procs.h"
-    #undef SDL_DYNAPI_PROC
+    #if ENABLE_SDL_CALL_LOGGING
+    {
+        const char *env = SDL_getenv_REAL("SDL_DYNAPI_LOG_CALLS");
+        const SDL_bool log_calls = (env && SDL_atoi_REAL(env));
+        if (log_calls) {
+            #define SDL_DYNAPI_PROC(rc,fn,params,args,ret) jump_table.fn = fn##_LOGSDLCALLS;
+            #include "SDL_dynapi_procs.h"
+            #undef SDL_DYNAPI_PROC
+        } else {
+            #define SDL_DYNAPI_PROC(rc,fn,params,args,ret) jump_table.fn = fn##_REAL;
+            #include "SDL_dynapi_procs.h"
+            #undef SDL_DYNAPI_PROC
+        }
+    }
+    #else
+        #define SDL_DYNAPI_PROC(rc,fn,params,args,ret) jump_table.fn = fn##_REAL;
+        #include "SDL_dynapi_procs.h"
+        #undef SDL_DYNAPI_PROC
+    #endif
 
     /* Then the external table... */
     if (output_jump_table != &jump_table) {