mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-05-30 08:27:39 +00:00
Document iconv functions + add testautomation (#10131)
* stdinc: document SDL_iconv* functions * iconv: add automation tests * iconv: don't potentially crash on invalid inputs
This commit is contained in:
parent
75d89f8e12
commit
0fa2049fef
3 changed files with 204 additions and 5 deletions
|
@ -2899,14 +2899,70 @@ extern SDL_DECLSPEC float SDLCALL SDL_tanf(float x);
|
||||||
#define SDL_ICONV_EILSEQ (size_t)-3
|
#define SDL_ICONV_EILSEQ (size_t)-3
|
||||||
#define SDL_ICONV_EINVAL (size_t)-4
|
#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;
|
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,
|
extern SDL_DECLSPEC SDL_iconv_t SDLCALL SDL_iconv_open(const char *tocode,
|
||||||
const char *fromcode);
|
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);
|
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,
|
extern SDL_DECLSPEC size_t SDLCALL SDL_iconv(SDL_iconv_t cd, const char **inbuf,
|
||||||
size_t * inbytesleft, char **outbuf,
|
size_t *inbytesleft, char **outbuf,
|
||||||
size_t * outbytesleft);
|
size_t *outbytesleft);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to convert a string's encoding in one call.
|
* 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.
|
* \returns a new string, converted to the new encoding, or NULL on error.
|
||||||
*
|
*
|
||||||
* \since This function is available since SDL 3.0.0.
|
* \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,
|
extern SDL_DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode,
|
||||||
const char *fromcode,
|
const char *fromcode,
|
||||||
|
|
|
@ -39,6 +39,9 @@ SDL_iconv_t SDL_iconv_open(const char *tocode, const char *fromcode)
|
||||||
|
|
||||||
int SDL_iconv_close(SDL_iconv_t cd)
|
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));
|
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,
|
const char **inbuf, size_t *inbytesleft,
|
||||||
char **outbuf, size_t *outbytesleft)
|
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
|
/* 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. */
|
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);
|
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;
|
Uint32 ch = 0;
|
||||||
size_t total;
|
size_t total;
|
||||||
|
|
||||||
|
if ((size_t)cd == SDL_ICONV_ERROR) {
|
||||||
|
return SDL_ICONV_ERROR;
|
||||||
|
}
|
||||||
if (!inbuf || !*inbuf) {
|
if (!inbuf || !*inbuf) {
|
||||||
/* Reset the context */
|
/* Reset the context */
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -769,9 +778,10 @@ size_t SDL_iconv(SDL_iconv_t cd,
|
||||||
|
|
||||||
int SDL_iconv_close(SDL_iconv_t cd)
|
int SDL_iconv_close(SDL_iconv_t cd)
|
||||||
{
|
{
|
||||||
if (cd != (SDL_iconv_t)-1) {
|
if (cd == (SDL_iconv_t)-1) {
|
||||||
SDL_free(cd);
|
return -1;
|
||||||
}
|
}
|
||||||
|
SDL_free(cd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1064,6 +1064,130 @@ stdlib_overflow(void *arg)
|
||||||
return TEST_COMPLETED;
|
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 ================== */
|
/* ================= Test References ================== */
|
||||||
|
|
||||||
/* Standard C routine test cases */
|
/* Standard C routine test cases */
|
||||||
|
@ -1103,6 +1227,10 @@ static const SDLTest_TestCaseReference stdlibTestOverflow = {
|
||||||
stdlib_overflow, "stdlib_overflow", "Overflow detection", TEST_ENABLED
|
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 */
|
/* Sequence of Standard C routine test cases */
|
||||||
static const SDLTest_TestCaseReference *stdlibTests[] = {
|
static const SDLTest_TestCaseReference *stdlibTests[] = {
|
||||||
&stdlibTest_strnlen,
|
&stdlibTest_strnlen,
|
||||||
|
@ -1114,6 +1242,7 @@ static const SDLTest_TestCaseReference *stdlibTests[] = {
|
||||||
&stdlibTest_sscanf,
|
&stdlibTest_sscanf,
|
||||||
&stdlibTest_aligned_alloc,
|
&stdlibTest_aligned_alloc,
|
||||||
&stdlibTestOverflow,
|
&stdlibTestOverflow,
|
||||||
|
&stdlibIconv,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue