Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue #2274. #2275

Merged
merged 1 commit into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/fmt/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ template <typename Char> struct formatter<std::error_code, Char> {
};

#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.
Expand Down Expand Up @@ -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).
Expand Down
83 changes: 63 additions & 20 deletions src/os.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<wchar_t*>(&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<char>& 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<uint32_t>(buf.size()), nullptr);
if (result != 0) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
format_to(buffer_appender<char>(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<char>(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(...) {}
Expand Down
21 changes: 12 additions & 9 deletions test/os-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ void check_utf_conversion_error(
fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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<LPWSTR>(&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");
Expand All @@ -120,8 +120,12 @@ TEST(os_test, format_long_windows_error) {
0, static_cast<DWORD>(provisioning_not_allowed),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&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,
Expand All @@ -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());
}
Expand Down
2 changes: 1 addition & 1 deletion test/posix-mock-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down