[PATCH] msvcrt: Fix strtof() error reporting for values out of float range
If the values weren't out of range for the internal strtod() call, we still must mark them as out of range when returning as float. Signed-off-by: Martin Storsjo <martin(a)martin.st> --- dlls/msvcrt/string.c | 15 ++++++++++++++- dlls/msvcrt/wcs.c | 16 +++++++++++++++- dlls/ucrtbase/tests/string.c | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/dlls/msvcrt/string.c b/dlls/msvcrt/string.c index 27e3284326a..823e1e77b9a 100644 --- a/dlls/msvcrt/string.c +++ b/dlls/msvcrt/string.c @@ -1065,7 +1065,20 @@ double CDECL strtod( const char *str, char **end ) */ float CDECL _strtof_l( const char *str, char **end, _locale_t locale ) { - return _strtod_l(str, end, locale); + double ret = _strtod_l(str, end, locale); + if (isfinite(ret)) { + /* Check for cases that aren't out of range for doubles, but that are + * for floats. */ + if (ret > FLT_MAX) + *_errno() = ERANGE; + else if (ret < -FLT_MAX) + *_errno() = ERANGE; + else if (ret > 0 && ret < FLT_MIN) + *_errno() = ERANGE; + else if (ret < 0 && ret > -FLT_MIN) + *_errno() = ERANGE; + } + return ret; } /********************************************************************* diff --git a/dlls/msvcrt/wcs.c b/dlls/msvcrt/wcs.c index 615b4f36a33..1baed5d1458 100644 --- a/dlls/msvcrt/wcs.c +++ b/dlls/msvcrt/wcs.c @@ -23,6 +23,7 @@ #include <limits.h> #include <locale.h> #include <math.h> +#include <float.h> #include <assert.h> #include <wchar.h> #include <wctype.h> @@ -798,7 +799,20 @@ double CDECL _wtof_l(const wchar_t *str, _locale_t locale) */ float CDECL _wcstof_l( const wchar_t *str, wchar_t **end, _locale_t locale ) { - return _wcstod_l(str, end, locale); + double ret = _wcstod_l(str, end, locale); + if (isfinite(ret)) { + /* Check for cases that aren't out of range for doubles, but that are + * for floats. */ + if (ret > FLT_MAX) + *_errno() = ERANGE; + else if (ret < -FLT_MAX) + *_errno() = ERANGE; + else if (ret > 0 && ret < FLT_MIN) + *_errno() = ERANGE; + else if (ret < 0 && ret > -FLT_MIN) + *_errno() = ERANGE; + } + return ret; } /********************************************************************* diff --git a/dlls/ucrtbase/tests/string.c b/dlls/ucrtbase/tests/string.c index 9b1d21b801c..92626048410 100644 --- a/dlls/ucrtbase/tests/string.c +++ b/dlls/ucrtbase/tests/string.c @@ -152,6 +152,38 @@ static void test_strtod(void) test_strtod_str_errno("2.47e-324", 0, 9, ERANGE); } +static void test_strtof(void) +{ + static const struct { + const char *str; + int len; + float ret; + int err; + } tests[] = { + { "12.1", 4, 12.1f }, + { "-13.721", 7, -13.721f }, + { "1.e40", 5, INFINITY, ERANGE }, + { "-1.e40", 6, -INFINITY, ERANGE }, + { "1.e-60", 6, 0, ERANGE }, + { "-1.e-60", 7, 0, ERANGE }, + }; + + char *end; + float f; + int i; + + for (i=0; i<ARRAY_SIZE(tests); i++) + { + errno = 0xdeadbeef; + f = strtof(tests[i].str, &end); + ok(f == tests[i].ret, "%d) f = %.16e\n", i, f); + ok(end == tests[i].str + tests[i].len, "%d) len = %d\n", + i, (int)(end - tests[i].str)); + ok(errno == tests[i].err || (!tests[i].err && errno == 0xdeadbeef), + "%d) errno = %d\n", i, errno); + } +} + static void test__memicmp(void) { static const char *s1 = "abc"; @@ -558,6 +590,7 @@ START_TEST(string) "Invalid parameter handler was already set\n"); test_strtod(); + test_strtof(); test__memicmp(); test__memicmp_l(); test___strncnt(); -- 2.25.1
Hi Martin, On 7/28/21 12:44 AM, Martin Storsjo wrote:
float CDECL _strtof_l( const char *str, char **end, _locale_t locale ) { - return _strtod_l(str, end, locale); + double ret = _strtod_l(str, end, locale); + if (isfinite(ret)) { + /* Check for cases that aren't out of range for doubles, but that are + * for floats. */ + if (ret > FLT_MAX) + *_errno() = ERANGE; + else if (ret < -FLT_MAX) + *_errno() = ERANGE; + else if (ret > 0 && ret < FLT_MIN) + *_errno() = ERANGE; + else if (ret < 0 && ret > -FLT_MIN) + *_errno() = ERANGE; + } + return ret; It doesn't work for denormals (e.g. for "1.4e-45" input string). Maybe something along this lines will work better: if (ret && isfinite(ret)) { float f = ret; if (!f || !isfinite(f)) *_errno() = ERANGE; }
Thanks, Piotr
On Wed, 28 Jul 2021, Piotr Caban wrote:
Hi Martin,
On 7/28/21 12:44 AM, Martin Storsjo wrote:
float CDECL _strtof_l( const char *str, char **end, _locale_t locale ) { - return _strtod_l(str, end, locale); + double ret = _strtod_l(str, end, locale); + if (isfinite(ret)) { + /* Check for cases that aren't out of range for doubles, but that are + * for floats. */ + if (ret > FLT_MAX) + *_errno() = ERANGE; + else if (ret < -FLT_MAX) + *_errno() = ERANGE; + else if (ret > 0 && ret < FLT_MIN) + *_errno() = ERANGE; + else if (ret < 0 && ret > -FLT_MIN) + *_errno() = ERANGE; + } + return ret; It doesn't work for denormals (e.g. for "1.4e-45" input string).
Hmm, right... When I studied this aspect for mingw-w64, I noted that different CRTs seem to differ in what they do for denormals in strtod/strtof; some set ERANGE, some don't. But you're right that ucrtbase doesn't seem to set it for these denormals, so we shouldn't either.
Maybe something along this lines will work better: if (ret && isfinite(ret)) { float f = ret; if (!f || !isfinite(f)) *_errno() = ERANGE; }
Thanks! That does seem to work. I'll amend the patch to include testcases for 0 too, in addition to denormals. // Martin
participants (3)
-
Martin Storsjo -
Martin Storsjö -
Piotr Caban