diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h index 14e0750995..cbd374432a 100644 --- a/include/SDL3/SDL_stdinc.h +++ b/include/SDL3/SDL_stdinc.h @@ -2899,14 +2899,70 @@ extern SDL_DECLSPEC float SDLCALL SDL_tanf(float x); #define SDL_ICONV_EILSEQ (size_t)-3 #define SDL_ICONV_EINVAL (size_t)-4 -/* SDL_iconv_* are now always real symbols/types, not macros or inlined. */ typedef struct SDL_iconv_data_t *SDL_iconv_t; + +/** + * This function allocates a context for the specified character set conversion. + * + * \param tocode The target character encoding, must not be NULL. + * \param fromcode The source character encoding, must not be NULL. + * \returns a handle that must be freed with SDL_iconv_close, + * or SDL_ICONV_ERROR on failure. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_iconv + * \sa SDL_iconv_close + * \sa SDL_iconv_string + */ extern SDL_DECLSPEC SDL_iconv_t SDLCALL SDL_iconv_open(const char *tocode, const char *fromcode); + +/** + * This function frees a context used for character set conversion. + * + * \param cd The character set conversion handle. + * \returns 0 on success, or -1 on failure. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_iconv + * \sa SDL_iconv_open + * \sa SDL_iconv_string + */ extern SDL_DECLSPEC int SDLCALL SDL_iconv_close(SDL_iconv_t cd); + +/** + * This function converts text between encodings, reading from and writing to a buffer. + * It returns the number of succesful conversions. + * + * \param cd The character set conversion context, created in SDL_iconv_open(). + * \param inbuf Address of variable that points to the first character of the input sequence. + * \param inbytesleft The number of bytes in the input buffer. + * \param outbuf Address of variable that points to the output buffer. + * \param outbytesleft The number of bytes in the output buffer. + * \returns the number of conversions on success, else + * SDL_ICONV_E2BIG is returned when the output buffer is too small, or + * SDL_ICONV_EILSEQ is returned when an invalid input sequence is encountered, or + * SDL_ICONV_EINVAL is returned when an incomplete input sequence is encountered. + * + * On exit: + * - inbuf will point to the beginning of the next multibyte sequence. + * On error, this is the location of the problematic input sequence. + * On success, this is the end of the input sequence. + * - inbytesleft will be set to the number of bytes left to convert, which will be 0 on success. + * - outbuf will point to the location where to store the next output byte. + * - outbytesleft will be set to the number of bytes left in the output buffer. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_iconv_open + * \sa SDL_iconv_close + * \sa SDL_iconv_string + */ extern SDL_DECLSPEC size_t SDLCALL SDL_iconv(SDL_iconv_t cd, const char **inbuf, - size_t * inbytesleft, char **outbuf, - size_t * outbytesleft); + size_t *inbytesleft, char **outbuf, + size_t *outbytesleft); /** * Helper function to convert a string's encoding in one call. @@ -2928,6 +2984,10 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_iconv(SDL_iconv_t cd, const char **inbuf, * \returns a new string, converted to the new encoding, or NULL on error. * * \since This function is available since SDL 3.0.0. + * + * \sa SDL_iconv_open + * \sa SDL_iconv_close + * \sa SDL_iconv */ extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, const char *fromcode, diff --git a/src/stdlib/SDL_iconv.c b/src/stdlib/SDL_iconv.c index dfda45b480..b31ee0c42c 100644 --- a/src/stdlib/SDL_iconv.c +++ b/src/stdlib/SDL_iconv.c @@ -39,6 +39,9 @@ SDL_iconv_t SDL_iconv_open(const char *tocode, const char *fromcode) int SDL_iconv_close(SDL_iconv_t cd) { + if ((size_t)cd == SDL_ICONV_ERROR) { + return -1; + } return iconv_close((iconv_t)((uintptr_t)cd)); } @@ -46,6 +49,9 @@ size_t SDL_iconv(SDL_iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft) { + if ((size_t)cd == SDL_ICONV_ERROR) { + return SDL_ICONV_ERROR; + } /* iconv's second parameter may or may not be `const char const *` depending on the C runtime's whims. Casting to void * seems to make everyone happy, though. */ const size_t retCode = iconv((iconv_t)((uintptr_t)cd), (void *)inbuf, inbytesleft, outbuf, outbytesleft); @@ -236,6 +242,9 @@ size_t SDL_iconv(SDL_iconv_t cd, Uint32 ch = 0; size_t total; + if ((size_t)cd == SDL_ICONV_ERROR) { + return SDL_ICONV_ERROR; + } if (!inbuf || !*inbuf) { /* Reset the context */ return 0; @@ -769,9 +778,10 @@ size_t SDL_iconv(SDL_iconv_t cd, int SDL_iconv_close(SDL_iconv_t cd) { - if (cd != (SDL_iconv_t)-1) { - SDL_free(cd); + if (cd == (SDL_iconv_t)-1) { + return -1; } + SDL_free(cd); return 0; } diff --git a/test/testautomation_stdlib.c b/test/testautomation_stdlib.c index 44a0907264..5e649ab391 100644 --- a/test/testautomation_stdlib.c +++ b/test/testautomation_stdlib.c @@ -1064,6 +1064,130 @@ stdlib_overflow(void *arg) return TEST_COMPLETED; } +static void format_for_description(char *buffer, size_t buflen, const char *text) { + if (text == NULL) { + SDL_strlcpy(buffer, "NULL", buflen); + } else { + SDL_snprintf(buffer, buflen, "\"%s\"", text); + } +} + +static int +stdlib_iconv(void *arg) +{ + struct { + SDL_bool expect_success; + const char *from_encoding; + const char *text; + const char *to_encoding; + const char *expected; + } inputs[] = { + { SDL_FALSE, "bogus-from-encoding", NULL, "bogus-to-encoding", NULL }, + { SDL_FALSE, "bogus-from-encoding", "hello world", "bogus-to-encoding", NULL }, + { SDL_FALSE, "bogus-from-encoding", "hello world", "ascii", NULL }, + { SDL_TRUE, "utf-8", NULL, "ascii", "" }, + { SDL_TRUE, "utf-8", "hello world", "ascii", "hello world" }, + { SDL_TRUE, "utf-8", "\xe2\x8c\xa8\xf0\x9f\x92\xbb", "utf-16le", "\x28\x23\x3d\xd8\xbb\xdc\x00" }, + }; + SDL_iconv_t cd; + size_t i; + + for (i = 0; i < SDL_arraysize(inputs); i++) { + char to_encoding_str[32]; + char from_encoding_str[32]; + char text_str[32]; + size_t len_text = 0; + int r; + char out_buffer[6]; + const char *in_ptr; + size_t in_pos; + char *out_ptr; + char *output; + size_t iconv_result; + size_t out_len; + SDL_bool is_error; + size_t out_pos; + + SDLTest_AssertPass("case %d", (int)i); + format_for_description(to_encoding_str, SDL_arraysize(to_encoding_str), inputs[i].to_encoding); + format_for_description(from_encoding_str, SDL_arraysize(from_encoding_str), inputs[i].from_encoding); + format_for_description(text_str, SDL_arraysize(text_str), inputs[i].text); + + if (inputs[i].text) { + len_text = SDL_strlen(inputs[i].text) + 1; + } + + SDLTest_AssertPass("About to call SDL_iconv_open(%s, %s)", to_encoding_str, from_encoding_str); + cd = SDL_iconv_open(inputs[i].to_encoding, inputs[i].from_encoding); + if (inputs[i].expect_success) { + SDLTest_AssertCheck(cd != (SDL_iconv_t)SDL_ICONV_ERROR, "result must NOT be SDL_ICONV_ERROR"); + } else { + SDLTest_AssertCheck(cd == (SDL_iconv_t)SDL_ICONV_ERROR, "result must be SDL_ICONV_ERROR"); + } + + in_ptr = inputs[i].text; + in_pos = 0; + out_pos = 0; + do { + size_t in_left; + size_t count_written; + size_t count_read; + + in_left = len_text - in_pos; + out_ptr = out_buffer; + out_len = SDL_arraysize(out_buffer); + SDLTest_AssertPass("About to call SDL_iconv(cd, %s+%d, .., dest, ..)", text_str, (int)in_pos); + iconv_result = SDL_iconv(cd, &in_ptr, &in_left, &out_ptr, &out_len); + count_written = SDL_arraysize(out_buffer) - out_len; + count_read = in_ptr - inputs[i].text - in_pos; + in_pos += count_read; + + is_error = iconv_result == SDL_ICONV_ERROR + || iconv_result == SDL_ICONV_EILSEQ + || iconv_result == SDL_ICONV_EINVAL; + if (inputs[i].expect_success) { + SDLTest_AssertCheck(!is_error, "result must NOT be an error code"); + SDLTest_AssertCheck(count_written > 0 || inputs[i].expected[out_pos] == '\0', "%" SDL_PRIu64 " bytes have been written", (Uint64)count_written); + SDLTest_AssertCheck(out_pos <= SDL_strlen(inputs[i].expected), "Data written by SDL_iconv cannot be longer then reference output"); + SDLTest_CompareMemory(out_buffer, count_written, inputs[i].expected + out_pos, count_written); + } else { + SDLTest_AssertCheck(is_error, "result must be an error code"); + break; + } + out_pos += count_written; + if (count_written == 0) { + break; + } + if (count_read == 0) { + SDLTest_AssertCheck(SDL_FALSE, "SDL_iconv wrote data, but read no data"); + break; + } + } while (!is_error && in_pos < len_text); + + SDLTest_AssertPass("About to call SDL_iconv_close(cd)"); + r = SDL_iconv_close(cd); + if (inputs[i].expect_success) { + SDLTest_AssertCheck(r == 0, "result must be 0"); + } else { + SDLTest_AssertCheck(r == -1, "result must be -1"); + } + + SDLTest_AssertPass("About to call SDL_iconv_string(%s, %s, %s, %" SDL_PRIu64 ")", + to_encoding_str, from_encoding_str, text_str, (Uint64)len_text); + output = SDL_iconv_string(inputs[i].to_encoding, inputs[i].from_encoding, inputs[i].text, len_text); + if (inputs[i].expect_success) { + SDLTest_AssertCheck(output != NULL, "result must NOT be NULL"); + SDLTest_AssertCheck(SDL_strncmp(inputs[i].expected, output, SDL_strlen(inputs[i].expected)) == 0, + "converted string should be correct"); + } else { + SDLTest_AssertCheck(output == NULL, "result must be NULL"); + } + SDL_free(output); + } + + return TEST_COMPLETED; +} + /* ================= Test References ================== */ /* Standard C routine test cases */ @@ -1103,6 +1227,10 @@ static const SDLTest_TestCaseReference stdlibTestOverflow = { stdlib_overflow, "stdlib_overflow", "Overflow detection", TEST_ENABLED }; +static const SDLTest_TestCaseReference stdlibIconv = { + stdlib_iconv, "stdlib_iconv", "Calls to iconv", TEST_ENABLED +}; + /* Sequence of Standard C routine test cases */ static const SDLTest_TestCaseReference *stdlibTests[] = { &stdlibTest_strnlen, @@ -1114,6 +1242,7 @@ static const SDLTest_TestCaseReference *stdlibTests[] = { &stdlibTest_sscanf, &stdlibTest_aligned_alloc, &stdlibTestOverflow, + &stdlibIconv, NULL };