Переглянути джерело

stdlib: Add SDL_wcstoul(), SDL_wcstoll(), and SDL_wcstoull()

Includes the appropriate stdlib checks and automated tests.
Frank Praznik 11 годин тому
батько
коміт
96919c37c9

+ 1 - 1
CMakeLists.txt

@@ -1149,7 +1149,7 @@ if(SDL_LIBC)
     tan tanf trunc truncf
     unsetenv
     vsnprintf vsscanf
-    wcsnlen wcscmp wcsdup wcslcat wcslcpy wcslen wcsncmp wcsstr wcstol
+    wcsnlen wcscmp wcsdup wcslcat wcslcpy wcslen wcsncmp wcsstr wcstol wcstoll wcstoul wcstoull
   )
   if(WINDOWS OR CYGWIN)
     list(APPEND symbols_to_check

+ 5 - 0
build-scripts/check_stdlib_usage.py

@@ -123,6 +123,7 @@ STDLIB_SYMBOLS = (
     'strtol',
     'strtoll',
     'strtoul',
+    'strtoull',
     'strupr',
     'tan',
     'tanf',
@@ -147,6 +148,10 @@ STDLIB_SYMBOLS = (
     'wcsncasecmp',
     'wcsncmp',
     'wcsstr',
+    'wcstol',
+    'wcstoll',
+    'wcstoul',
+    'wcstoull',
 )
 RE_STDLIB_SYMBOL = re.compile(rf"(?<!->)\b(?P<symbol>{'|'.join(STDLIB_SYMBOLS)})\b\(")
 

+ 3 - 0
cmake/PreseedDOSCache.cmake

@@ -154,6 +154,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "DOS")
       set(LIBC_HAS_WCSNLEN                               ""    CACHE INTERNAL "Have symbol wcsnlen")
       set(LIBC_HAS_WCSSTR                                ""    CACHE INTERNAL "Have symbol wcsstr")
       set(LIBC_HAS_WCSTOL                                ""    CACHE INTERNAL "Have symbol wcstol")
+      set(LIBC_HAS_WCSTOLL                               ""    CACHE INTERNAL "Have symbol wcstoll")
+      set(LIBC_HAS_WCSTOUL                               ""    CACHE INTERNAL "Have symbol wcstoul")
+      set(LIBC_HAS_WCSTOULL                              ""    CACHE INTERNAL "Have symbol wcstoull")
       set(LIBC_HAS__EXIT                                 "1"   CACHE INTERNAL "Have symbol _Exit")
       set(LIBC_HAS__I64TOA                               ""    CACHE INTERNAL "Have symbol _i64toa")
       set(LIBC_HAS__LTOA                                 ""    CACHE INTERNAL "Have symbol _ltoa")

+ 3 - 0
cmake/PreseedEmscriptenCache.cmake

@@ -127,6 +127,9 @@ if(EMSCRIPTEN)
     set(LIBC_HAS_WCSNLEN                                 "1"   CACHE INTERNAL "Have symbol wcsnlen")
     set(LIBC_HAS_WCSSTR                                  "1"   CACHE INTERNAL "Have symbol wcsstr")
     set(LIBC_HAS_WCSTOL                                  "1"   CACHE INTERNAL "Have symbol wcstol")
+    set(LIBC_HAS_WCSTOLL                                 "1"   CACHE INTERNAL "Have symbol wcstoll")
+    set(LIBC_HAS_WCSTOUL                                 "1"   CACHE INTERNAL "Have symbol wcstoul")
+    set(LIBC_HAS_WCSTOULL                                "1"   CACHE INTERNAL "Have symbol wcstoull")
     set(LIBC_HAS__EXIT                                   "1"   CACHE INTERNAL "Have symbol _Exit")
     set(LIBC_HAS__I64TOA                                 ""    CACHE INTERNAL "Have symbol _i64toa")
     set(LIBC_HAS__LTOA                                   ""    CACHE INTERNAL "Have symbol _ltoa")

+ 3 - 0
cmake/PreseedMSVCCache.cmake

@@ -149,6 +149,9 @@ if(MSVC)
       set(LIBC_HAS_WCSNLEN                                 "1"   CACHE INTERNAL "Have symbol wcsnlen")
       set(LIBC_HAS_WCSSTR                                  "1"   CACHE INTERNAL "Have symbol wcsstr")
       set(LIBC_HAS_WCSTOL                                  "1"   CACHE INTERNAL "Have symbol wcstol")
+      set(LIBC_HAS_WCSTOLL                                 "1"   CACHE INTERNAL "Have symbol wcstoll")
+      set(LIBC_HAS_WCSTOUL                                 "1"   CACHE INTERNAL "Have symbol wcstoul")
+      set(LIBC_HAS_WCSTOULL                                "1"   CACHE INTERNAL "Have symbol wcstoull")
       set(LIBC_HAS__EXIT                                   "1"   CACHE INTERNAL "Have symbol _Exit")
       set(LIBC_HAS__I64TOA                                 "1"   CACHE INTERNAL "Have symbol _i64toa")
       set(LIBC_HAS__LTOA                                   "1"   CACHE INTERNAL "Have symbol _ltoa")

+ 3 - 0
cmake/PreseedNokiaNGageCache.cmake

@@ -130,6 +130,9 @@ if(NGAGESDK)
     set(LIBC_HAS_WCSNLEN                                 ""    CACHE INTERNAL "Have symbol wcsnlen")
     set(LIBC_HAS_WCSSTR                                  ""    CACHE INTERNAL "Have symbol wcsstr")
     set(LIBC_HAS_WCSTOL                                  ""    CACHE INTERNAL "Have symbol wcstol")
+    set(LIBC_HAS_WCSTOLL                                 ""    CACHE INTERNAL "Have symbol wcstoll")
+    set(LIBC_HAS_WCSTOUL                                 ""    CACHE INTERNAL "Have symbol wcstoul")
+    set(LIBC_HAS_WCSTOULL                                ""    CACHE INTERNAL "Have symbol wcstoull")
     set(LIBC_HAS__EXIT                                   ""    CACHE INTERNAL "Have symbol _Exit")
     set(LIBC_HAS__I64TOA                                 ""    CACHE INTERNAL "Have symbol _i64toa")
     set(LIBC_HAS__LTOA                                   ""    CACHE INTERNAL "Have symbol _ltoa")

+ 81 - 0
include/SDL3/SDL_stdinc.h

@@ -3054,6 +3054,87 @@ extern SDL_DECLSPEC int SDLCALL SDL_wcsncasecmp(const wchar_t *str1, const wchar
  */
 extern SDL_DECLSPEC long SDLCALL SDL_wcstol(const wchar_t *str, wchar_t **endp, int base);
 
+/**
+ * Parse an `unsigned long` from a wide string.
+ *
+ * If `str` starts with whitespace, then those whitespace characters are
+ * skipped before attempting to parse the number.
+ *
+ * If the parsed number does not fit inside an `unsigned long`, the result is clamped to
+ * the minimum and maximum representable `unsigned long` values.
+ *
+ * \param str The null-terminated wide string to read. Must not be NULL.
+ * \param endp If not NULL, the address of the first invalid wide character
+ *             (i.e. the next character after the parsed number) will be
+ *             written to this pointer.
+ * \param base The base of the integer to read. Supported values are 0 and 2
+ *             to 36 inclusive. If 0, the base will be inferred from the
+ *             number's prefix (0x for hexadecimal, 0 for octal, decimal
+ *             otherwise).
+ * \returns the parsed `unsigned long`, or 0 if no number could be parsed.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_strtoul
+ */
+extern SDL_DECLSPEC unsigned long SDLCALL SDL_wcstoul(const wchar_t *str, wchar_t **endp, int base);
+
+/**
+ * Parse a `long long` from a wide string.
+ *
+ * If `str` starts with whitespace, then those whitespace characters are
+ * skipped before attempting to parse the number.
+ *
+ * If the parsed number does not fit inside a `long long`, the result is clamped to
+ * the minimum and maximum representable `long long` values.
+ *
+ * \param str The null-terminated wide string to read. Must not be NULL.
+ * \param endp If not NULL, the address of the first invalid wide character
+ *             (i.e. the next character after the parsed number) will be
+ *             written to this pointer.
+ * \param base The base of the integer to read. Supported values are 0 and 2
+ *             to 36 inclusive. If 0, the base will be inferred from the
+ *             number's prefix (0x for hexadecimal, 0 for octal, decimal
+ *             otherwise).
+ * \returns the parsed `long long`, or 0 if no number could be parsed.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_strtoll
+ */
+extern SDL_DECLSPEC long long SDLCALL SDL_wcstoll(const wchar_t *str, wchar_t **endp, int base);
+
+/**
+ * Parse an `unsigned long long` from a wide string.
+ *
+ * If `str` starts with whitespace, then those whitespace characters are
+ * skipped before attempting to parse the number.
+ *
+ * If the parsed number does not fit inside an `unsigned long long`, the result is clamped to
+ * the minimum and maximum representable `unsigned long long` values.
+ *
+ * \param str The null-terminated wide string to read. Must not be NULL.
+ * \param endp If not NULL, the address of the first invalid wide character
+ *             (i.e. the next character after the parsed number) will be
+ *             written to this pointer.
+ * \param base The base of the integer to read. Supported values are 0 and 2
+ *             to 36 inclusive. If 0, the base will be inferred from the
+ *             number's prefix (0x for hexadecimal, 0 for octal, decimal
+ *             otherwise).
+ * \returns the parsed `unsigned long long`, or 0 if no number could be parsed.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_strtoull
+ */
+extern SDL_DECLSPEC unsigned long long SDLCALL SDL_wcstoull(const wchar_t *str, wchar_t **endp, int base);
+
 /**
  * This works exactly like strlen() but doesn't require access to a C runtime.
  *

+ 3 - 0
include/build_config/SDL_build_config.h.cmake

@@ -92,6 +92,9 @@
 #cmakedefine HAVE_WCSCMP 1
 #cmakedefine HAVE_WCSNCMP 1
 #cmakedefine HAVE_WCSTOL 1
+#cmakedefine HAVE_WCSTOLL 1
+#cmakedefine HAVE_WCSTOUL 1
+#cmakedefine HAVE_WCSTOULL 1
 #cmakedefine HAVE_STRLEN 1
 #cmakedefine HAVE_STRNLEN 1
 #cmakedefine HAVE_STRLCPY 1

+ 3 - 0
src/dynapi/SDL_dynapi.exports

@@ -1291,3 +1291,6 @@ _SDL_HasSVE2
 _SDL_GamepadHasCapSense
 _SDL_GetGamepadCapSense
 _SDL_aligned_alloc_zero
+_SDL_wcstoul
+_SDL_wcstoll
+_SDL_wcstoull

+ 3 - 0
src/dynapi/SDL_dynapi.sym

@@ -1292,6 +1292,9 @@ SDL3_0.0.0 {
     SDL_GamepadHasCapSense;
     SDL_GetGamepadCapSense;
     SDL_aligned_alloc_zero;
+    SDL_wcstoul;
+    SDL_wcstoll;
+    SDL_wcstoull;
     # extra symbols go here (don't modify this line)
   local: *;
 };

+ 3 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -1318,3 +1318,6 @@
 #define SDL_GamepadHasCapSense SDL_GamepadHasCapSense_REAL
 #define SDL_GetGamepadCapSense SDL_GetGamepadCapSense_REAL
 #define SDL_aligned_alloc_zero SDL_aligned_alloc_zero_REAL
+#define SDL_wcstoul SDL_wcstoul_REAL
+#define SDL_wcstoll SDL_wcstoll_REAL
+#define SDL_wcstoull SDL_wcstoull_REAL

+ 3 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -1326,3 +1326,6 @@ SDL_DYNAPI_PROC(bool,SDL_HasSVE2,(void),(),return)
 SDL_DYNAPI_PROC(bool,SDL_GamepadHasCapSense,(SDL_Gamepad *a,SDL_GamepadCapSenseType b),(a,b),return)
 SDL_DYNAPI_PROC(bool,SDL_GetGamepadCapSense,(SDL_Gamepad *a,SDL_GamepadCapSenseType b),(a,b),return)
 SDL_DYNAPI_PROC(void*,SDL_aligned_alloc_zero,(size_t a,size_t b),(a,b),return)
+SDL_DYNAPI_PROC(unsigned long,SDL_wcstoul,(const wchar_t *a,wchar_t **b,int c),(a,b,c),return)
+SDL_DYNAPI_PROC(long long,SDL_wcstoll,(const wchar_t *a,wchar_t **b,int c),(a,b,c),return)
+SDL_DYNAPI_PROC(unsigned long long,SDL_wcstoull,(const wchar_t *a,wchar_t **b,int c),(a,b,c),return)

+ 105 - 1
src/stdlib/SDL_string.c

@@ -432,7 +432,7 @@ static size_t SDL_ScanUnsignedLongLongInternal(const char *text, int count, int
 }
 #endif
 
-#ifndef HAVE_WCSTOL
+#if !defined(HAVE_WCSTOL) || !defined (HAVE_WCSTOLL) || !defined(HAVE_WCSTOUL) || !defined(HAVE_WCSTOULL)
 // SDL_ScanUnsignedLongLongInternalW assumes that wchar_t can be converted to int without truncating bits
 SDL_COMPILE_TIME_ASSERT(wchar_t_int, sizeof(wchar_t) <= sizeof(int));
 
@@ -591,6 +591,29 @@ static size_t SDL_ScanUnsignedLong(const char *text, int count, int radix, unsig
 }
 #endif
 
+#ifndef HAVE_WCSTOUL
+static size_t SDL_ScanUnsignedLongW(const wchar_t *text, int count, int radix, unsigned long *valuep)
+{
+    const unsigned long ulong_max = ~0UL;
+    unsigned long long value;
+    bool negative;
+    size_t len = SDL_ScanUnsignedLongLongInternalW(text, count, radix, &value, &negative);
+    if (negative) {
+        if (value == 0 || value > ulong_max) {
+            value = ulong_max;
+        } else if (value == ulong_max) {
+            value = 1;
+        } else {
+            value = 0ULL - value;
+        }
+    } else if (value > ulong_max) {
+        value = ulong_max;
+    }
+    *valuep = (unsigned long)value;
+    return len;
+}
+#endif
+
 #ifndef HAVE_VSSCANF
 static size_t SDL_ScanUintPtrT(const char *text, uintptr_t *valuep)
 {
@@ -636,6 +659,28 @@ static size_t SDL_ScanLongLong(const char *text, int count, int radix, long long
 }
 #endif
 
+#ifndef HAVE_WCSTOLL
+static size_t SDL_ScanLongLongW(const wchar_t *text, int count, int radix, long long *valuep)
+{
+    const unsigned long long llong_max = (~0ULL) >> 1;
+    unsigned long long value;
+    bool negative;
+    size_t len = SDL_ScanUnsignedLongLongInternalW(text, count, radix, &value, &negative);
+    if (negative) {
+        const unsigned long long abs_llong_min = llong_max + 1;
+        if (value == 0 || value > abs_llong_min) {
+            value = 0ULL - abs_llong_min;
+        } else {
+            value = 0ULL - value;
+        }
+    } else if (value > llong_max) {
+        value = llong_max;
+    }
+    *valuep = value;
+    return len;
+}
+#endif
+
 #if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOULL) || !defined(HAVE_STRTOD)
 static size_t SDL_ScanUnsignedLongLong(const char *text, int count, int radix, unsigned long long *valuep)
 {
@@ -653,6 +698,23 @@ static size_t SDL_ScanUnsignedLongLong(const char *text, int count, int radix, u
 }
 #endif
 
+#ifndef HAVE_WCSTOULL
+static size_t SDL_ScanUnsignedLongLongW(const wchar_t *text, int count, int radix, unsigned long long *valuep)
+{
+    const unsigned long long ullong_max = ~0ULL;
+    bool negative;
+    size_t len = SDL_ScanUnsignedLongLongInternalW(text, count, radix, valuep, &negative);
+    if (negative) {
+        if (*valuep == 0) {
+            *valuep = ullong_max;
+        } else {
+            *valuep = 0ULL - *valuep;
+        }
+    }
+    return len;
+}
+#endif
+
 #if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOD)
 static size_t SDL_ScanFloat(const char *text, double *valuep)
 {
@@ -923,6 +985,48 @@ long SDL_wcstol(const wchar_t *string, wchar_t **endp, int base)
 #endif // HAVE_WCSTOL
 }
 
+unsigned long SDL_wcstoul(const wchar_t *string, wchar_t **endp, int base)
+{
+#ifdef HAVE_WCSTOUL
+    return wcstoul(string, endp, base);
+#else
+    unsigned long value = 0;
+    size_t len = SDL_ScanUnsignedLongW(string, 0, base, &value);
+    if (endp) {
+        *endp = (wchar_t *)string + len;
+    }
+    return value;
+#endif // HAVE_WCSTOUL
+}
+
+long long SDL_wcstoll(const wchar_t *string, wchar_t **endp, int base)
+{
+#ifdef HAVE_WCSTOLL
+    return wcstoll(string, endp, base);
+#else
+    long long value = 0;
+    size_t len = SDL_ScanLongLongW(string, 0, base, &value);
+    if (endp) {
+        *endp = (wchar_t *)string + len;
+    }
+    return value;
+#endif // HAVE_WCSTOLL
+}
+
+unsigned long long SDL_wcstoull(const wchar_t *string, wchar_t **endp, int base)
+{
+#ifdef HAVE_WCSTOULL
+    return wcstoull(string, endp, base);
+#else
+    unsigned long long value = 0;
+    size_t len = SDL_ScanUnsignedLongLongW(string, 0, base, &value);
+    if (endp) {
+        *endp = (wchar_t *)string + len;
+    }
+    return value;
+#endif // HAVE_WCSTOULL
+}
+
 size_t SDL_strlcpy(SDL_OUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen)
 {
 #ifdef HAVE_STRLCPY

+ 69 - 35
test/testautomation_stdlib.c

@@ -1289,54 +1289,88 @@ stdlib_strpbrk(void *arg)
     return TEST_COMPLETED;
 }
 
-static int SDLCALL stdlib_wcstol(void *arg)
+static int SDLCALL stdlib_wcstox(void *arg)
 {
-    const long long_max = (~0UL) >> 1;
-    const long long_min = ((~0UL) >> 1) + 1UL;
-
-#define WCSTOL_TEST_CASE(str, base, expected_result, expected_endp_offset) do {                             \
-        const wchar_t *s = str;                                                                             \
-        long r, expected_r = expected_result;                                                               \
-        wchar_t *ep, *expected_ep = (wchar_t *)s + expected_endp_offset;                                    \
-        r = SDL_wcstol(s, &ep, base);                                                                       \
-        SDLTest_AssertPass("Call to SDL_wcstol(" #str ", &endp, " #base ")");                               \
-        SDLTest_AssertCheck(r == expected_r, "Check result value, expected: %ld, got: %ld", expected_r, r); \
-        SDLTest_AssertCheck(ep == expected_ep, "Check endp value, expected: %p, got: %p", expected_ep, ep); \
+    const unsigned long long ullong_max = ~0ULL;
+
+#define WCSTOX_TEST_CASE(func_name, type, format_spec, str, base, expected_result, expected_endp_offset)                         \
+    do {                                                                                                                         \
+        const wchar_t *s = str;                                                                                                  \
+        type r, expected_r = expected_result;                                                                                    \
+        wchar_t *ep, *expected_ep = (wchar_t *)s + expected_endp_offset;                                                         \
+        r = func_name(s, &ep, base);                                                                                             \
+        SDLTest_AssertPass("Call to " #func_name "(" #str ", &endp, " #base ")");                                                \
+        SDLTest_AssertCheck(r == expected_r, "Check result value, expected: " format_spec ", got: " format_spec, expected_r, r); \
+        SDLTest_AssertCheck(ep == expected_ep, "Check endp value, expected: %p, got: %p", expected_ep, ep);                      \
     } while (0)
 
     // infer decimal
-    WCSTOL_TEST_CASE(L"\t  123abcxyz", 0, 123, 6); // skip leading space
-    WCSTOL_TEST_CASE(L"+123abcxyz", 0, 123, 4);
-    WCSTOL_TEST_CASE(L"-123abcxyz", 0, -123, 4);
-    WCSTOL_TEST_CASE(L"99999999999999999999abcxyz", 0, long_max, 20);
-    WCSTOL_TEST_CASE(L"-99999999999999999999abcxyz", 0, long_min, 21);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"\t  123abcxyz", 0, 123, 6); // skip leading space
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"+123abcxyz", 0, 123, 4);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"+123abcxyz", 0, 123, 4);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"-123abcxyz", 0, -123, 4);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"9999999999999999999999999999999999999999abcxyz", 0, ullong_max, 40);
 
     // infer hexadecimal
-    WCSTOL_TEST_CASE(L"0x123abcxyz", 0, 0x123abc, 8);
-    WCSTOL_TEST_CASE(L"0X123ABCXYZ", 0, 0x123abc, 8); // uppercase X
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"0x123abcxyz", 0, 0x123abc, 8);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"0X123ABCXYZ", 0, 0x123abc, 8); // uppercase X
 
     // infer octal
-    WCSTOL_TEST_CASE(L"0123abcxyz", 0, 0123, 4);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"0123abcxyz", 0, 0123, 4);
 
     // arbitrary bases
-    WCSTOL_TEST_CASE(L"00110011", 2, 51, 8);
-    WCSTOL_TEST_CASE(L"-uvwxyz", 32, -991, 3);
-    WCSTOL_TEST_CASE(L"ZzZzZzZzZzZzZ", 36, long_max, 13);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"00110011", 2, 51, 8);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"-uvwxyz", 32, -991, 3);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"ZzZzZzZzZzZzZzZzZzZzZzZzZ", 36, ullong_max, 25);
+
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"0", 0, 0, 1);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"0", 10, 0, 1);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"-0", 0, 0, 2);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L"-0", 10, 0, 2);
+    WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLu, L" - 1", 0, 0, 0); // invalid input
 
-    WCSTOL_TEST_CASE(L"-0", 10, 0, 2);
-    WCSTOL_TEST_CASE(L" - 1", 0, 0, 0); // invalid input
+    // We know that SDL_wcstol, SDL_wcstoul and SDL_wcstoll share the same code path as SDL_wcstoull under the hood,
+    // so the most interesting test cases are those close to the bounds of the integer type.
+
+    // For simplicity, we only run long/long long tests when they are 32-bit/64-bit, respectively.
+    // Suppressing warnings would be difficult otherwise.
+    // Since the CI runs the tests against a variety of targets, this should be fine in practice.
 
-    // values near the bounds of the type
     const bool long_is_32bit = sizeof(long) == 4;
     if (long_is_32bit) {
-        WCSTOL_TEST_CASE(L"2147483647", 10, 2147483647, 10);
-        WCSTOL_TEST_CASE(L"2147483648", 10, 2147483647, 10);
-        WCSTOL_TEST_CASE(L"-2147483648", 10, -2147483647L - 1, 11);
-        WCSTOL_TEST_CASE(L"-2147483649", 10, -2147483647L - 1, 11);
-        WCSTOL_TEST_CASE(L"-9999999999999999999999999999999999999999", 10, -2147483647L - 1, 41);
+        WCSTOX_TEST_CASE(SDL_wcstol, long, "%ld", L"0", 0, 0, 1);
+        WCSTOX_TEST_CASE(SDL_wcstol, long, "%ld", L"0", 10, 0, 1);
+        WCSTOX_TEST_CASE(SDL_wcstol, long, "%ld", L"-0", 0, 0, 2);
+        WCSTOX_TEST_CASE(SDL_wcstol, long, "%ld", L"-0", 10, 0, 2);
+        WCSTOX_TEST_CASE(SDL_wcstol, long, "%ld", L"2147483647", 10, 2147483647, 10);
+        WCSTOX_TEST_CASE(SDL_wcstol, long, "%ld", L"2147483648", 10, 2147483647, 10);
+        WCSTOX_TEST_CASE(SDL_wcstol, long, "%ld", L"-2147483648", 10, -2147483647L - 1, 11);
+        WCSTOX_TEST_CASE(SDL_wcstol, long, "%ld", L"-2147483649", 10, -2147483647L - 1, 11);
+        WCSTOX_TEST_CASE(SDL_wcstol, long, "%ld", L"-9999999999999999999999999999999999999999", 10, -2147483647L - 1, 41);
+
+        WCSTOX_TEST_CASE(SDL_wcstoul, unsigned long, "%lu", L"4294967295", 10, 4294967295UL, 10);
+        WCSTOX_TEST_CASE(SDL_wcstoul, unsigned long, "%lu", L"4294967296", 10, 4294967295UL, 10);
+        WCSTOX_TEST_CASE(SDL_wcstoul, unsigned long, "%lu", L"-4294967295", 10, 1, 11);
+    }
+
+    const bool long_long_is_64bit = sizeof(long long) == 8;
+    if (long_long_is_64bit) {
+        WCSTOX_TEST_CASE(SDL_wcstoll, long long, FMT_PRILLd, L"0", 0, 0LL, 1);
+        WCSTOX_TEST_CASE(SDL_wcstoll, long long, FMT_PRILLd, L"0", 10, 0LL, 1);
+        WCSTOX_TEST_CASE(SDL_wcstoll, long long, FMT_PRILLd, L"-0", 0, 0LL, 2);
+        WCSTOX_TEST_CASE(SDL_wcstoll, long long, FMT_PRILLd, L"-0", 10, 0LL, 2);
+        WCSTOX_TEST_CASE(SDL_wcstoll, long long, FMT_PRILLd, L"9223372036854775807", 10, 9223372036854775807LL, 19);
+        WCSTOX_TEST_CASE(SDL_wcstoll, long long, FMT_PRILLd, L"9223372036854775808", 10, 9223372036854775807LL, 19);
+        WCSTOX_TEST_CASE(SDL_wcstoll, long long, FMT_PRILLd, L"-9223372036854775808", 10, -9223372036854775807LL - 1, 20);
+        WCSTOX_TEST_CASE(SDL_wcstoll, long long, FMT_PRILLd, L"-9223372036854775809", 10, -9223372036854775807LL - 1, 20);
+        WCSTOX_TEST_CASE(SDL_wcstoll, long long, FMT_PRILLd, L"-9999999999999999999999999999999999999999", 10, -9223372036854775807LL - 1, 41);
+
+        WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLd, L"18446744073709551615", 10, 18446744073709551615ULL, 20);
+        WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLd, L"18446744073709551616", 10, 18446744073709551615ULL, 20);
+        WCSTOX_TEST_CASE(SDL_wcstoull, unsigned long long, FMT_PRILLd, L"-18446744073709551615", 10, 1, 21);
     }
 
-#undef WCSTOL_TEST_CASE
+#undef WCSTOX_TEST_CASE
 
     return TEST_COMPLETED;
 }
@@ -1494,8 +1528,8 @@ static const SDLTest_TestCaseReference stdlibTest_strpbrk = {
     stdlib_strpbrk, "stdlib_strpbrk", "Calls to SDL_strpbrk", TEST_ENABLED
 };
 
-static const SDLTest_TestCaseReference stdlibTest_wcstol = {
-    stdlib_wcstol, "stdlib_wcstol", "Calls to SDL_wcstol", TEST_ENABLED
+static const SDLTest_TestCaseReference stdlibTest_wcstox = {
+    stdlib_wcstox, "stdlib_wcstox", "Calls to SDL_wcstol, SDL_wcstoul, SDL_wcstoll, and SDL_wcstoull", TEST_ENABLED
 };
 
 static const SDLTest_TestCaseReference stdlibTest_strtox = {
@@ -1519,7 +1553,7 @@ static const SDLTest_TestCaseReference *stdlibTests[] = {
     &stdlibTestOverflow,
     &stdlibTest_iconv,
     &stdlibTest_strpbrk,
-    &stdlibTest_wcstol,
+    &stdlibTest_wcstox,
     &stdlibTest_strtox,
     &stdlibTest_strtod,
     NULL