From 3877b9b94f2b15838d8f9e4886c4fb8f063283af Mon Sep 17 00:00:00 2001 From: mohammed arib Date: Wed, 1 Jul 2026 13:02:39 +0530 Subject: [PATCH] re2: parse float/double args with locale-independent from_chars --- re2/re2.cc | 24 ++++++++++++++---------- re2/testing/re2_test.cc | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/re2/re2.cc b/re2/re2.cc index 2e00d2285..252253931 100644 --- a/re2/re2.cc +++ b/re2/re2.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,7 @@ #include "absl/log/absl_check.h" #include "absl/log/absl_log.h" #include "absl/strings/ascii.h" +#include "absl/strings/charconv.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "re2/prog.h" @@ -1176,11 +1178,12 @@ bool Parse(const char* str, size_t n, float* dest) { static const int kMaxLength = 200; char buf[kMaxLength+1]; str = TerminateNumber(buf, sizeof buf, str, &n, true); - char* end; - errno = 0; - float r = strtof(str, &end); - if (end != str + n) return false; // Leftover junk - if (errno) return false; + if (str[0] == '\0') return false; + // Use absl::from_chars() instead of strtof() to avoid the radix character + // (and thus the accepted grammar) depending on the process's LC_NUMERIC. + float r; + absl::from_chars_result res = absl::from_chars(str, str + n, r); + if (res.ec != std::errc() || res.ptr != str + n) return false; // Leftover junk if (dest == NULL) return true; *dest = r; return true; @@ -1192,11 +1195,12 @@ bool Parse(const char* str, size_t n, double* dest) { static const int kMaxLength = 200; char buf[kMaxLength+1]; str = TerminateNumber(buf, sizeof buf, str, &n, true); - char* end; - errno = 0; - double r = strtod(str, &end); - if (end != str + n) return false; // Leftover junk - if (errno) return false; + if (str[0] == '\0') return false; + // Use absl::from_chars() instead of strtod() to avoid the radix character + // (and thus the accepted grammar) depending on the process's LC_NUMERIC. + double r; + absl::from_chars_result res = absl::from_chars(str, str + n, r); + if (res.ec != std::errc() || res.ptr != str + n) return false; // Leftover junk if (dest == NULL) return true; *dest = r; return true; diff --git a/re2/testing/re2_test.cc b/re2/testing/re2_test.cc index 5c1822d7f..1f087ba9f 100644 --- a/re2/testing/re2_test.cc +++ b/re2/testing/re2_test.cc @@ -8,6 +8,7 @@ #include "re2/re2.h" #include +#include #include #include #include @@ -949,6 +950,37 @@ TEST(RE2, FloatingPointFullMatchTypes) { } } +TEST(RE2, FloatingPointFullMatchIsLocaleIndependent) { + // Parsing must not depend on LC_NUMERIC: '.' is always the radix character + // regardless of the process locale. Skip if no comma-radix locale is present. + const char* saved = setlocale(LC_NUMERIC, NULL); + std::string savedcopy = saved != NULL ? saved : "C"; + static const char* const kCommaLocales[] = { + "de_DE.UTF-8", "de_DE.utf8", "de_DE", "fr_FR.UTF-8", "nl_NL.UTF-8", + }; + bool have_locale = false; + for (const char* name : kCommaLocales) { + if (setlocale(LC_NUMERIC, name) != NULL) { + have_locale = true; + break; + } + } + if (!have_locale) + GTEST_SKIP() << "no comma-radix locale available"; + + double d = 0; + EXPECT_TRUE(RE2::FullMatch("1.5", "(.*)", &d)); + EXPECT_EQ(d, 1.5); + EXPECT_FALSE(RE2::FullMatch("1,5", "(.*)", &d)); + + float f = 0; + EXPECT_TRUE(RE2::FullMatch("1.5", "(.*)", &f)); + EXPECT_EQ(f, 1.5f); + EXPECT_FALSE(RE2::FullMatch("1,5", "(.*)", &f)); + + setlocale(LC_NUMERIC, savedcopy.c_str()); +} + TEST(RE2, FullMatchAnchored) { int i; // Check that matching is fully anchored