diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 8468843d..130ee2f6 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -27,11 +27,6 @@ #include "format.h" -// Dummy implementations of strerror_r and strerror_s called if corresponding -// system functions are not available. -inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; } -inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; } - FMT_BEGIN_NAMESPACE namespace detail { @@ -57,75 +52,6 @@ inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) { # define FMT_SNPRINTF fmt_snprintf #endif // _MSC_VER -// A portable thread-safe version of strerror. -// Sets buffer to point to a string describing the error code. -// This can be either a pointer to a string stored in buffer, -// or a pointer to some static immutable string. -// Returns one of the following values: -// 0 - success -// ERANGE - buffer is not large enough to store the error message -// other - failure -// Buffer should be at least of size 1. -inline int safe_strerror(int error_code, char*& buffer, size_t buffer_size) { - FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer"); - - class dispatcher { - private: - int error_code_; - char*& buffer_; - size_t buffer_size_; - - // A noop assignment operator to avoid bogus warnings. - void operator=(const dispatcher&) {} - - // Handle the result of XSI-compliant version of strerror_r. - int handle(int result) { - // glibc versions before 2.13 return result in errno. - return result == -1 ? errno : result; - } - - // Handle the result of GNU-specific version of strerror_r. - FMT_MAYBE_UNUSED - int handle(char* message) { - // If the buffer is full then the message is probably truncated. - if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) - return ERANGE; - buffer_ = message; - return 0; - } - - // Handle the case when strerror_r is not available. - FMT_MAYBE_UNUSED - int handle(detail::null<>) { - return fallback(strerror_s(buffer_, buffer_size_, error_code_)); - } - - // Fallback to strerror_s when strerror_r is not available. - FMT_MAYBE_UNUSED - int fallback(int result) { - // If the buffer is full then the message is probably truncated. - return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? ERANGE - : result; - } - -#if !FMT_MSC_VER - // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(detail::null<>) { - errno = 0; - buffer_ = strerror(error_code_); - return errno; - } -#endif - - public: - dispatcher(int err_code, char*& buf, size_t buf_size) - : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} - - int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); } - }; - return dispatcher(error_code, buffer, buffer_size).run(); -} - FMT_FUNC void format_error_code(detail::buffer& out, int error_code, string_view message) FMT_NOEXCEPT { // Report error code making sure that the output fits into @@ -150,7 +76,7 @@ FMT_FUNC void format_error_code(detail::buffer& out, int error_code, } FMT_FUNC void report_error(format_func func, int error_code, - string_view message) FMT_NOEXCEPT { + const char* message) FMT_NOEXCEPT { memory_buffer full_message; func(full_message, error_code, message); // Don't use fwrite_fully because the latter may throw. @@ -202,16 +128,12 @@ template FMT_FUNC Char decimal_point_impl(locale_ref) { #if !FMT_MSC_VER FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default; -FMT_API FMT_FUNC system_error::~system_error() FMT_NOEXCEPT = default; #endif -FMT_FUNC void system_error::init(int err_code, string_view format_str, - format_args args) { - error_code_ = err_code; - memory_buffer buffer; - format_system_error(buffer, err_code, vformat(format_str, args)); - std::runtime_error& base = *this; - base = std::runtime_error(to_string(buffer)); +FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str, + format_args args) { + auto ec = std::error_code(error_code, std::generic_category()); + return std::system_error(ec, vformat(format_str, args)); } namespace detail { @@ -2634,23 +2556,11 @@ FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { } FMT_FUNC void format_system_error(detail::buffer& out, int error_code, - string_view message) FMT_NOEXCEPT { + const char* message) FMT_NOEXCEPT { FMT_TRY { - memory_buffer buf; - buf.resize(inline_buffer_size); - for (;;) { - char* system_message = &buf[0]; - int result = - detail::safe_strerror(error_code, system_message, buf.size()); - if (result == 0) { - format_to(detail::buffer_appender(out), FMT_STRING("{}: {}"), - message, system_message); - return; - } - if (result != ERANGE) - break; // Can't get error message, report error code instead. - buf.resize(buf.size() * 2); - } + auto ec = std::error_code(error_code, std::generic_category()); + write(std::back_inserter(out), std::system_error(ec, message).what()); + return; } FMT_CATCH(...) {} format_error_code(out, error_code, message); @@ -2661,7 +2571,7 @@ FMT_FUNC void detail::error_handler::on_error(const char* message) { } FMT_FUNC void report_system_error(int error_code, - fmt::string_view message) FMT_NOEXCEPT { + const char* message) FMT_NOEXCEPT { report_error(format_system_error, error_code, message); } diff --git a/include/fmt/format.h b/include/fmt/format.h index 84a968cd..241aa902 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -37,10 +37,11 @@ #include // std::signbit #include #include -#include // std::numeric_limits -#include // std::uninitialized_copy -#include // std::runtime_error -#include // std::swap +#include // std::numeric_limits +#include // std::uninitialized_copy +#include // std::runtime_error +#include // std::system_error +#include // std::swap #include "core.h" @@ -2675,13 +2676,13 @@ FMT_CONSTEXPR void handle_dynamic_spec(int& value, } } -using format_func = void (*)(detail::buffer&, int, string_view); +using format_func = void (*)(detail::buffer&, int, const char*); FMT_API void format_error_code(buffer& out, int error_code, string_view message) FMT_NOEXCEPT; FMT_API void report_error(format_func func, int error_code, - string_view message) FMT_NOEXCEPT; + const char* message) FMT_NOEXCEPT; } // namespace detail FMT_MODULE_EXPORT_BEGIN @@ -2689,76 +2690,55 @@ template using arg_formatter FMT_DEPRECATED_ALIAS = detail::arg_formatter; +FMT_API std::system_error vsystem_error(int error_code, string_view format_str, + format_args args); + /** - An error returned by an operating system or a language runtime, - for example a file opening error. + \rst + Constructs :class:`std::system_error` with a message formatted with + ``fmt::format(message, args...)``. + *error_code* is a system error code as given by ``errno``. + + **Example**:: + + // This throws std::system_error with the description + // cannot open file 'madeup': No such file or directory + // or similar (system message may vary). + const char *filename = "madeup"; + std::FILE *file = std::fopen(filename, "r"); + if (!file) + throw fmt::system_error(errno, "cannot open file '{}'", filename); + \endrst */ -FMT_CLASS_API -class FMT_API system_error : public std::runtime_error { - private: - void init(int err_code, string_view format_str, format_args args); - - protected: - int error_code_; - - system_error() : std::runtime_error(""), error_code_(0) {} - - public: - /** - \rst - Constructs a :class:`fmt::system_error` object with a description - formatted with `fmt::format_system_error`. *message* and additional - arguments passed into the constructor are formatted similarly to - `fmt::format`. - - **Example**:: - - // This throws a system_error with the description - // cannot open file 'madeup': No such file or directory - // or similar (system message may vary). - const char *filename = "madeup"; - std::FILE *file = std::fopen(filename, "r"); - if (!file) - throw fmt::system_error(errno, "cannot open file '{}'", filename); - \endrst - */ - template - system_error(int error_code, string_view message, const Args&... args) - : std::runtime_error("") { - init(error_code, message, make_format_args(args...)); - } - system_error(const system_error&) = default; - system_error& operator=(const system_error&) = default; - system_error(system_error&&) = default; - system_error& operator=(system_error&&) = default; - ~system_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT; - - int error_code() const { return error_code_; } -}; +template +std::system_error system_error(int error_code, string_view message, + const Args&... args) { + return vsystem_error(error_code, message, make_format_args(args...)); +} /** \rst - Formats an error returned by an operating system or a language runtime, - for example a file opening error, and writes it to *out* in the following - form: + Formats an error message for an error returned by an operating system or a + language runtime, for example a file opening error, and writes it to *out*. + The format is the same as the one used by ``std::system_error(ec, message)`` + where ``ec`` is ``std::error_code(error_code, std::generic_category()})``. + It is implementation-defined but normally looks like: .. parsed-literal:: **: ** - where ** is the passed message and ** is - the system message corresponding to the error code. + where ** is the passed message and ** is the system + message corresponding to the error code. *error_code* is a system error code as given by ``errno``. - If *error_code* is not a valid error code such as -1, the system message - may look like "Unknown error -1" and is platform-dependent. \endrst */ FMT_API void format_system_error(detail::buffer& out, int error_code, - string_view message) FMT_NOEXCEPT; + const char* message) FMT_NOEXCEPT; // Reports a system error without throwing an exception. // Can be used to report errors from destructors. FMT_API void report_system_error(int error_code, - string_view message) FMT_NOEXCEPT; + const char* message) FMT_NOEXCEPT; /** Fast integer formatter. */ class format_int { diff --git a/include/fmt/os.h b/include/fmt/os.h index 7d9166b0..482b1a81 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -152,7 +152,7 @@ class utf16_to_utf8 { }; FMT_API void format_windows_error(buffer& out, int error_code, - string_view message) FMT_NOEXCEPT; + const char* message) FMT_NOEXCEPT; } // namespace detail FMT_API std::system_error vwindows_error(int error_code, string_view format_str, @@ -174,7 +174,7 @@ FMT_API std::system_error vwindows_error(int error_code, string_view format_str, **Example**:: - // This throws a windows_error with the description + // This throws a system_error with the description // cannot open file 'madeup': The system cannot find the file specified. // or similar (system message may vary). const char *filename = "madeup"; @@ -195,7 +195,7 @@ std::system_error windows_error(int error_code, string_view message, // Reports a Windows error without throwing an exception. // Can be used to report errors from destructors. FMT_API void report_windows_error(int error_code, - string_view message) FMT_NOEXCEPT; + const char* message) FMT_NOEXCEPT; #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 b55f61af..68d6c6c7 100644 --- a/src/os.cc +++ b/src/os.cc @@ -107,7 +107,7 @@ std::system_error vwindows_error(int err_code, string_view format_str, } void detail::format_windows_error(detail::buffer& out, int error_code, - string_view message) FMT_NOEXCEPT { + const char* message) FMT_NOEXCEPT { FMT_TRY { wmemory_buffer buf; buf.resize(inline_buffer_size); @@ -135,8 +135,7 @@ void detail::format_windows_error(detail::buffer& out, int error_code, format_error_code(out, error_code, message); } -void report_windows_error(int error_code, - fmt::string_view message) FMT_NOEXCEPT { +void report_windows_error(int error_code, const char* message) FMT_NOEXCEPT { report_error(detail::format_windows_error, error_code, message); } #endif // _WIN32 diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 8094daf8..877111af 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -304,40 +304,6 @@ TEST(fp_test, grisu_format_compiles_with_on_ieee_double) { format_float(0.42, -1, fmt::detail::float_specs(), buf); } -TEST(format_impl_test, strerror) { - char* message = nullptr; - char buffer[256]; - EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = nullptr, 0), - "invalid buffer"); - EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = buffer, 0), - "invalid buffer"); - buffer[0] = 'x'; -#if defined(_GNU_SOURCE) && !defined(__COVERITY__) - // Use invalid error code to make sure that safe_strerror returns an error - // message in the buffer rather than a pointer to a static string. - int error_code = -1; -#else - int error_code = EDOM; -#endif - - int result = fmt::detail::safe_strerror(error_code, message = buffer, 256); - EXPECT_EQ(result, 0); - size_t message_size = std::strlen(message); - EXPECT_GE(255u, message_size); - EXPECT_EQ(get_system_error(error_code), message); - - // safe_strerror never uses buffer on MinGW. -#if !defined(__MINGW32__) && !defined(__sun) - result = - fmt::detail::safe_strerror(error_code, message = buffer, message_size); - EXPECT_EQ(ERANGE, result); - result = fmt::detail::safe_strerror(error_code, message = buffer, 1); - EXPECT_EQ(buffer, message); // Message should point to buffer. - EXPECT_EQ(ERANGE, result); - EXPECT_STREQ("", message); -#endif -} - TEST(format_impl_test, format_error_code) { std::string msg = "error 42", sep = ": "; { diff --git a/test/format-test.cc b/test/format-test.cc index 992b1269..50e96ef5 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -136,8 +136,8 @@ TEST(util_test, allocator_ref) { TEST(util_test, format_system_error) { fmt::memory_buffer message; fmt::format_system_error(message, EDOM, "test"); - EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), - to_string(message)); + auto ec = std::error_code(EDOM, std::generic_category()); + EXPECT_EQ(to_string(message), std::system_error(ec, "test").what()); message = fmt::memory_buffer(); // Check if std::allocator throws on allocating max size_t / 2 chars. @@ -153,25 +153,24 @@ TEST(util_test, format_system_error) { fmt::print("warning: std::allocator allocates {} chars", max_size); return; } - fmt::format_system_error(message, EDOM, fmt::string_view(nullptr, max_size)); - EXPECT_EQ(fmt::format("error {}", EDOM), to_string(message)); } TEST(util_test, system_error) { auto test_error = fmt::system_error(EDOM, "test"); - EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), test_error.what()); - EXPECT_EQ(EDOM, test_error.error_code()); + auto ec = std::error_code(EDOM, std::generic_category()); + EXPECT_STREQ(test_error.what(), std::system_error(ec, "test").what()); + EXPECT_EQ(test_error.code(), ec); - auto error = fmt::system_error(0, ""); + auto error = std::system_error(std::error_code()); try { throw fmt::system_error(EDOM, "test {}", "error"); - } catch (const fmt::system_error& e) { + } catch (const std::system_error& e) { error = e; } fmt::memory_buffer message; fmt::format_system_error(message, EDOM, "test error"); - EXPECT_EQ(to_string(message), error.what()); - EXPECT_EQ(EDOM, error.error_code()); + EXPECT_EQ(error.what(), to_string(message)); + EXPECT_EQ(error.code(), std::error_code(EDOM, std::generic_category())); } TEST(util_test, report_system_error) { diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index 46be75b8..c03e8b1f 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -281,10 +281,10 @@ TEST(ExpectTest, EXPECT_SYSTEM_ERROR) { EXPECT_NONFATAL_FAILURE( EXPECT_SYSTEM_ERROR(throw_exception(), EDOM, "test"), "Expected: throw_exception() throws an exception of " - "type fmt::system_error.\n Actual: it throws a different type."); + "type std::system_error.\n Actual: it throws a different type."); EXPECT_NONFATAL_FAILURE( EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "test"), - "Expected: do_nothing() throws an exception of type fmt::system_error.\n" + "Expected: do_nothing() throws an exception of type std::system_error.\n" " Actual: it throws nothing."); EXPECT_NONFATAL_FAILURE( EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other"), @@ -292,8 +292,8 @@ TEST(ExpectTest, EXPECT_SYSTEM_ERROR) { "throw_system_error() throws an exception with a different message.\n" "Expected: {}\n" " Actual: {}", - format_system_error(EDOM, "other"), - format_system_error(EDOM, "test"))); + system_error_message(EDOM, "other"), + system_error_message(EDOM, "test"))); } TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) { @@ -317,7 +317,7 @@ TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) { TEST(UtilTest, FormatSystemError) { fmt::memory_buffer out; fmt::format_system_error(out, EDOM, "test message"); - EXPECT_EQ(to_string(out), format_system_error(EDOM, "test message")); + EXPECT_EQ(to_string(out), system_error_message(EDOM, "test message")); } #if FMT_USE_FCNTL @@ -424,7 +424,7 @@ TEST(OutputRedirectTest, ErrorInDtor) { FMT_POSIX(close(write_fd)); SUPPRESS_ASSERT(redir.reset(nullptr)); }, - format_system_error(EBADF, "cannot flush stream")); + system_error_message(EBADF, "cannot flush stream")); write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail } diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc index d71b951e..dc542184 100644 --- a/test/gtest-extra.cc +++ b/test/gtest-extra.cc @@ -79,9 +79,3 @@ std::string read(file& f, size_t count) { } #endif // FMT_USE_FCNTL - -std::string format_system_error(int error_code, fmt::string_view message) { - fmt::memory_buffer out; - format_system_error(out, error_code, message); - return to_string(out); -} diff --git a/test/gtest-extra.h b/test/gtest-extra.h index 84a6ee67..bbda1c01 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -53,11 +53,15 @@ FMT_TEST_THROW_(statement, expected_exception, expected_message, \ GTEST_NONFATAL_FAILURE_) -std::string format_system_error(int error_code, fmt::string_view message); +inline std::string system_error_message(int error_code, + const std::string& message) { + auto ec = std::error_code(error_code, std::generic_category()); + return std::system_error(ec, message).what(); +} #define EXPECT_SYSTEM_ERROR(statement, error_code, message) \ - EXPECT_THROW_MSG(statement, fmt::system_error, \ - format_system_error(error_code, message)) + EXPECT_THROW_MSG(statement, std::system_error, \ + system_error_message(error_code, message)) #if FMT_USE_FCNTL diff --git a/test/os-test.cc b/test/os-test.cc index 589f8fb2..52b8532f 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -83,11 +83,6 @@ TEST(os_test, format_windows_error) { EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), fmt::to_string(actual_message)); actual_message.resize(0); - auto max_size = fmt::detail::max_value() / 2; - fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, - fmt::string_view(nullptr, max_size)); - EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS), - fmt::to_string(actual_message)); } TEST(os_test, format_long_windows_error) { @@ -244,7 +239,7 @@ TEST(buffered_file_test, close_error_in_dtor) { FMT_POSIX(close(f->fileno())); SUPPRESS_ASSERT(f.reset(nullptr)); }, - format_system_error(EBADF, "cannot close file") + "\n"); + system_error_message(EBADF, "cannot close file") + "\n"); } TEST(buffered_file_test, close) { @@ -424,7 +419,7 @@ TEST(file_test, close_error_in_dtor) { FMT_POSIX(close(f->descriptor())); SUPPRESS_ASSERT(f.reset(nullptr)); }, - format_system_error(EBADF, "cannot close file") + "\n"); + system_error_message(EBADF, "cannot close file") + "\n"); } TEST(file_test, close) { diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index 19ad2753..ec75887c 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -244,7 +244,7 @@ TEST(file_test, close_no_retry_in_dtor) { saved_close_count = close_count; close_count = 0; }, - format_system_error(EINTR, "cannot close file") + "\n"); + system_error_message(EINTR, "cannot close file") + "\n"); EXPECT_EQ(2, saved_close_count); } @@ -328,7 +328,7 @@ TEST(file_test, convert_read_count) { if (sizeof(unsigned) != sizeof(size_t)) ++size; read_count = 1; read_nbyte = 0; - EXPECT_THROW(read_end.read(&c, size), fmt::system_error); + EXPECT_THROW(read_end.read(&c, size), std::system_error); read_count = 0; EXPECT_EQ(UINT_MAX, read_nbyte); } @@ -341,7 +341,7 @@ TEST(file_test, convert_write_count) { if (sizeof(unsigned) != sizeof(size_t)) ++size; write_count = 1; write_nbyte = 0; - EXPECT_THROW(write_end.write(&c, size), fmt::system_error); + EXPECT_THROW(write_end.write(&c, size), std::system_error); write_count = 0; EXPECT_EQ(UINT_MAX, write_nbyte); } @@ -420,7 +420,7 @@ TEST(buffered_file_test, close_no_retry_in_dtor) { saved_fclose_count = fclose_count; fclose_count = 0; }, - format_system_error(EINTR, "cannot close file") + "\n"); + system_error_message(EINTR, "cannot close file") + "\n"); EXPECT_EQ(2, saved_fclose_count); } diff --git a/test/util.cc b/test/util.cc index 6bdbe613..931b9fec 100644 --- a/test/util.cc +++ b/test/util.cc @@ -9,18 +9,6 @@ #include -std::string get_system_error(int error_code) { -#if defined(__MINGW32__) || !defined(_WIN32) - return strerror(error_code); -#else - enum { buffer_size = 200 }; - char buffer[buffer_size]; - if (strerror_s(buffer, buffer_size, error_code)) - throw std::exception("strerror_s failed"); - return buffer; -#endif -} - const char* const file_content = "Don't panic!"; fmt::buffered_file open_buffered_file(FILE** fp) { diff --git a/test/util.h b/test/util.h index 92871cbd..c82b31cb 100644 --- a/test/util.h +++ b/test/util.h @@ -25,8 +25,6 @@ void safe_sprintf(char (&buffer)[SIZE], const char* format, ...) { va_end(args); } -std::string get_system_error(int error_code); - extern const char* const file_content; // Opens a buffered file for reading.