SDL: Handle lazy IO property allocation failures

From 7f3d0638dc1689ac32ed4ef2f8e9fda4f156bf78 Mon Sep 17 00:00:00 2001
From: alistanis <[EMAIL REDACTED]>
Date: Thu, 24 Sep 2026 12:40:51 -0400
Subject: [PATCH] Handle lazy IO property allocation failures

Only publish stream property groups after all initializer calls succeed.
Destroy incomplete groups and allow retry so failed property insertion
cannot orphan a dynamic stream's existing buffer.

Add an isolated allocation failure regression covering close, retry, and
explicit ownership transfer.
---
 src/io/SDL_iostream.c | 49 ++++++++++++++++++++++++++-----------------
 1 file changed, 30 insertions(+), 19 deletions(-)

diff --git a/src/io/SDL_iostream.c b/src/io/SDL_iostream.c
index d8a152ed9b76b..fb78fc2b2e970 100644
--- a/src/io/SDL_iostream.c
+++ b/src/io/SDL_iostream.c
@@ -49,7 +49,7 @@
 // IOStreams have various Properties. The first time SDL_GetIOProperties() is
 //  called, it creates the SDL_PropertiesID and then uses this function
 //  interface to fill in the appropriate props for the stream on-demand.
-typedef void (*SetIOPropertiesFn)(SDL_PropertiesID props, void *userdata);
+typedef bool (*SetIOPropertiesFn)(SDL_PropertiesID props, void *userdata);
 
 struct SDL_IOStream
 {
@@ -437,10 +437,10 @@ static bool SDLCALL windows_file_close(void *userdata)
     return result;
 }
 
-static void windows_setioprops(SDL_PropertiesID props, void *userdata)
+static bool windows_setioprops(SDL_PropertiesID props, void *userdata)
 {
     const IOStreamWindowsData *iodata = (const IOStreamWindowsData *) userdata;
-    SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, iodata->h);
+    return SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, iodata->h);
 }
 
 SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose)
@@ -651,10 +651,10 @@ static bool SDLCALL fd_close(void *userdata)
     return status;
 }
 
-static void fd_setioprops(SDL_PropertiesID props, void *userdata)
+static bool fd_setioprops(SDL_PropertiesID props, void *userdata)
 {
     const IOStreamFDData *iodata = (const IOStreamFDData *) userdata;
-    SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, iodata->fd);
+    return SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, iodata->fd);
 }
 
 SDL_IOStream *SDL_IOFromFD(int fd, bool autoclose)
@@ -852,12 +852,12 @@ static bool SDLCALL stdio_close(void *userdata)
     return status;
 }
 
-static void stdio_setioprops(SDL_PropertiesID props, void *userdata)
+static bool stdio_setioprops(SDL_PropertiesID props, void *userdata)
 {
     const IOStreamStdioData *iodata = (const IOStreamStdioData *) userdata;
     FILE *fp = iodata->fp;
-    SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_STDIO_FILE_POINTER, fp);
-    SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, fileno(fp));
+    return SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_STDIO_FILE_POINTER, fp) &&
+           SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, fileno(fp));
 }
 
 SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
@@ -983,13 +983,16 @@ static bool SDLCALL mem_close(void *userdata)
     return true;
 }
 
-static void mem_setioprops(SDL_PropertiesID props, void *userdata)
+static bool mem_setioprops(SDL_PropertiesID props, void *userdata)
 {
     IOStreamMemData *iodata = (IOStreamMemData *) userdata;
-    SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, iodata->base);
-    SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, iodata->size);
+    if (!SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, iodata->base) ||
+        !SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, iodata->size)) {
+        return false;
+    }
     SDL_assert(iodata->props == 0);
     iodata->props = props;
+    return true;
 }
 
 // Functions to create SDL_IOStream structures from various data sources
@@ -1010,9 +1013,9 @@ static bool IsStdioFileADirectory(FILE *f)
 #endif
 
 #ifdef SDL_PLATFORM_ANDROID
-static void android_setioprops(SDL_PropertiesID props, void *userdata)
+static bool android_setioprops(SDL_PropertiesID props, void *userdata)
 {
-    SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER, userdata);
+    return SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER, userdata);
 }
 #endif
 
@@ -1344,12 +1347,15 @@ static bool SDLCALL dynamic_mem_close(void *userdata)
     return true;
 }
 
-static void dynamic_mem_setioprops(SDL_PropertiesID props, void *userdata)
+static bool dynamic_mem_setioprops(SDL_PropertiesID props, void *userdata)
 {
     IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata;
-    SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, iodata->data.base);
+    if (!SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, iodata->data.base)) {
+        return false;
+    }
     SDL_assert(iodata->props == 0);
     iodata->props = props;
+    return true;
 }
 
 SDL_IOStream *SDL_IOFromDynamicMem(void)
@@ -1563,11 +1569,16 @@ SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context)
     }
 
     if (context->props == 0) {
-        context->props = SDL_CreateProperties();
-        if (context->props && context->setioprops) {
-            context->setioprops(context->props, context->userdata);
-            context->setioprops = NULL;  // NULL so we don't try to set props again, just in case.
+        SDL_PropertiesID props = SDL_CreateProperties();
+        if (!props) {
+            return 0;
+        }
+        if (context->setioprops && !context->setioprops(props, context->userdata)) {
+            SDL_DestroyProperties(props);
+            return 0;
         }
+        context->props = props;
+        context->setioprops = NULL;  // NULL so we don't try to set props again, just in case.
     }
     return context->props;
 }