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