Skip to content

Commit 1abf05c

Browse files
committed
fix(util): on Windows, ensure safe number formatting
1 parent e177f61 commit 1abf05c

1 file changed

Lines changed: 67 additions & 0 deletions

File tree

src/util.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
#include <cstdlib>
3939
#include <fstream>
4040
#include <iostream>
41+
#include <iterator>
42+
#include <locale>
4143
#include <mutex>
4244
#include <optional>
4345
#include <sstream>
@@ -884,6 +886,69 @@ bool getenv_is_enabled(os_access const& os, char const* var) {
884886
return false;
885887
}
886888

889+
namespace {
890+
891+
#ifdef _WIN32
892+
893+
std::string wchar_to_utf8(wchar_t wc) {
894+
std::string out;
895+
if (wc != 0) {
896+
try {
897+
static_assert(sizeof(wchar_t) == 2);
898+
auto const cu = static_cast<char16_t>(wc);
899+
utf8::utf16to8(&cu, &cu + 1, std::back_inserter(out));
900+
} catch (std::exception const&) {
901+
out.clear();
902+
}
903+
}
904+
return out;
905+
}
906+
907+
// `numpunct<char>` replacement that disables grouping for char streams.
908+
struct safe_numpunct : std::numpunct<char> {
909+
std::string do_grouping() const override { return {}; }
910+
char do_decimal_point() const override { return '.'; }
911+
};
912+
913+
void install_number_formatting_facets() {
914+
bool needs_wide_chars = true; // safe default
915+
916+
// Now check if number formatting uses multi-byte UTF-8 characters.
917+
// If so, we can provide fmt with a custom facet that uses UTF-8 strings
918+
// based on the wide-character locale.
919+
try {
920+
auto const& np = std::use_facet<std::numpunct<wchar_t>>(std::locale());
921+
auto const grouping = np.grouping();
922+
923+
// Empty grouping means "don't group" -> we're done.
924+
if (grouping.empty()) {
925+
needs_wide_chars = false;
926+
} else {
927+
auto const sep = wchar_to_utf8(np.thousands_sep());
928+
auto dp = wchar_to_utf8(np.decimal_point());
929+
930+
needs_wide_chars = sep.size() > 1 || dp.size() > 1;
931+
932+
if (needs_wide_chars) {
933+
std::locale::global(std::locale(
934+
std::locale(),
935+
new fmt::format_facet<std::locale>(sep, grouping, std::move(dp))));
936+
}
937+
}
938+
} catch (std::exception const&) {
939+
// fall back to the safe default
940+
needs_wide_chars = true;
941+
}
942+
943+
if (needs_wide_chars) {
944+
std::locale::global(std::locale(std::locale(), new safe_numpunct));
945+
}
946+
}
947+
948+
#endif
949+
950+
} // namespace
951+
887952
void setup_default_locale() {
888953
char const* const candidates[] = {
889954
#ifdef _WIN32
@@ -919,6 +984,8 @@ void setup_default_locale() {
919984
}
920985

921986
#ifdef _WIN32
987+
install_number_formatting_facets();
988+
922989
SetConsoleOutputCP(CP_UTF8);
923990
#endif
924991
}

0 commit comments

Comments
 (0)