]> Savannah Git Hosting - gnulib.git/commitdiff
c-strtod, c-strtold: Make multithread-safe.
authorBruno Haible <bruno@clisp.org>
Tue, 5 Mar 2024 16:10:38 +0000 (17:10 +0100)
committerBruno Haible <bruno@clisp.org>
Sat, 16 Mar 2024 10:44:22 +0000 (11:44 +0100)
* lib/c-strtod.c: Include <langinfo.h>, 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.

ChangeLog
lib/c-strtod.c
m4/c-strtod.m4
modules/c-strtod
modules/c-strtold

index 86bc81228a44ed9a277d50077ec294afdfc967e6..8ec375982f484eb681e70200dc78636b06a5626f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2024-03-05  Bruno Haible  <bruno@clisp.org>
+
+       c-strtod, c-strtold: Make multithread-safe.
+       * lib/c-strtod.c: Include <langinfo.h>, 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  <bruno@clisp.org>
 
        isnan: Fix compilation error in C++ mode on OpenBSD 7.5-beta.
index f955434eaa94e3315e3619fc1ba282bfa5fa1bf9..0a22eed175ad6f72a43c28a9d479d5212eedac8c 100644 (file)
@@ -57,6 +57,35 @@ c_locale (void)
   return c_locale_cache;
 }
 
+#else
+
+# if HAVE_NL_LANGINFO
+#  include <langinfo.h>
+# 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
+         <https://pubs.opengroup.org/onlinepubs/9699919799/functions/strtod.html>.
+       */
+      {
+        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
index 746b39bb445d866b46493cece60b73d3811bcc2a..aa53958b9e51e85cad31aa2c2dcbce94d8556000 100644 (file)
@@ -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 <langinfo.h>]])
 
   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 <langinfo.h>]])
+
   gl_CHECK_FUNCS_ANDROID([strtold_l], [[#include <stdlib.h>]])
 ])
index a2741c557170059329840bc6ea8f9729d445e6fe..2ca8f2a2f2e4944c0fafff9d3680bd1806b762ad 100644 (file)
@@ -11,7 +11,7 @@ Depends-on:
 c99
 extensions
 locale
-strdup-posix
+c-ctype
 strtod
 
 configure.ac:
index f642a8895287f173a29e7d8f10182221803f4071..0ec27d7983105142b9c2fb2f8cecee986d07c0b0 100644 (file)
@@ -12,7 +12,7 @@ Depends-on:
 c99
 extensions
 locale
-strdup-posix
+c-ctype
 strtold
 
 configure.ac: