From: Bruno Haible Date: Tue, 5 Mar 2024 16:10:38 +0000 (+0100) Subject: c-strtod, c-strtold: Make multithread-safe. X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=2340b31a5becc9dae937244b3be01790389b7dae;p=gnulib.git c-strtod, c-strtold: Make multithread-safe. * lib/c-strtod.c: Include , c-ctype.h. (decimal_point_char): New function, copied from lib/vasnprintf.c. (C_STRTOD): On platforms that don't have STRTOD_L nor a working uselocale(), pre-parse the number and call STRTOD after having replaced the '.' with the locale-dependent decimal point character. * m4/c-strtod.m4 (gl_C_STRTOD, gl_C_STRTOLD): Test for nl_langinfo(). * modules/c-strtod (Depends-on): Add c-ctype. Remove strdup-posix. * modules/c-strtold (Depends-on): Likewise. --- diff --git a/ChangeLog b/ChangeLog index 86bc81228a..8ec375982f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2024-03-05 Bruno Haible + + c-strtod, c-strtold: Make multithread-safe. + * lib/c-strtod.c: Include , c-ctype.h. + (decimal_point_char): New function, copied from lib/vasnprintf.c. + (C_STRTOD): On platforms that don't have STRTOD_L nor a working + uselocale(), pre-parse the number and call STRTOD after having replaced + the '.' with the locale-dependent decimal point character. + * m4/c-strtod.m4 (gl_C_STRTOD, gl_C_STRTOLD): Test for nl_langinfo(). + * modules/c-strtod (Depends-on): Add c-ctype. Remove strdup-posix. + * modules/c-strtold (Depends-on): Likewise. + 2024-02-27 Bruno Haible isnan: Fix compilation error in C++ mode on OpenBSD 7.5-beta. diff --git a/lib/c-strtod.c b/lib/c-strtod.c index f955434eaa..0a22eed175 100644 --- a/lib/c-strtod.c +++ b/lib/c-strtod.c @@ -57,6 +57,35 @@ c_locale (void) return c_locale_cache; } +#else + +# if HAVE_NL_LANGINFO +# include +# endif +# include "c-ctype.h" + +/* Determine the decimal-point character according to the current locale. */ +static char +decimal_point_char (void) +{ + const char *point; + /* Determine it in a multithread-safe way. We know nl_langinfo is + multithread-safe on glibc systems and Mac OS X systems, but is not required + to be multithread-safe by POSIX. sprintf(), however, is multithread-safe. + localeconv() is rarely multithread-safe. */ +# if HAVE_NL_LANGINFO && (__GLIBC__ || defined __UCLIBC__ || (defined __APPLE__ && defined __MACH__)) + point = nl_langinfo (RADIXCHAR); +# elif 1 + char pointbuf[5]; + sprintf (pointbuf, "%#.0f", 1.0); + point = &pointbuf[1]; +# else + point = localeconv () -> decimal_point; +# endif + /* The decimal point is always a single byte: either '.' or ','. */ + return (point[0] != '\0' ? point[0] : '.'); +} + #endif DOUBLE @@ -100,29 +129,201 @@ C_STRTOD (char const *nptr, char **endptr) #else - char *saved_locale = setlocale (LC_NUMERIC, NULL); + char decimal_point = decimal_point_char (); - if (saved_locale) + if (decimal_point == '.') + /* In this locale, C_STRTOD and STRTOD behave the same way. */ + r = STRTOD (nptr, endptr); + else { - saved_locale = strdup (saved_locale); - if (saved_locale == NULL) + /* Start and end of the floating-point number. */ + char const *start; + char const *end; + /* Position of the decimal point '.' in the floating-point number. + Either decimal_point_p == NULL or start <= decimal_point_p < end. */ + char const *decimal_point_p = NULL; + /* Set to true if we encountered decimal_point while parsing. */ + int seen_decimal_point = 0; + + /* Parse + 1. a sequence of white-space characters, + 2. a subject sequence possibly containing a floating-point number, + as described in POSIX + . + */ + { + char const *p; + + p = nptr; + + /* Parse a sequence of white-space characters. */ + while (*p != '\0' && c_isspace ((unsigned char) *p)) + p++; + start = p; + end = p; + + /* Start to parse a subject sequence. */ + if (*p == '+' || *p == '-') + p++; + if (*p == '0') + { + end = p + 1; + if (p[1] == 'x' || p[1] == 'X') + { + size_t num_hex_digits = 0; + p += 2; + /* Parse a non-empty sequence of hexadecimal digits optionally + containing the decimal point character '.'. */ + while (*p != '\0') + { + if (c_isxdigit ((unsigned char) *p)) + { + num_hex_digits++; + p++; + } + else if (*p == '.') + { + if (decimal_point_p == NULL) + { + decimal_point_p = p; + p++; + } + else + break; + } + else + break; + } + seen_decimal_point = (*p == decimal_point); + if (num_hex_digits > 0) + { + end = p; + /* Parse the character 'p' or the character 'P', optionally + followed by a '+' or '-' character, and then followed by + one or more decimal digits. */ + if (*p == 'p' || *p == 'P') + { + p++; + if (*p == '+' || *p == '-') + p++; + if (*p != '\0' && c_isdigit ((unsigned char) *p)) + { + p++; + while (*p != '\0' && c_isdigit ((unsigned char) *p)) + p++; + end = p; + } + } + } + else + decimal_point_p = NULL; + goto done_parsing; + } + } { - if (endptr) - *endptr = (char *) nptr; - return 0; /* errno is set here */ + size_t num_digits = 0; + /* Parse a non-empty sequence of decimal digits optionally containing + the decimal point character '.'. */ + while (*p != '\0') + { + if (c_isdigit ((unsigned char) *p)) + { + num_digits++; + p++; + } + else if (*p == '.') + { + if (decimal_point_p == NULL) + { + decimal_point_p = p; + p++; + } + else + break; + } + else + break; + } + seen_decimal_point = (*p == decimal_point); + if (num_digits > 0) + { + end = p; + /* Parse the character 'e' or the character 'E', optionally + followed by a '+' or '-' character, and then followed by one or + more decimal digits. */ + if (*p == 'e' || *p == 'E') + { + p++; + if (*p == '+' || *p == '-') + p++; + if (*p != '\0' && c_isdigit ((unsigned char) *p)) + { + p++; + while (*p != '\0' && c_isdigit ((unsigned char) *p)) + p++; + end = p; + } + } + } + else + decimal_point_p = NULL; } - setlocale (LC_NUMERIC, "C"); - } + } + done_parsing: + if (end == start || (decimal_point_p == NULL && !seen_decimal_point)) + /* If end == start, we have not parsed anything. The given string + might be "INF", "INFINITY", "NAN", "NAN(chars)", or invalid. + We can pass it to STRTOD. + If end > start and decimal_point_p == NULL, we have parsed a + floating-point number, and it does not have a '.' (and not a ',' + either, of course). If !seen_decimal_point, we did not + encounter decimal_point while parsing. In this case, the + locale-dependent STRTOD function can handle it. */ + r = STRTOD (nptr, endptr); + else + { + /* Create a modified floating-point number, in which the character '.' + is replaced with the locale-dependent decimal_point. */ + size_t len = end - start; + char *buf; + char *buf_malloced = NULL; + char stackbuf[1000]; + char *end_in_buf; - r = STRTOD (nptr, endptr); + if (len < sizeof (stackbuf)) + buf = stackbuf; + else + { + buf = malloc (len + 1); + if (buf == NULL) + { + /* Out of memory. + Callers may not be prepared to see errno == ENOMEM. But + they should be prepared to see errno == EINVAL. */ + errno = EINVAL; + if (endptr != NULL) + *endptr = (char *) nptr; + return 0; + } + buf_malloced = buf; + } - if (saved_locale) - { - int saved_errno = errno; + memcpy (buf, start, len); + if (decimal_point_p != NULL) + buf[decimal_point_p - start] = decimal_point; + buf[len] = '\0'; - setlocale (LC_NUMERIC, saved_locale); - free (saved_locale); - errno = saved_errno; + r = STRTOD (buf, &end_in_buf); + if (endptr != NULL) + *endptr = (char *) (start + (end_in_buf - buf)); + + if (buf_malloced != NULL) + { + int saved_errno = errno; + free (buf_malloced); + errno = saved_errno; + } + } } #endif diff --git a/m4/c-strtod.m4 b/m4/c-strtod.m4 index 746b39bb44..aa53958b9e 100644 --- a/m4/c-strtod.m4 +++ b/m4/c-strtod.m4 @@ -1,6 +1,6 @@ -# c-strtod.m4 serial 19 +# c-strtod.m4 serial 19.1 -# Copyright (C) 2004-2006, 2009-2023 Free Software Foundation, Inc. +# Copyright (C) 2004-2006, 2009-2024 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -12,6 +12,7 @@ AC_DEFUN([gl_C_STRTOD], [ AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) AC_REQUIRE([gt_FUNC_USELOCALE]) + gl_CHECK_FUNCS_ANDROID([nl_langinfo], [[#include ]]) AC_CHECK_HEADERS_ONCE([xlocale.h]) dnl We can't use AC_CHECK_FUNC here, because strtod_l() is defined as a @@ -47,5 +48,7 @@ AC_DEFUN([gl_C_STRTOLD], [ AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) AC_REQUIRE([gt_FUNC_USELOCALE]) + gl_CHECK_FUNCS_ANDROID([nl_langinfo], [[#include ]]) + gl_CHECK_FUNCS_ANDROID([strtold_l], [[#include ]]) ]) diff --git a/modules/c-strtod b/modules/c-strtod index a2741c5571..2ca8f2a2f2 100644 --- a/modules/c-strtod +++ b/modules/c-strtod @@ -11,7 +11,7 @@ Depends-on: c99 extensions locale -strdup-posix +c-ctype strtod configure.ac: diff --git a/modules/c-strtold b/modules/c-strtold index f642a88952..0ec27d7983 100644 --- a/modules/c-strtold +++ b/modules/c-strtold @@ -12,7 +12,7 @@ Depends-on: c99 extensions locale -strdup-posix +c-ctype strtold configure.ac: