diff --git a/stl/inc/xlocinfo b/stl/inc/xlocinfo index 054c2524fa6..dcd4801cd13 100644 --- a/stl/inc/xlocinfo +++ b/stl/inc/xlocinfo @@ -134,6 +134,28 @@ private: void* _Timeptr; // pointer to time information }; +template +_Elem* _Ntcts_dup_dbg(const _Elem* const _Ptr) noexcept { + _STL_INTERNAL_STATIC_ASSERT(_Is_any_of_v<_Elem, char, wchar_t, unsigned short>); + + auto _Iter = _Ptr; + while (*_Iter != _Elem{}) { + ++_Iter; + } + const size_t _Count = static_cast(_Iter - _Ptr + 1) * sizeof(_Elem); + +#ifdef _DEBUG + const auto _Result = static_cast<_Elem*>(_CSTD _malloc_dbg(_Count, _CRT_BLOCK, __FILE__, __LINE__)); +#else + const auto _Result = static_cast<_Elem*>(_CSTD malloc(_Count)); +#endif + if (_Result) { + _CSTD memcpy(_Result, _Ptr, _Count); + } + + return _Result; +} + extern "C++" template class _CRTIMP2_PURE_IMPORT _Yarn { // wrap a NTCTS public: @@ -156,26 +178,22 @@ public: _Tidy(); if (_Right) { // new is not empty, copy it - const _Elem* _Ptr = _Right; - while (*_Ptr != _Elem{}) { - ++_Ptr; - } + _Myptr = _STD _Ntcts_dup_dbg(_Right); + } + } - const auto _Count = (++_Ptr - _Right) * sizeof(_Elem); + return *this; + } -#ifdef _DEBUG - _Myptr = static_cast<_Elem*>(_malloc_dbg(_Count, _CRT_BLOCK, __FILE__, __LINE__)); -#else - _Myptr = static_cast<_Elem*>(_CSTD malloc(_Count)); -#endif + template , int> = 0> + void _From_wide(const wchar_t* const _Right) noexcept { + if (reinterpret_cast(_Myptr) != _Right) { // new value, discard old and copy new + _Tidy(); - if (_Myptr) { - _CSTD memcpy(_Myptr, _Right, _Count); - } + if (_Right) { // new is not empty, copy it + _Myptr = reinterpret_cast(_STD _Ntcts_dup_dbg(_Right)); } } - - return *this; } __CLR_OR_THIS_CALL ~_Yarn() noexcept { @@ -382,6 +400,7 @@ private: _Yarn _Months; // month names _Yarn _W_Days; // wide weekday names _Yarn _W_Months; // wide month names + // TRANSITION, ABI, `_Oldlocname._Myptr` is reinterpreted as `wchar_t*`. `wchar` should be wrapped instead. _Yarn _Oldlocname; // old locale name to revert to on destruction _Yarn _Newlocname; // new locale name for this object }; diff --git a/stl/src/locale.cpp b/stl/src/locale.cpp index e11e106f16f..75d55c65154 100644 --- a/stl/src/locale.cpp +++ b/stl/src/locale.cpp @@ -137,9 +137,7 @@ void __CLRCALL_PURE_OR_CDECL locale::_Locimp::_Locimp_Addfac( void __CLRCALL_PURE_OR_CDECL _Locinfo::_Locinfo_ctor( _Locinfo* pLocinfo, int cat, const char* locname) { // capture a named locale - const char* oldlocname = setlocale(LC_ALL, nullptr); - - pLocinfo->_Oldlocname = oldlocname == nullptr ? "" : oldlocname; + pLocinfo->_Oldlocname._From_wide(_wsetlocale(LC_ALL, nullptr)); _Locinfo_Addcats(pLocinfo, cat, locname); } diff --git a/stl/src/locale0.cpp b/stl/src/locale0.cpp index 81aae0ad22e..1974c0e354a 100644 --- a/stl/src/locale0.cpp +++ b/stl/src/locale0.cpp @@ -222,9 +222,8 @@ void __CLRCALL_PURE_OR_CDECL locale::_Locimp::_Locimp_dtor(_Locimp* _This) { // void __CLRCALL_PURE_OR_CDECL _Locinfo::_Locinfo_ctor( _Locinfo* pLocinfo, const char* locname) { // switch to a named locale - const char* oldlocname = setlocale(LC_ALL, nullptr); + pLocinfo->_Oldlocname._From_wide(_wsetlocale(LC_ALL, nullptr)); - pLocinfo->_Oldlocname = oldlocname == nullptr ? "" : oldlocname; if (locname != nullptr) { locname = setlocale(LC_ALL, locname); } @@ -233,8 +232,12 @@ void __CLRCALL_PURE_OR_CDECL _Locinfo::_Locinfo_ctor( } void __CLRCALL_PURE_OR_CDECL _Locinfo::_Locinfo_dtor(_Locinfo* pLocinfo) { // destroy a _Locinfo object, revert locale - if (!pLocinfo->_Oldlocname._Empty()) { - setlocale(LC_ALL, pLocinfo->_Oldlocname._C_str()); + if (pLocinfo->_Oldlocname._Empty()) { + // `pLocinfo->_Oldlocname._C_str()` points to a single `char` of value 0 in this case, + // so reinterpret_cast is not reliable. + _wsetlocale(LC_ALL, L""); + } else { + _wsetlocale(LC_ALL, reinterpret_cast(pLocinfo->_Oldlocname._C_str())); } } _STD_END diff --git a/tests/std/test.lst b/tests/std/test.lst index 25c6dc46df0..0686b089500 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -271,6 +271,7 @@ tests\GH_005472_do_not_overlap tests\GH_005546_containers_size_type_cast tests\GH_005553_regex_character_translation tests\GH_005768_pow_accuracy +tests\GH_005780_non_ascii_locales tests\LWG2381_num_get_floating_point tests\LWG2510_tag_classes tests\LWG2597_complex_branch_cut diff --git a/tests/std/tests/GH_005780_non_ascii_locales/env.lst b/tests/std/tests/GH_005780_non_ascii_locales/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/GH_005780_non_ascii_locales/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_matrix.lst diff --git a/tests/std/tests/GH_005780_non_ascii_locales/test.cpp b/tests/std/tests/GH_005780_non_ascii_locales/test.cpp new file mode 100644 index 00000000000..47a2e17b160 --- /dev/null +++ b/tests/std/tests/GH_005780_non_ascii_locales/test.cpp @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +std::string set_locale(const std::string& locale_name) { + const char* ret = std::setlocale(LC_ALL, locale_name.c_str()); + assert(ret != nullptr); + return ret; +} + +std::string query_locale() { + const char* ret = std::setlocale(LC_ALL, nullptr); + assert(ret != nullptr); + return ret; +} + +void assert_string_non_ascii(const std::string& string) { + const auto char_not_ascii = [](const char c) { return (c & 0x80) != 0; }; + assert(std::any_of(string.begin(), string.end(), char_not_ascii)); +} + +void test_gh_5780() { + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/language-strings#supported-language-strings + std::string locale_name = set_locale("norwegian-bokmal.437"); + assert_string_non_ascii(locale_name); + + std::cerr.imbue(std::locale::classic()); + std::cerr << std::setprecision(2) << 0.1 << std::endl; + + assert(query_locale() == locale_name); +} + +int main() { + test_gh_5780(); +}