sdl2-compat: quit: Don't unload SDL3 if SDL_Quit() wasn't called

From b16a83848bd9c350458405ef78a53f2fe1eea00d Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Mon, 17 Mar 2025 01:01:54 -0500
Subject: [PATCH] quit: Don't unload SDL3 if SDL_Quit() wasn't called

This is a workaround for applications that don't synchronize threads
using SDL2 with the exit handler that invokes the sdl2-compat library
destructor.

Unfortunately, this may further suppress LeakSanitizer reports for
developers using sdl2-compat, but this is preferable than crashing
real apps that users care about.
---
 src/sdl2_compat.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index b5f4f9b..ca3aba8 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -220,6 +220,7 @@ do { \
 
 
 static bool WantDebugLogging = false;
+static SDL_InitState InitSDL2CompatGlobals;
 
 
 static char *
@@ -977,6 +978,17 @@ static void dllinit(void)
 static void dllquit(void) __attribute__((destructor));
 static void dllquit(void)
 {
+    /* Some misbehaving applications may not join threads calling SDL2 functions
+       before exit handlers run and invoke the library destructor, which unloads
+       SDL3. As a workaround to avoid crashing those applications, we will skip
+       unloading SDL3 if SDL was used but SDL_Quit() was never called. */
+    if (SDL3_ShouldQuit(&InitSDL2CompatGlobals)) {
+        if (WantDebugLogging) {
+            SDL2Compat_LogAtStartup("sdl2-compat: Leaking SDL3 library reference due to missing call to SDL_Quit()");
+        }
+        return;
+    }
+
     UnloadSDL3();
 }
 
@@ -1090,8 +1102,6 @@ static SDL2_SensorID SensorID3to2(SDL_SensorID id);
 
 /* Functions! */
 
-static SDL_InitState InitSDL2CompatGlobals;
-
 /**
  * Verbosity of logged events as defined in SDL_HINT_EVENT_LOGGING:
  *  - 0: (default) no logging