diff --git a/include/fmt/os.h b/include/fmt/os.h index a679de09..1a8833de 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -136,6 +136,8 @@ template struct formatter { }; #ifdef _WIN32 +FMT_API const std::error_category& system_category() FMT_NOEXCEPT; + namespace detail { // A converter from UTF-16 to UTF-8. // It is only provided for Windows since other systems support UTF-8 natively. @@ -202,6 +204,10 @@ std::system_error windows_error(int error_code, string_view message, // Can be used to report errors from destructors. FMT_API void report_windows_error(int error_code, const char* message) FMT_NOEXCEPT; +#else +inline const std::error_category& system_category() FMT_NOEXCEPT { + return std::system_category(); +} #endif // _WIN32 // std::system is not available on some platforms such as iOS (#2248). diff --git a/src/os.cc b/src/os.cc index 3fd043c7..6474d455 100644 --- a/src/os.cc +++ b/src/os.cc @@ -100,35 +100,78 @@ int detail::utf16_to_utf8::convert(wstring_view s) { return 0; } +namespace detail { + +class system_message { + system_message(const system_message&) = delete; + void operator=(const system_message&) = delete; + + unsigned long result_; + wchar_t* message_; + + static bool is_whitespace(wchar_t c) FMT_NOEXCEPT { + return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0'; + } + + public: + explicit system_message(unsigned long error_code) + : result_(0), message_(nullptr) { + result_ = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message_), 0, nullptr); + if (result_ != 0) { + while (result_ != 0 && is_whitespace(message_[result_ - 1])) { + --result_; + } + } + } + ~system_message() { LocalFree(message_); } + explicit operator bool() const FMT_NOEXCEPT { return result_ != 0; } + operator wstring_view() const FMT_NOEXCEPT { + return wstring_view(message_, result_); + } +}; + +class utf8_system_category final : public std::error_category { + public: + const char* name() const FMT_NOEXCEPT override { return "system"; } + std::string message(int error_code) const override { + system_message msg(error_code); + if (msg) { + utf16_to_utf8 utf8_message; + if (utf8_message.convert(msg) == ERROR_SUCCESS) { + return utf8_message.str(); + } + } + return "unknown error"; + } +}; + +} // namespace detail + +FMT_API const std::error_category& system_category() FMT_NOEXCEPT { + static const detail::utf8_system_category category; + return category; +} + std::system_error vwindows_error(int err_code, string_view format_str, format_args args) { - auto ec = std::error_code(err_code, std::system_category()); + auto ec = std::error_code(err_code, system_category()); throw std::system_error(ec, vformat(format_str, args)); } void detail::format_windows_error(detail::buffer& out, int error_code, const char* message) FMT_NOEXCEPT { FMT_TRY { - wmemory_buffer buf; - buf.resize(inline_buffer_size); - for (;;) { - wchar_t* system_message = &buf[0]; - int result = FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, - error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message, - static_cast(buf.size()), nullptr); - if (result != 0) { - utf16_to_utf8 utf8_message; - if (utf8_message.convert(system_message) == ERROR_SUCCESS) { - format_to(buffer_appender(out), "{}: {}", message, - utf8_message); - return; - } - break; + system_message msg(error_code); + if (msg) { + utf16_to_utf8 utf8_message; + if (utf8_message.convert(msg) == ERROR_SUCCESS) { + format_to(buffer_appender(out), "{}: {}", message, utf8_message); + return; } - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - break; // Can't get error message, report error code instead. - buf.resize(buf.size() * 2); } } FMT_CATCH(...) {} diff --git a/test/os-test.cc b/test/os-test.cc index 8cf3c5e8..09115b77 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -45,7 +45,6 @@ void check_utf_conversion_error( fmt::basic_string_view str = fmt::basic_string_view(0, 1)) { fmt::memory_buffer out; fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message); - out.resize(out.size() - 2); // Remove newline. auto error = std::system_error(std::error_code()); try { (Converter)(str); @@ -74,10 +73,10 @@ TEST(os_test, format_std_error_code) { std::error_code(42, std::generic_category()))); EXPECT_EQ("system:42", fmt::format(FMT_STRING("{0}"), - std::error_code(42, std::system_category()))); + std::error_code(42, fmt::system_category()))); EXPECT_EQ("system:-42", fmt::format(FMT_STRING("{0}"), - std::error_code(-42, std::system_category()))); + std::error_code(-42, fmt::system_category()))); } TEST(os_test, format_std_error_code_wide) { @@ -86,10 +85,10 @@ TEST(os_test, format_std_error_code_wide) { std::error_code(42, std::generic_category()))); EXPECT_EQ(L"system:42", fmt::format(FMT_STRING(L"{0}"), - std::error_code(42, std::system_category()))); + std::error_code(42, fmt::system_category()))); EXPECT_EQ(L"system:-42", fmt::format(FMT_STRING(L"{0}"), - std::error_code(-42, std::system_category()))); + std::error_code(-42, fmt::system_category()))); } TEST(os_test, format_windows_error) { @@ -99,7 +98,8 @@ TEST(os_test, format_windows_error) { FORMAT_MESSAGE_IGNORE_INSERTS, 0, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&message), 0, 0); - fmt::detail::utf16_to_utf8 utf8_message(message); + fmt::detail::utf16_to_utf8 utf8_message( + fmt::wstring_view(message, result - 2)); LocalFree(message); fmt::memory_buffer actual_message; fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test"); @@ -120,8 +120,12 @@ TEST(os_test, format_long_windows_error) { 0, static_cast(provisioning_not_allowed), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&message), 0, 0); - EXPECT_NE(result, 0); - fmt::detail::utf16_to_utf8 utf8_message(message); + if (result == 0) { + LocalFree(message); + return; + } + fmt::detail::utf16_to_utf8 utf8_message( + fmt::wstring_view(message, result - 2)); LocalFree(message); fmt::memory_buffer actual_message; fmt::detail::format_windows_error(actual_message, provisioning_not_allowed, @@ -139,7 +143,6 @@ TEST(os_test, windows_error) { } fmt::memory_buffer message; fmt::detail::format_windows_error(message, ERROR_FILE_EXISTS, "test error"); - message.resize(message.size() - 2); EXPECT_THAT(error.what(), HasSubstr(to_string(message))); EXPECT_EQ(ERROR_FILE_EXISTS, error.code().value()); } diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index f669f13a..191d7aef 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -272,7 +272,7 @@ TEST(file_test, size) { } fstat_sim = none; EXPECT_EQ(error_code, - std::error_code(ERROR_ACCESS_DENIED, std::system_category())); + std::error_code(ERROR_ACCESS_DENIED, fmt::system_category())); # else f.close(); EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes");