diff --git a/cppformat/format.cc b/cppformat/format.cc index f30d5f2d5a74..3ed2114048c6 100644 --- a/cppformat/format.cc +++ b/cppformat/format.cc @@ -588,27 +588,25 @@ FMT_FUNC void fmt::WindowsError::init( FMT_FUNC void fmt::internal::format_windows_error( fmt::Writer &out, int error_code, fmt::StringRef message) FMT_NOEXCEPT { - class String { - private: - LPWSTR str_; - - public: - String() : str_() {} - ~String() { LocalFree(str_); } - LPWSTR *ptr() { return &str_; } - LPCWSTR c_str() const { return str_; } - }; FMT_TRY { - String system_message; - if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, - error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(system_message.ptr()), 0, 0)) { - UTF16ToUTF8 utf8_message; - if (utf8_message.convert(system_message.c_str()) == ERROR_SUCCESS) { - out << message << ": " << utf8_message; - return; + MemoryBuffer buffer; + buffer.resize(INLINE_BUFFER_SIZE); + for (;;) { + wchar_t *system_message = &buffer[0]; + int result = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + 0, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + system_message, static_cast(buffer.size()), 0); + if (result != 0) { + UTF16ToUTF8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) { + out << message << ": " << utf8_message; + return; + } + break; } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buffer.resize(buffer.size() * 2); } } FMT_CATCH(...) {} fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. diff --git a/test/util-test.cc b/test/util-test.cc index 38b52e0f1d9a..df754b2579f1 100644 --- a/test/util-test.cc +++ b/test/util-test.cc @@ -873,6 +873,27 @@ TEST(UtilTest, FormatWindowsError) { EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS), actual_message.str()); } +TEST(UtilTest, FormatLongWindowsError) { + LPWSTR message = 0; + // this error code is not available on all Windows platforms and + // Windows SDKs, so do not fail the test if the error string cannot + // be retrieved. + const int provisioning_not_allowed = 0x80284013L /*TBS_E_PROVISIONING_NOT_ALLOWED*/; + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, + provisioning_not_allowed, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message), 0, 0) == 0) { + return; + } + fmt::internal::UTF16ToUTF8 utf8_message(message); + LocalFree(message); + fmt::MemoryWriter actual_message; + fmt::internal::format_windows_error( + actual_message, provisioning_not_allowed, "test"); + EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), + actual_message.str()); +} + TEST(UtilTest, WindowsError) { check_throw_error( ERROR_FILE_EXISTS, fmt::internal::format_windows_error);