SDL: Added support for the "%n" sscanf format specifier (ab444)

From ab44451578ec6e449bd78b8f99ee0333dba69e3c Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 24 Feb 2025 18:49:31 -0800
Subject: [PATCH] Added support for the "%n" sscanf format specifier

(cherry picked from commit 69803253100111f870c068300d336edac19783b4)
---
 src/stdlib/SDL_string.c      | 31 ++++++++++++++++++++++
 test/testautomation_stdlib.c | 50 +++++++++++++++++++++---------------
 2 files changed, 61 insertions(+), 20 deletions(-)

diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c
index 9af6977fe6b28..2ce22186e3c56 100644
--- a/src/stdlib/SDL_string.c
+++ b/src/stdlib/SDL_string.c
@@ -1113,6 +1113,7 @@ static SDL_bool CharacterMatchesSet(char c, const char *set, size_t set_len)
 /* NOLINTNEXTLINE(readability-non-const-parameter) */
 int SDL_vsscanf(const char *text, const char *fmt, va_list ap)
 {
+    const char *start = text;
     int retval = 0;
 
     if (!text || !*text) {
@@ -1383,6 +1384,36 @@ int SDL_vsscanf(const char *text, const char *fmt, va_list ap)
                     }
                     done = SDL_TRUE;
                     break;
+                case 'n':
+                    switch (inttype) {
+                    case DO_SHORT:
+                    {
+                        short *valuep = va_arg(ap, short *);
+                        *valuep = (short)(text - start);
+                    } break;
+                    case DO_INT:
+                    {
+                        int *valuep = va_arg(ap, int *);
+                        *valuep = (int)(text - start);
+                    } break;
+                    case DO_LONG:
+                    {
+                        long *valuep = va_arg(ap, long *);
+                        *valuep = (long)(text - start);
+                    } break;
+                    case DO_LONGLONG:
+                    {
+                        long long *valuep = va_arg(ap, long long *);
+                        *valuep = (long long)(text - start);
+                    } break;
+                    case DO_SIZE_T:
+                    {
+                        size_t *valuep = va_arg(ap, size_t *);
+                        *valuep = (size_t)(text - start);
+                    } break;
+                    }
+                    done = SDL_TRUE;
+                    break;
                 case '[':
                 {
                     const char *set = fmt + 1;
diff --git a/test/testautomation_stdlib.c b/test/testautomation_stdlib.c
index 30abf0a7f76a3..e62069da4b1ae 100644
--- a/test/testautomation_stdlib.c
+++ b/test/testautomation_stdlib.c
@@ -393,6 +393,7 @@ int stdlib_getsetenv(void *arg)
 #endif
 
 #define FMT_PRILLd "%lld"
+#define FMT_PRILLdn "%lld%lln"
 #define FMT_PRILLu "%llu"
 
 /**
@@ -403,11 +404,12 @@ int stdlib_sscanf(void *arg)
 {
     int output;
     int result;
+    int length;
     int expected_output;
     int expected_result;
-    short short_output, expected_short_output;
-    long long_output, expected_long_output;
-    long long long_long_output, expected_long_long_output;
+    short short_output, expected_short_output, short_length;
+    long long_output, expected_long_output, long_length;
+    long long long_long_output, expected_long_long_output, long_long_length;
     size_t size_output, expected_size_output;
     char text[128], text2[128];
 
@@ -426,43 +428,51 @@ int stdlib_sscanf(void *arg)
     SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result);
 
     output = 123;
+    length = 0;
     expected_output = 2;
     expected_result = 1;
-    result = SDL_sscanf("2", "%i", &output);
-    SDLTest_AssertPass("Call to SDL_sscanf(\"2\", \"%%i\", &output)");
+    result = SDL_sscanf("2", "%i%n", &output, &length);
+    SDLTest_AssertPass("Call to SDL_sscanf(\"2\", \"%%i%%n\", &output, &length)");
     SDLTest_AssertCheck(expected_output == output, "Check output, expected: %i, got: %i", expected_output, output);
     SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result);
+    SDLTest_AssertCheck(length == 1, "Check length, expected: 1, got: %i", length);
 
     output = 123;
+    length = 0;
     expected_output = 0xa;
     expected_result = 1;
-    result = SDL_sscanf("aa", "%1x", &output);
-    SDLTest_AssertPass("Call to SDL_sscanf(\"aa\", \"%%1x\", &output)");
+    result = SDL_sscanf("aa", "%1x%n", &output, &length);
+    SDLTest_AssertPass("Call to SDL_sscanf(\"aa\", \"%%1x%%n\", &output, &length)");
     SDLTest_AssertCheck(expected_output == output, "Check output, expected: %i, got: %i", expected_output, output);
     SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result);
+    SDLTest_AssertCheck(length == 1, "Check length, expected: 1, got: %i", length);
 
-#define SIZED_TEST_CASE(type, var, format_specifier)                                                                                                                             \
+#define SIZED_TEST_CASE(type, var, printf_specifier, scanf_specifier)                                                                                                            \
     var##_output = 123;                                                                                                                                                          \
+    var##_length = 0;                                                                                                                                                            \
     expected_##var##_output = (type)(((unsigned type)(~0)) >> 1);                                                                                                                \
     expected_result = 1;                                                                                                                                                         \
-    result = SDL_snprintf(text, sizeof(text), format_specifier, expected_##var##_output);                                                                                        \
-    result = SDL_sscanf(text, format_specifier, &var##_output);                                                                                                                  \
-    SDLTest_AssertPass("Call to SDL_sscanf(\"%s\", \"%s\", &output)", text, #format_specifier);                                                                                  \
-    SDLTest_AssertCheck(expected_##var##_output == var##_output, "Check output, expected: " format_specifier ", got: " format_specifier, expected_##var##_output, var##_output); \
+    result = SDL_snprintf(text, sizeof(text), printf_specifier, expected_##var##_output);                                                                                        \
+    result = SDL_sscanf(text, scanf_specifier, &var##_output, &var##_length);                                                                                                    \
+    SDLTest_AssertPass("Call to SDL_sscanf(\"%s\", %s, &output, &length)", text, #scanf_specifier);                                                                              \
+    SDLTest_AssertCheck(expected_##var##_output == var##_output, "Check output, expected: " printf_specifier ", got: " printf_specifier, expected_##var##_output, var##_output); \
     SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result);                                                        \
+    SDLTest_AssertCheck(var##_length == (type)SDL_strlen(text), "Check length, expected: %i, got: %i", (int)SDL_strlen(text), (int)var##_length);                                \
                                                                                                                                                                                  \
     var##_output = 123;                                                                                                                                                          \
+    var##_length = 0;                                                                                                                                                            \
     expected_##var##_output = ~(type)(((unsigned type)(~0)) >> 1);                                                                                                               \
     expected_result = 1;                                                                                                                                                         \
-    result = SDL_snprintf(text, sizeof(text), format_specifier, expected_##var##_output);                                                                                        \
-    result = SDL_sscanf(text, format_specifier, &var##_output);                                                                                                                  \
-    SDLTest_AssertPass("Call to SDL_sscanf(\"%s\", \"%s\", &output)", text, #format_specifier);                                                                                  \
-    SDLTest_AssertCheck(expected_##var##_output == var##_output, "Check output, expected: " format_specifier ", got: " format_specifier, expected_##var##_output, var##_output); \
-    SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result);
+    result = SDL_snprintf(text, sizeof(text), printf_specifier, expected_##var##_output);                                                                                        \
+    result = SDL_sscanf(text, scanf_specifier, &var##_output, &var##_length);                                                                                                    \
+    SDLTest_AssertPass("Call to SDL_sscanf(\"%s\", %s, &output, &length)", text, #scanf_specifier);                                                                              \
+    SDLTest_AssertCheck(expected_##var##_output == var##_output, "Check output, expected: " printf_specifier ", got: " printf_specifier, expected_##var##_output, var##_output); \
+    SDLTest_AssertCheck(expected_result == result, "Check return value, expected: %i, got: %i", expected_result, result);                                                        \
+    SDLTest_AssertCheck(var##_length == (type)SDL_strlen(text), "Check length, expected: %i, got: %i", (int)SDL_strlen(text), (int)var##_length);                                \
 
-    SIZED_TEST_CASE(short, short, "%hd")
-    SIZED_TEST_CASE(long, long, "%ld")
-    SIZED_TEST_CASE(long long, long_long, "%lld")
+    SIZED_TEST_CASE(short, short, "%hd", "%hd%hn")
+    SIZED_TEST_CASE(long, long, "%ld", "%ld%ln")
+    SIZED_TEST_CASE(long long, long_long, FMT_PRILLd, FMT_PRILLdn)
 
     size_output = 123;
     expected_size_output = ~((size_t)0);