Don't throw exceptions from error formatting functions. Gracefully fallback to a less descriptive message instead.

This commit is contained in:
Victor Zverovich 2014-09-03 08:03:05 -07:00
parent 82d4d11c11
commit 22f75d8b6d
3 changed files with 73 additions and 42 deletions

View File

@ -136,13 +136,33 @@ struct IntChecker<true> {
const char RESET_COLOR[] = "\x1b[0m"; const char RESET_COLOR[] = "\x1b[0m";
typedef void (*FormatFunc)(fmt::Writer &, int , fmt::StringRef); typedef void (*FormatFunc)(fmt::Writer &, int, fmt::StringRef);
// TODO: test
void format_error_code(fmt::Writer &out, int error_code,
fmt::StringRef message) FMT_NOEXCEPT(true) {
// Report error code making sure that the output fits into
// INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential
// bad_alloc.
out.clear();
static const char SEP[] = ": ";
static const char ERROR[] = "error ";
// SEP and ERROR add two terminating null characters, so subtract 1 as
// we need only one.
fmt::internal::IntTraits<int>::MainType ec_value = error_code;
std::size_t error_code_size =
sizeof(SEP) + sizeof(ERROR) + fmt::internal::count_digits(ec_value) - 1;
if (message.size() < fmt::internal::INLINE_BUFFER_SIZE - error_code_size)
out << message << SEP;
out << ERROR << error_code;
assert(out.size() <= fmt::internal::INLINE_BUFFER_SIZE);
}
void report_error(FormatFunc func, void report_error(FormatFunc func,
int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) {
try { try {
fmt::Writer full_message; fmt::Writer full_message;
func(full_message, error_code, message); // TODO: make sure this doesn't throw func(full_message, error_code, message);
std::fwrite(full_message.c_str(), full_message.size(), 1, stderr); std::fwrite(full_message.c_str(), full_message.size(), 1, stderr);
std::fputc('\n', stderr); std::fputc('\n', stderr);
} catch (...) {} } catch (...) {}
@ -453,28 +473,31 @@ int fmt::internal::safe_strerror(
} }
void fmt::internal::format_system_error( void fmt::internal::format_system_error(
fmt::Writer &out, int error_code, fmt::StringRef message) { fmt::Writer &out, int error_code,
Array<char, INLINE_BUFFER_SIZE> buffer; fmt::StringRef message) FMT_NOEXCEPT(true) {
buffer.resize(INLINE_BUFFER_SIZE); try {
char *system_message = 0; Array<char, INLINE_BUFFER_SIZE> buffer;
for (;;) { buffer.resize(INLINE_BUFFER_SIZE);
system_message = &buffer[0]; char *system_message = 0;
int result = safe_strerror(error_code, system_message, buffer.size()); for (;;) {
if (result == 0) system_message = &buffer[0];
break; int result = safe_strerror(error_code, system_message, buffer.size());
if (result != ERANGE) { if (result == 0) {
// Can't get error message, report error code instead. out << message << ": " << system_message;
out << message << ": error code = " << error_code; return;
return; }
if (result != ERANGE)
break; // Can't get error message, report error code instead.
buffer.resize(buffer.size() * 2);
} }
buffer.resize(buffer.size() * 2); } catch (...) {}
} format_error_code(out, error_code, message);
out << message << ": " << system_message;
} }
#ifdef _WIN32 #ifdef _WIN32
void fmt::internal::format_windows_error( void fmt::internal::format_windows_error(
fmt::Writer &out, int error_code, fmt::StringRef message) { fmt::Writer &out, int error_code,
fmt::StringRef message) FMT_NOEXCEPT(true) {
class String { class String {
private: private:
LPWSTR str_; LPWSTR str_;
@ -485,19 +508,20 @@ void fmt::internal::format_windows_error(
LPWSTR *ptr() { return &str_; } LPWSTR *ptr() { return &str_; }
LPCWSTR c_str() const { return str_; } LPCWSTR c_str() const { return str_; }
}; };
String system_message; try {
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | String system_message;
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0,
reinterpret_cast<LPWSTR>(system_message.ptr()), 0, 0)) { error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
UTF16ToUTF8 utf8_message; reinterpret_cast<LPWSTR>(system_message.ptr()), 0, 0)) {
if (!utf8_message.convert(system_message.c_str())) { UTF16ToUTF8 utf8_message;
out << message << ": " << utf8_message; if (utf8_message.convert(system_message.c_str()) == ERROR_SUCCESS) {
return; out << message << ": " << utf8_message;
return;
}
} }
} } catch (...) {}
// Can't get error message, report error code instead. format_error_code(out, error_code, message);
out << message << ": error code = " << error_code;
} }
#endif #endif
@ -1190,14 +1214,12 @@ void fmt::BasicFormatter<Char>::format(
void fmt::report_system_error( void fmt::report_system_error(
int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) {
// FIXME: format_system_error may throw
report_error(internal::format_system_error, error_code, message); report_error(internal::format_system_error, error_code, message);
} }
#ifdef _WIN32 #ifdef _WIN32
void fmt::report_windows_error( void fmt::report_windows_error(
int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) {
// FIXME: format_windows_error may throw
report_error(internal::format_windows_error, error_code, message); report_error(internal::format_windows_error, error_code, message);
} }
#endif #endif

View File

@ -307,7 +307,7 @@ class Array {
grow(capacity); grow(capacity);
} }
void clear() { size_ = 0; } void clear() FMT_NOEXCEPT(true) { size_ = 0; }
void push_back(const T &value) { void push_back(const T &value) {
if (size_ == capacity_) if (size_ == capacity_)
@ -528,7 +528,8 @@ class UTF16ToUTF8 {
std::string str() const { return std::string(&buffer_[0], size()); } std::string str() const { return std::string(&buffer_[0], size()); }
// Performs conversion returning a system error code instead of // Performs conversion returning a system error code instead of
// throwing exception on error. // throwing exception on conversion error. This method may still throw
// in case of memory allocation error.
int convert(WStringRef s); int convert(WStringRef s);
}; };
#endif #endif
@ -545,12 +546,12 @@ class UTF16ToUTF8 {
int safe_strerror(int error_code, int safe_strerror(int error_code,
char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT(true); char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT(true);
void format_system_error( void format_system_error(fmt::Writer &out, int error_code,
fmt::Writer &out, int error_code, fmt::StringRef message); fmt::StringRef message) FMT_NOEXCEPT(true);
#ifdef _WIN32 #ifdef _WIN32
void format_windows_error( void format_windows_error(fmt::Writer &out, int error_code,
fmt::Writer &out, int error_code, fmt::StringRef message); fmt::StringRef message) FMT_NOEXCEPT(true);
#endif #endif
// Computes max(Arg, 1) at compile time. It is used to avoid errors about // Computes max(Arg, 1) at compile time. It is used to avoid errors about
@ -1518,7 +1519,7 @@ class BasicWriter {
return *this; return *this;
} }
void clear() { buffer_.clear(); } void clear() FMT_NOEXCEPT(true) { buffer_.clear(); }
}; };
template <typename Char> template <typename Char>

View File

@ -471,8 +471,11 @@ void check_throw_error(int error_code, FormatErrorMessage format) {
TEST(UtilTest, FormatSystemError) { TEST(UtilTest, FormatSystemError) {
fmt::Writer message; fmt::Writer message;
fmt::internal::format_system_error(message, EDOM, "test"); fmt::internal::format_system_error(message, EDOM, "test");
EXPECT_EQ(fmt::format("test: {}", EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), message.str());
get_system_error(EDOM)), message.str()); message.clear();
fmt::internal::format_system_error(
message, EDOM, fmt::StringRef("x", std::numeric_limits<size_t>::max()));
EXPECT_EQ(fmt::format("error {}", EDOM), message.str());
} }
TEST(UtilTest, SystemError) { TEST(UtilTest, SystemError) {
@ -504,6 +507,11 @@ TEST(UtilTest, FormatWindowsError) {
actual_message, ERROR_FILE_EXISTS, "test"); actual_message, ERROR_FILE_EXISTS, "test");
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
actual_message.str()); actual_message.str());
actual_message.clear();
fmt::internal::format_windows_error(
actual_message, ERROR_FILE_EXISTS,
fmt::StringRef("x", std::numeric_limits<size_t>::max()));
EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS), message.str());
} }
TEST(UtilTest, WindowsError) { TEST(UtilTest, WindowsError) {