SDL: Removed SDL_RWFromFP from the public API

From 05139f4a2ea1d4f848cd66769eb12e7a9098ad1a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 25 Nov 2022 10:46:26 -0800
Subject: [PATCH] Removed SDL_RWFromFP from the public API

This will blow up if the SDL library and the application have a different C runtime, which can easily happen on Windows.
---
 WhatsNew.txt                      |   1 +
 docs/README-migration.md          | 113 +++++++++++++++++++++++++++++
 include/SDL_rwops.h               |  36 ---------
 src/dynapi/SDL_dynapi_overrides.h |   1 -
 src/dynapi/SDL_dynapi_procs.h     |   6 --
 src/file/SDL_rwops.c              |  49 ++++++-------
 test/testautomation_rwops.c       | 117 +-----------------------------
 7 files changed, 138 insertions(+), 185 deletions(-)

diff --git a/WhatsNew.txt b/WhatsNew.txt
index e196247f11c4..3d90d8f53b82 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -8,3 +8,4 @@ This is a list of major changes in SDL's version history.
 General:
 * M_PI is no longer defined in SDL_stdinc.h, now the symbols SDL_M_PIl (double) and SDL_M_PIf (float) are available
 * SDL_GetWindowWMInfo() returns a standard int result code instead of SDL_bool, and takes SDL_SYSWM_CURRENT_VERSION as a new third parameter
+* SDL_RWFromFP has been removed from the API
diff --git a/docs/README-migration.md b/docs/README-migration.md
index 6e83c8a88f24..3e165d18c1ce 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -4,6 +4,119 @@ This guide provides useful information for migrating applications from SDL 2.0 t
 
 We have provided a handy Python script to automate some of this work for you [link to script], and details on the changes are organized by SDL 2.0 header below.
 
+## SDL_rwops.h
+
+SDL_RWFromFP has been removed from the API, due to issues when the SDL library uses a different C runtime from the application.
+
+You can implement this in your own code easily:
+```c
+#include <stdio.h>
+
+
+static Sint64 SDLCALL
+stdio_size(SDL_RWops * context)
+{
+    Sint64 pos, size;
+
+    pos = SDL_RWseek(context, 0, RW_SEEK_CUR);
+    if (pos < 0) {
+        return -1;
+    }
+    size = SDL_RWseek(context, 0, RW_SEEK_END);
+
+    SDL_RWseek(context, pos, RW_SEEK_SET);
+    return size;
+}
+
+static Sint64 SDLCALL
+stdio_seek(SDL_RWops * context, Sint64 offset, int whence)
+{
+    int stdiowhence;
+
+    switch (whence) {
+    case RW_SEEK_SET:
+        stdiowhence = SEEK_SET;
+        break;
+    case RW_SEEK_CUR:
+        stdiowhence = SEEK_CUR;
+        break;
+    case RW_SEEK_END:
+        stdiowhence = SEEK_END;
+        break;
+    default:
+        return SDL_SetError("Unknown value for 'whence'");
+    }
+
+    if (fseek((FILE *)context->hidden.stdio.fp, (fseek_off_t)offset, stdiowhence) == 0) {
+        Sint64 pos = ftell((FILE *)context->hidden.stdio.fp);
+        if (pos < 0) {
+            return SDL_SetError("Couldn't get stream offset");
+        }
+        return pos;
+    }
+    return SDL_Error(SDL_EFSEEK);
+}
+
+static size_t SDLCALL
+stdio_read(SDL_RWops * context, void *ptr, size_t size, size_t maxnum)
+{
+    size_t nread;
+
+    nread = fread(ptr, size, maxnum, (FILE *)context->hidden.stdio.fp);
+    if (nread == 0 && ferror((FILE *)context->hidden.stdio.fp)) {
+        SDL_Error(SDL_EFREAD);
+    }
+    return nread;
+}
+
+static size_t SDLCALL
+stdio_write(SDL_RWops * context, const void *ptr, size_t size, size_t num)
+{
+    size_t nwrote;
+
+    nwrote = fwrite(ptr, size, num, (FILE *)context->hidden.stdio.fp);
+    if (nwrote == 0 && ferror((FILE *)context->hidden.stdio.fp)) {
+        SDL_Error(SDL_EFWRITE);
+    }
+    return nwrote;
+}
+
+static int SDLCALL
+stdio_close(SDL_RWops * context)
+{
+    int status = 0;
+    if (context) {
+        if (context->hidden.stdio.autoclose) {
+            /* WARNING:  Check the return value here! */
+            if (fclose((FILE *)context->hidden.stdio.fp) != 0) {
+                status = SDL_Error(SDL_EFWRITE);
+            }
+        }
+        SDL_FreeRW(context);
+    }
+    return status;
+}
+
+SDL_RWops *
+SDL_RWFromFP(void *fp, SDL_bool autoclose)
+{
+    SDL_RWops *rwops = NULL;
+
+    rwops = SDL_AllocRW();
+    if (rwops != NULL) {
+        rwops->size = stdio_size;
+        rwops->seek = stdio_seek;
+        rwops->read = stdio_read;
+        rwops->write = stdio_write;
+        rwops->close = stdio_close;
+        rwops->hidden.stdio.fp = fp;
+        rwops->hidden.stdio.autoclose = autoclose;
+        rwops->type = SDL_RWOPS_STDFILE;
+    }
+    return rwops;
+}
+```
+
 ## SDL_stdinc.h
 
 M_PI is no longer defined in SDL_stdinc.h, you can use the new symbols SDL_M_PIl (double) and SDL_M_PIf (float) instead.
diff --git a/include/SDL_rwops.h b/include/SDL_rwops.h
index a9cc2b8783e3..ef6a1807ccd2 100644
--- a/include/SDL_rwops.h
+++ b/include/SDL_rwops.h
@@ -195,7 +195,6 @@ typedef struct SDL_RWops
  *
  * \sa SDL_RWclose
  * \sa SDL_RWFromConstMem
- * \sa SDL_RWFromFP
  * \sa SDL_RWFromMem
  * \sa SDL_RWread
  * \sa SDL_RWseek
@@ -205,34 +204,6 @@ typedef struct SDL_RWops
 extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFile(const char *file,
                                                   const char *mode);
 
-/**
- * Use this function to create an SDL_RWops structure from a standard I/O file
- * pointer (stdio.h's `FILE *`).
- *
- * This function is not available on Windows, since files opened in an
- * application on that platform cannot be used by a dynamically linked
- * library.
- *
- * \param fp the `FILE *` that feeds the SDL_RWops stream
- * \param autoclose SDL_TRUE to close the `FILE*` when closing the SDL_RWops,
- *                  SDL_FALSE to leave the `FILE*` open when the RWops is
- *                  closed
- * \returns a pointer to the SDL_RWops structure that is created, or NULL on
- *          failure; call SDL_GetError() for more information.
- *
- * \since This function is available since SDL 3.0.0.
- *
- * \sa SDL_RWclose
- * \sa SDL_RWFromConstMem
- * \sa SDL_RWFromFile
- * \sa SDL_RWFromMem
- * \sa SDL_RWread
- * \sa SDL_RWseek
- * \sa SDL_RWtell
- * \sa SDL_RWwrite
- */
-extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFP(void *fp, SDL_bool autoclose);
-
 /**
  * Use this function to prepare a read-write memory buffer for use with
  * SDL_RWops.
@@ -257,7 +228,6 @@ extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFP(void *fp, SDL_bool autoclose);
  * \sa SDL_RWclose
  * \sa SDL_RWFromConstMem
  * \sa SDL_RWFromFile
- * \sa SDL_RWFromFP
  * \sa SDL_RWFromMem
  * \sa SDL_RWread
  * \sa SDL_RWseek
@@ -292,7 +262,6 @@ extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromMem(void *mem, int size);
  * \sa SDL_RWclose
  * \sa SDL_RWFromConstMem
  * \sa SDL_RWFromFile
- * \sa SDL_RWFromFP
  * \sa SDL_RWFromMem
  * \sa SDL_RWread
  * \sa SDL_RWseek
@@ -400,7 +369,6 @@ extern DECLSPEC Sint64 SDLCALL SDL_RWsize(SDL_RWops *context);
  * \sa SDL_RWclose
  * \sa SDL_RWFromConstMem
  * \sa SDL_RWFromFile
- * \sa SDL_RWFromFP
  * \sa SDL_RWFromMem
  * \sa SDL_RWread
  * \sa SDL_RWtell
@@ -428,7 +396,6 @@ extern DECLSPEC Sint64 SDLCALL SDL_RWseek(SDL_RWops *context,
  * \sa SDL_RWclose
  * \sa SDL_RWFromConstMem
  * \sa SDL_RWFromFile
- * \sa SDL_RWFromFP
  * \sa SDL_RWFromMem
  * \sa SDL_RWread
  * \sa SDL_RWseek
@@ -461,7 +428,6 @@ extern DECLSPEC Sint64 SDLCALL SDL_RWtell(SDL_RWops *context);
  * \sa SDL_RWclose
  * \sa SDL_RWFromConstMem
  * \sa SDL_RWFromFile
- * \sa SDL_RWFromFP
  * \sa SDL_RWFromMem
  * \sa SDL_RWseek
  * \sa SDL_RWwrite
@@ -495,7 +461,6 @@ extern DECLSPEC size_t SDLCALL SDL_RWread(SDL_RWops *context,
  * \sa SDL_RWclose
  * \sa SDL_RWFromConstMem
  * \sa SDL_RWFromFile
- * \sa SDL_RWFromFP
  * \sa SDL_RWFromMem
  * \sa SDL_RWread
  * \sa SDL_RWseek
@@ -525,7 +490,6 @@ extern DECLSPEC size_t SDLCALL SDL_RWwrite(SDL_RWops *context,
  *
  * \sa SDL_RWFromConstMem
  * \sa SDL_RWFromFile
- * \sa SDL_RWFromFP
  * \sa SDL_RWFromMem
  * \sa SDL_RWread
  * \sa SDL_RWseek
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 953fd8efbb88..515b854ca99d 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -39,7 +39,6 @@
 #define SDL_sscanf SDL_sscanf_REAL
 #define SDL_snprintf SDL_snprintf_REAL
 #define SDL_CreateThread SDL_CreateThread_REAL
-#define SDL_RWFromFP SDL_RWFromFP_REAL
 #define SDL_RegisterApp SDL_RegisterApp_REAL
 #define SDL_UnregisterApp SDL_UnregisterApp_REAL
 #define SDL_Direct3D9GetAdapterIndex SDL_Direct3D9GetAdapterIndex_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 778d8ed79f70..862878aeb46c 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -54,12 +54,6 @@ SDL_DYNAPI_PROC(SDL_Thread*,SDL_CreateThread,(SDL_ThreadFunction a, const char *
 SDL_DYNAPI_PROC(SDL_Thread*,SDL_CreateThread,(SDL_ThreadFunction a, const char *b, void *c),(a,b,c),return)
 #endif
 
-#ifdef HAVE_STDIO_H
-SDL_DYNAPI_PROC(SDL_RWops*,SDL_RWFromFP,(void *a, SDL_bool b),(a,b),return)
-#else
-SDL_DYNAPI_PROC(SDL_RWops*,SDL_RWFromFP,(void *a, SDL_bool b),(a,b),return)
-#endif
-
 #if defined(__WIN32__) || defined(__GDK__)
 SDL_DYNAPI_PROC(int,SDL_RegisterApp,(const char *a, Uint32 b, void *c),(a,b,c),return)
 SDL_DYNAPI_PROC(void,SDL_UnregisterApp,(void),(),)
diff --git a/src/file/SDL_rwops.c b/src/file/SDL_rwops.c
index dfb43b596e0e..2b48e557ddd8 100644
--- a/src/file/SDL_rwops.c
+++ b/src/file/SDL_rwops.c
@@ -525,6 +525,27 @@ mem_close(SDL_RWops * context)
 
 /* Functions to create SDL_RWops structures from various data sources */
 
+#ifdef HAVE_STDIO_H
+static SDL_RWops *
+SDL_RWFromFP(void *fp, SDL_bool autoclose)
+{
+    SDL_RWops *rwops = NULL;
+
+    rwops = SDL_AllocRW();
+    if (rwops != NULL) {
+        rwops->size = stdio_size;
+        rwops->seek = stdio_seek;
+        rwops->read = stdio_read;
+        rwops->write = stdio_write;
+        rwops->close = stdio_close;
+        rwops->hidden.stdio.fp = fp;
+        rwops->hidden.stdio.autoclose = autoclose;
+        rwops->type = SDL_RWOPS_STDFILE;
+    }
+    return rwops;
+}
+#endif /* HAVE_STDIO_H */
+
 SDL_RWops *
 SDL_RWFromFile(const char *file, const char *mode)
 {
@@ -614,34 +635,6 @@ SDL_RWFromFile(const char *file, const char *mode)
     return rwops;
 }
 
-#ifdef HAVE_STDIO_H
-SDL_RWops *
-SDL_RWFromFP(void *fp, SDL_bool autoclose)
-{
-    SDL_RWops *rwops = NULL;
-
-    rwops = SDL_AllocRW();
-    if (rwops != NULL) {
-        rwops->size = stdio_size;
-        rwops->seek = stdio_seek;
-        rwops->read = stdio_read;
-        rwops->write = stdio_write;
-        rwops->close = stdio_close;
-        rwops->hidden.stdio.fp = fp;
-        rwops->hidden.stdio.autoclose = autoclose;
-        rwops->type = SDL_RWOPS_STDFILE;
-    }
-    return rwops;
-}
-#else
-SDL_RWops *
-SDL_RWFromFP(void *fp, SDL_bool autoclose)
-{
-    SDL_SetError("SDL not compiled with stdio support");
-    return NULL;
-}
-#endif /* HAVE_STDIO_H */
-
 SDL_RWops *
 SDL_RWFromMem(void *mem, int size)
 {
diff --git a/test/testautomation_rwops.c b/test/testautomation_rwops.c
index 687ff75de8cb..75b84744f2c7 100644
--- a/test/testautomation_rwops.c
+++ b/test/testautomation_rwops.c
@@ -384,111 +384,6 @@ rwops_testFileWrite(void)
 }
 
 
-/**
- * @brief Tests reading from file handle
- *
- * \sa
- * http://wiki.libsdl.org/SDL_RWFromFP
- * http://wiki.libsdl.org/SDL_RWClose
- *
- */
-int
-rwops_testFPRead(void)
-{
-#ifdef HAVE_LIBC
-   FILE *fp;
-   SDL_RWops *rw;
-   int result;
-
-   /* Run read tests. */
-   fp = fopen(RWopsReadTestFilename, "r");
-   SDLTest_AssertCheck(fp != NULL, "Verify handle from opening file '%s' in read mode is not NULL", RWopsReadTestFilename);
-
-   /* Bail out if NULL */
-   if (fp == NULL) return TEST_ABORTED;
-
-   /* Open */
-   rw = SDL_RWFromFP( fp, SDL_TRUE );
-   SDLTest_AssertPass("Call to SDL_RWFromFP() succeeded");
-   SDLTest_AssertCheck(rw != NULL, "Verify opening file with SDL_RWFromFP in read mode does not return NULL");
-
-   /* Bail out if NULL */
-   if (rw == NULL) {
-     fclose(fp);
-     return TEST_ABORTED;
-   }
-
-   /* Check type */
-   SDLTest_AssertCheck(
-       rw->type == SDL_RWOPS_STDFILE,
-       "Verify RWops type is SDL_RWOPS_STDFILE; expected: %d, got: %" SDL_PRIu32, SDL_RWOPS_STDFILE, rw->type);
-
-   /* Run generic tests */
-   _testGenericRWopsValidations( rw, 0 );
-
-   /* Close handle - does fclose() */
-   result = SDL_RWclose(rw);
-   SDLTest_AssertPass("Call to SDL_RWclose() succeeded");
-   SDLTest_AssertCheck(result == 0, "Verify result value is 0; got: %d", result);
-
-#endif /* HAVE_LIBC */
-
-   return TEST_COMPLETED;
-}
-
-
-/**
- * @brief Tests writing to file handle
- *
- * \sa
- * http://wiki.libsdl.org/SDL_RWFromFP
- * http://wiki.libsdl.org/SDL_RWClose
- *
- */
-int
-rwops_testFPWrite(void)
-{
-#ifdef HAVE_LIBC
-   FILE *fp;
-   SDL_RWops *rw;
-   int result;
-
-   /* Run write tests. */
-   fp = fopen(RWopsWriteTestFilename, "w+");
-   SDLTest_AssertCheck(fp != NULL, "Verify handle from opening file '%s' in write mode is not NULL", RWopsWriteTestFilename);
-
-   /* Bail out if NULL */
-   if (fp == NULL) return TEST_ABORTED;
-
-   /* Open */
-   rw = SDL_RWFromFP( fp, SDL_TRUE );
-   SDLTest_AssertPass("Call to SDL_RWFromFP() succeeded");
-   SDLTest_AssertCheck(rw != NULL, "Verify opening file with SDL_RWFromFP in write mode does not return NULL");
-
-   /* Bail out if NULL */
-   if (rw == NULL) {
-     fclose(fp);
-     return TEST_ABORTED;
-   }
-
-   /* Check type */
-   SDLTest_AssertCheck(
-       rw->type == SDL_RWOPS_STDFILE,
-       "Verify RWops type is SDL_RWOPS_STDFILE; expected: %d, got: %" SDL_PRIu32, SDL_RWOPS_STDFILE, rw->type);
-
-   /* Run generic tests */
-   _testGenericRWopsValidations( rw, 1 );
-
-   /* Close handle - does fclose() */
-   result = SDL_RWclose(rw);
-   SDLTest_AssertPass("Call to SDL_RWclose() succeeded");
-   SDLTest_AssertCheck(result == 0, "Verify result value is 0; got: %d", result);
-
-#endif /* HAVE_LIBC */
-
-   return TEST_COMPLETED;
-}
-
 /**
  * @brief Tests alloc and free RW context.
  *
@@ -728,24 +623,18 @@ static const SDLTest_TestCaseReference rwopsTest5 =
         { (SDLTest_TestCaseFp)rwops_testFileWrite, "rwops_testFileWrite", "Test writing to a file", TEST_ENABLED };
 
 static const SDLTest_TestCaseReference rwopsTest6 =
-        { (SDLTest_TestCaseFp)rwops_testFPRead, "rwops_testFPRead", "Test reading from file pointer", TEST_ENABLED };
-
-static const SDLTest_TestCaseReference rwopsTest7 =
-        { (SDLTest_TestCaseFp)rwops_testFPWrite, "rwops_testFPWrite", "Test writing to file pointer", TEST_ENABLED };
-
-static const SDLTest_TestCaseReference rwopsTest8 =
         { (SDLTest_TestCaseFp)rwops_testAllocFree, "rwops_testAllocFree", "Test alloc and free of RW context", TEST_ENABLED };
 
-static const SDLTest_TestCaseReference rwopsTest9 =
+static const SDLTest_TestCaseReference rwopsTest7 =
         { (SDLTest_TestCaseFp)rwops_testFileWriteReadEndian, "rwops_testFileWriteReadEndian", "Test writing and reading via the Endian aware functions", TEST_ENABLED };
 
-static const SDLTest_TestCaseReference rwopsTest10 =
+static const SDLTest_TestCaseReference rwopsTest8 =
         { (SDLTest_TestCaseFp)rwops_testCompareRWFromMemWithRWFromFile, "rwops_testCompareRWFromMemWithRWFromFile", "Compare RWFromMem and RWFromFile RWops for read and seek", TEST_ENABLED };
 
 /* Sequence of RWops test cases */
 static const SDLTest_TestCaseReference *rwopsTests[] =  {
     &rwopsTest1, &rwopsTest2, &rwopsTest3, &rwopsTest4, &rwopsTest5, &rwopsTest6,
-    &rwopsTest7, &rwopsTest8, &rwopsTest9, &rwopsTest10, NULL
+    &rwopsTest7, &rwopsTest8, NULL
 };
 
 /* RWops test suite (global) */