mirror of
https://github.com/nlohmann/json.git
synced 2026-07-28 05:14:54 +00:00
Fix CI failures in from_chars locale handling (PR #5237)
Three issues in the extended-locale number parsing were breaking CI:
- On Windows, _create_locale() was called with LC_ALL_MASK, a POSIX-only
constant that doesn't exist in the Windows CRT; it needs LC_ALL there.
- `::isspace_l`/`::_isspace_l` were called with a leading `::`, but on
glibc, mingw-w64 and some other C libraries these are macros that expand
to a parenthesized expression, so `::` followed by the expansion is a
syntax error ("expected unqualified-id"). Dropping the `::` fixes this
on all affected toolchains (gcc/clang on Linux, nvcc, mingw).
- The Windows implementation assumed the full `_<funcname>_l` family is
always available, but some MinGW toolchains only provide a subset
(e.g. missing `_strtoll_l`/`_strtoull_l`/`_strtof_l`/`_strtold_l`).
The extended-locale fast path is now only used for genuine MSVC
(including clang-cl, which mimics the MSVC ABI/CRT); other Windows
toolchains fall back to the locale-dependent standard functions.
Also fixes a clang-tidy hicpp-named-parameter/readability-named-parameter
warning-as-error on two unnamed `init_val_t` test parameters.
Verified locally with gcc/clang on Linux, clang++ in C++20 mode, and
mingw-w64 (both g++ and clang targeting x86_64-w64-mingw32).
This commit is contained in:
@@ -190,6 +190,58 @@ inline from_chars_result from_chars(const char* first, const char* last, T& valu
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
/*!
|
||||
@brief Extended locale functions on Windows
|
||||
|
||||
A trait object which provides wrappers for isspace and the strto* family of
|
||||
functions, based on the platform's support for extended locale APIs.
|
||||
|
||||
This is the Windows implementation. The primary class template definition
|
||||
falls back to the standard locale-dependent functions, which is used on
|
||||
MinGW, whose C runtime only provides an incomplete subset of the
|
||||
`_<funcname>_l` family (e.g., missing `_strtof_l`/`_strtold_l` depending on
|
||||
the toolchain version). A specialization for genuine MSVC (including
|
||||
clang-cl, which mimics the MSVC ABI and CRT) is used instead, as the full
|
||||
`_<funcname>_l` family has reliably been available there since at least
|
||||
Visual Studio 2005.
|
||||
*/
|
||||
template <typename = const char*, typename = float>
|
||||
struct extended_locale_traits
|
||||
{
|
||||
static constexpr bool is_locale_independent = false;
|
||||
|
||||
JSON_HEDLEY_PURE
|
||||
static char get_decimal_point() noexcept
|
||||
{
|
||||
const auto* loc = localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
|
||||
}
|
||||
|
||||
static int isspace(int c) noexcept
|
||||
{
|
||||
return std::isspace(c);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
static long long strtoll(const char* str, char** endptr) noexcept
|
||||
{
|
||||
return std::strtoll(str, endptr, 10);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
static unsigned long long strtoull(const char* str, char** endptr) noexcept
|
||||
{
|
||||
return std::strtoull(str, endptr, 10);
|
||||
}
|
||||
|
||||
static constexpr float(&strtof)(const char*, char**) = std::strtof;
|
||||
static constexpr double(&strtod)(const char*, char**) = std::strtod;
|
||||
static constexpr long double(&strtold)(const char*, char**) = std::strtold;
|
||||
};
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
/*!
|
||||
@brief Get a pointer to a globally shared instance of the "C" locale on Windows
|
||||
|
||||
@@ -208,58 +260,51 @@ inline _locale_t get_c_locale_t() noexcept
|
||||
}
|
||||
};
|
||||
using LocaleUPtr = std::unique_ptr<std::remove_pointer<_locale_t>::type, LocaleTDeleter>;
|
||||
static const LocaleUPtr c_locale = LocaleUPtr{::_create_locale(LC_ALL_MASK, "C"), LocaleTDeleter{}};
|
||||
static const LocaleUPtr c_locale = LocaleUPtr{::_create_locale(LC_ALL, "C"), LocaleTDeleter{}};
|
||||
return c_locale.get();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Extended locale functions on Windows
|
||||
|
||||
A trait object which provides wrappers for isspace and the strto* family of
|
||||
functions, based on the platform's support for extended locale APIs.
|
||||
|
||||
This is the Windows implementation where extended locale support is always
|
||||
available through the `_<funcname>_l` variants.
|
||||
*/
|
||||
template <typename = const char*, typename = float>
|
||||
struct extended_locale_traits
|
||||
template <typename T>
|
||||
struct extended_locale_traits<T, float>
|
||||
{
|
||||
static constexpr bool is_locale_independent = true;
|
||||
static constexpr char(&get_decimal_point)() = get_locale_independent_decimal_point;
|
||||
|
||||
static int isspace(int c) noexcept
|
||||
{
|
||||
return ::_isspace_l(c, get_c_locale_t());
|
||||
return _isspace_l(c, get_c_locale_t());
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
static long long strtoll(const char* str, char** endptr) noexcept
|
||||
{
|
||||
return ::_strtoll_l(str, endptr, 10, get_c_locale_t());
|
||||
return _strtoll_l(str, endptr, 10, get_c_locale_t());
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
static unsigned long long strtoull(const char* str, char** endptr) noexcept
|
||||
{
|
||||
return ::_strtoull_l(str, endptr, 10, get_c_locale_t());
|
||||
return _strtoull_l(str, endptr, 10, get_c_locale_t());
|
||||
}
|
||||
|
||||
static float strtof(const char* str, char** str_end) noexcept
|
||||
static float strtof(T str, char** str_end)
|
||||
{
|
||||
return ::_strtof_l(str, str_end, get_c_locale_t());
|
||||
return _strtof_l(str, str_end, get_c_locale_t());
|
||||
}
|
||||
|
||||
static double strtod(const char* str, char** str_end) noexcept
|
||||
static double strtod(T str, char** str_end)
|
||||
{
|
||||
return ::_strtod_l(str, str_end, get_c_locale_t());
|
||||
return _strtod_l(str, str_end, get_c_locale_t());
|
||||
}
|
||||
|
||||
static long double strtold(const char* str, char** str_end) noexcept
|
||||
static long double strtold(T str, char** str_end)
|
||||
{
|
||||
return ::_strtold_l(str, str_end, get_c_locale_t());
|
||||
return _strtold_l(str, str_end, get_c_locale_t());
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
/*!
|
||||
@@ -360,7 +405,7 @@ struct extended_locale_traits<T, decltype(strtof_l(
|
||||
|
||||
static int isspace(int c) noexcept
|
||||
{
|
||||
return ::isspace_l(c, get_c_locale_t());
|
||||
return isspace_l(c, get_c_locale_t());
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
|
||||
@@ -7906,6 +7906,58 @@ inline from_chars_result from_chars(const char* first, const char* last, T& valu
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
/*!
|
||||
@brief Extended locale functions on Windows
|
||||
|
||||
A trait object which provides wrappers for isspace and the strto* family of
|
||||
functions, based on the platform's support for extended locale APIs.
|
||||
|
||||
This is the Windows implementation. The primary class template definition
|
||||
falls back to the standard locale-dependent functions, which is used on
|
||||
MinGW, whose C runtime only provides an incomplete subset of the
|
||||
`_<funcname>_l` family (e.g., missing `_strtof_l`/`_strtold_l` depending on
|
||||
the toolchain version). A specialization for genuine MSVC (including
|
||||
clang-cl, which mimics the MSVC ABI and CRT) is used instead, as the full
|
||||
`_<funcname>_l` family has reliably been available there since at least
|
||||
Visual Studio 2005.
|
||||
*/
|
||||
template <typename = const char*, typename = float>
|
||||
struct extended_locale_traits
|
||||
{
|
||||
static constexpr bool is_locale_independent = false;
|
||||
|
||||
JSON_HEDLEY_PURE
|
||||
static char get_decimal_point() noexcept
|
||||
{
|
||||
const auto* loc = localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
|
||||
}
|
||||
|
||||
static int isspace(int c) noexcept
|
||||
{
|
||||
return std::isspace(c);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
static long long strtoll(const char* str, char** endptr) noexcept
|
||||
{
|
||||
return std::strtoll(str, endptr, 10);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
static unsigned long long strtoull(const char* str, char** endptr) noexcept
|
||||
{
|
||||
return std::strtoull(str, endptr, 10);
|
||||
}
|
||||
|
||||
static constexpr float(&strtof)(const char*, char**) = std::strtof;
|
||||
static constexpr double(&strtod)(const char*, char**) = std::strtod;
|
||||
static constexpr long double(&strtold)(const char*, char**) = std::strtold;
|
||||
};
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
/*!
|
||||
@brief Get a pointer to a globally shared instance of the "C" locale on Windows
|
||||
|
||||
@@ -7924,58 +7976,51 @@ inline _locale_t get_c_locale_t() noexcept
|
||||
}
|
||||
};
|
||||
using LocaleUPtr = std::unique_ptr<std::remove_pointer<_locale_t>::type, LocaleTDeleter>;
|
||||
static const LocaleUPtr c_locale = LocaleUPtr{::_create_locale(LC_ALL_MASK, "C"), LocaleTDeleter{}};
|
||||
static const LocaleUPtr c_locale = LocaleUPtr{::_create_locale(LC_ALL, "C"), LocaleTDeleter{}};
|
||||
return c_locale.get();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Extended locale functions on Windows
|
||||
|
||||
A trait object which provides wrappers for isspace and the strto* family of
|
||||
functions, based on the platform's support for extended locale APIs.
|
||||
|
||||
This is the Windows implementation where extended locale support is always
|
||||
available through the `_<funcname>_l` variants.
|
||||
*/
|
||||
template <typename = const char*, typename = float>
|
||||
struct extended_locale_traits
|
||||
template <typename T>
|
||||
struct extended_locale_traits<T, float>
|
||||
{
|
||||
static constexpr bool is_locale_independent = true;
|
||||
static constexpr char(&get_decimal_point)() = get_locale_independent_decimal_point;
|
||||
|
||||
static int isspace(int c) noexcept
|
||||
{
|
||||
return ::_isspace_l(c, get_c_locale_t());
|
||||
return _isspace_l(c, get_c_locale_t());
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
static long long strtoll(const char* str, char** endptr) noexcept
|
||||
{
|
||||
return ::_strtoll_l(str, endptr, 10, get_c_locale_t());
|
||||
return _strtoll_l(str, endptr, 10, get_c_locale_t());
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
static unsigned long long strtoull(const char* str, char** endptr) noexcept
|
||||
{
|
||||
return ::_strtoull_l(str, endptr, 10, get_c_locale_t());
|
||||
return _strtoull_l(str, endptr, 10, get_c_locale_t());
|
||||
}
|
||||
|
||||
static float strtof(const char* str, char** str_end) noexcept
|
||||
static float strtof(T str, char** str_end)
|
||||
{
|
||||
return ::_strtof_l(str, str_end, get_c_locale_t());
|
||||
return _strtof_l(str, str_end, get_c_locale_t());
|
||||
}
|
||||
|
||||
static double strtod(const char* str, char** str_end) noexcept
|
||||
static double strtod(T str, char** str_end)
|
||||
{
|
||||
return ::_strtod_l(str, str_end, get_c_locale_t());
|
||||
return _strtod_l(str, str_end, get_c_locale_t());
|
||||
}
|
||||
|
||||
static long double strtold(const char* str, char** str_end) noexcept
|
||||
static long double strtold(T str, char** str_end)
|
||||
{
|
||||
return ::_strtold_l(str, str_end, get_c_locale_t());
|
||||
return _strtold_l(str, str_end, get_c_locale_t());
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
/*!
|
||||
@@ -8076,7 +8121,7 @@ struct extended_locale_traits<T, decltype(strtof_l(
|
||||
|
||||
static int isspace(int c) noexcept
|
||||
{
|
||||
return ::isspace_l(c, get_c_locale_t());
|
||||
return isspace_l(c, get_c_locale_t());
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(runtime/int)
|
||||
|
||||
@@ -79,7 +79,7 @@ check(std::string str, T expected_value, std::ptrdiff_t expected_ptr_offset, std
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
check(std::string str, init_val_t, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec)
|
||||
check(std::string str, init_val_t /*unused*/, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec)
|
||||
{
|
||||
T const init_value = 42;
|
||||
T value = init_value;
|
||||
@@ -89,7 +89,7 @@ check(std::string str, init_val_t, std::ptrdiff_t expected_ptr_offset, std::errc
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_floating_point<T>::value, void>::type
|
||||
check(std::string str, init_val_t, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec)
|
||||
check(std::string str, init_val_t /*unused*/, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec)
|
||||
{
|
||||
{
|
||||
T const init_value = 42;
|
||||
|
||||
Reference in New Issue
Block a user