• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2023 Google LLC.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 #include "upb/lex/strtod.h"
9 
10 #include <stdlib.h>
11 #include <string.h>
12 
13 // Must be last.
14 #include "upb/port/def.inc"
15 
16 // Determine the locale-specific radix character by calling sprintf() to print
17 // the number 1.5, then stripping off the digits.  As far as I can tell, this
18 // is the only portable, thread-safe way to get the C library to divulge the
19 // locale's radix character.  No, localeconv() is NOT thread-safe.
20 
GetLocaleRadix(char * data,size_t capacity)21 static int GetLocaleRadix(char *data, size_t capacity) {
22   char temp[16];
23   const int size = snprintf(temp, sizeof(temp), "%.1f", 1.5);
24   UPB_ASSERT(temp[0] == '1');
25   UPB_ASSERT(temp[size - 1] == '5');
26   UPB_ASSERT(size < capacity);
27   temp[size - 1] = '\0';
28   strcpy(data, temp + 1);
29   return size - 2;
30 }
31 
32 // Populates a string identical to *input except that the character pointed to
33 // by pos (which should be '.') is replaced with the locale-specific radix.
34 
LocalizeRadix(const char * input,const char * pos,char * output)35 static void LocalizeRadix(const char *input, const char *pos, char *output) {
36   const int len1 = pos - input;
37 
38   char radix[8];
39   const int len2 = GetLocaleRadix(radix, sizeof(radix));
40 
41   memcpy(output, input, len1);
42   memcpy(output + len1, radix, len2);
43   strcpy(output + len1 + len2, input + len1 + 1);
44 }
45 
_upb_NoLocaleStrtod(const char * str,char ** endptr)46 double _upb_NoLocaleStrtod(const char *str, char **endptr) {
47   // We cannot simply set the locale to "C" temporarily with setlocale()
48   // as this is not thread-safe.  Instead, we try to parse in the current
49   // locale first.  If parsing stops at a '.' character, then this is a
50   // pretty good hint that we're actually in some other locale in which
51   // '.' is not the radix character.
52 
53   char *temp_endptr;
54   double result = strtod(str, &temp_endptr);
55   if (endptr != NULL) *endptr = temp_endptr;
56   if (*temp_endptr != '.') return result;
57 
58   // Parsing halted on a '.'.  Perhaps we're in a different locale?  Let's
59   // try to replace the '.' with a locale-specific radix character and
60   // try again.
61 
62   char localized[80];
63   LocalizeRadix(str, temp_endptr, localized);
64   char *localized_endptr;
65   result = strtod(localized, &localized_endptr);
66   if ((localized_endptr - &localized[0]) > (temp_endptr - str)) {
67     // This attempt got further, so replacing the decimal must have helped.
68     // Update endptr to point at the right location.
69     if (endptr != NULL) {
70       // size_diff is non-zero if the localized radix has multiple bytes.
71       int size_diff = strlen(localized) - strlen(str);
72       *endptr = (char *)str + (localized_endptr - &localized[0] - size_diff);
73     }
74   }
75 
76   return result;
77 }
78