mirror of
https://github.com/fmtlib/fmt.git
synced 2026-07-30 16:26:27 +08:00
Add formatter for std::exception_ptr
The formatter prints std::exception::what() (if that's what the exception is) and prints the type name for exceptions that are not derived from std::exception. Fixes #4808
This commit is contained in:
parent
cd67635a19
commit
c9539a7b88
@ -506,6 +506,8 @@ chrono-format-specifications).
|
||||
- [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset)
|
||||
- [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code)
|
||||
- [`std::exception`](https://en.cppreference.com/w/cpp/error/exception)
|
||||
- [`std::exception_ptr`](
|
||||
https://en.cppreference.com/w/cpp/error/exception_ptr)
|
||||
- [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path)
|
||||
- [`std::monostate`](
|
||||
https://en.cppreference.com/w/cpp/utility/variant/monostate)
|
||||
|
||||
@ -649,6 +649,73 @@ struct formatter<
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_EXCEPTIONS
|
||||
namespace detail {
|
||||
|
||||
// Formats the exception currently being handled, writing its (dynamic) type
|
||||
// name and message, and recursing into nested exceptions. Must be called from
|
||||
// within a catch block.
|
||||
template <typename OutputIt>
|
||||
auto write_current_exception(OutputIt out) -> OutputIt {
|
||||
try {
|
||||
throw;
|
||||
} catch (const std::exception& ex) {
|
||||
// Reuse the std::exception formatter to render "<type>: <what>".
|
||||
out = fmt::format_to(out, "{:t}", ex);
|
||||
} catch (...) {
|
||||
// The thrown object does not derive from std::exception, so it has no
|
||||
// what(); fall back to its type name obtained from the ABI, if available.
|
||||
# if FMT_USE_RTTI && defined(FMT_HAS_ABI_CXA_DEMANGLE)
|
||||
if (const std::type_info* tp = abi::__cxa_current_exception_type())
|
||||
out = write_demangled_name(out, *tp);
|
||||
else
|
||||
out = write<char>(out, string_view("<unknown exception>"));
|
||||
# else
|
||||
out = write<char>(out, string_view("<unknown exception>"));
|
||||
# endif
|
||||
}
|
||||
|
||||
// Recurse into nested exceptions.
|
||||
try {
|
||||
throw;
|
||||
} catch (const std::nested_exception& ne) {
|
||||
if (ne.nested_ptr()) {
|
||||
out = write<char>(out, string_view(": "));
|
||||
try {
|
||||
std::rethrow_exception(ne.nested_ptr());
|
||||
} catch (...) {
|
||||
out = write_current_exception(out);
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// {fmt} has no formatter for std::exception_ptr through the std::exception
|
||||
// formatter above because std::exception_ptr is not derived from
|
||||
// std::exception. An empty exception_ptr is formatted as "<no exception>".
|
||||
template <> struct formatter<std::exception_ptr> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
auto format(const std::exception_ptr& eptr, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
if (!eptr)
|
||||
return detail::write<char>(ctx.out(), string_view("<no exception>"));
|
||||
try {
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (...) {
|
||||
return detail::write_current_exception(ctx.out());
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif // FMT_USE_EXCEPTIONS
|
||||
|
||||
template <int N, typename Char>
|
||||
struct formatter<detail::bitint<N>, Char> : formatter<long long, Char> {
|
||||
static_assert(N <= 64, "unsupported _BitInt");
|
||||
|
||||
@ -404,6 +404,48 @@ TEST(std_test, exception) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if FMT_USE_EXCEPTIONS
|
||||
TEST(std_test, exception_ptr) {
|
||||
using testing::StartsWith;
|
||||
|
||||
EXPECT_EQ("<no exception>", fmt::format("{}", std::exception_ptr()));
|
||||
|
||||
auto eptr = std::make_exception_ptr(std::runtime_error("Test Exception"));
|
||||
# if FMT_USE_RTTI
|
||||
EXPECT_EQ("std::runtime_error: Test Exception", fmt::format("{}", eptr));
|
||||
# else
|
||||
EXPECT_EQ("Test Exception", fmt::format("{}", eptr));
|
||||
# endif
|
||||
|
||||
// A nested exception is formatted recursively. The outer type name is an
|
||||
// implementation-defined wrapper (e.g. std::_Nested_exception<...>) so only
|
||||
// the messages and the nested rendering are checked here.
|
||||
std::exception_ptr nested;
|
||||
try {
|
||||
try {
|
||||
throw std::logic_error("inner");
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::runtime_error("outer"));
|
||||
}
|
||||
} catch (...) {
|
||||
nested = std::current_exception();
|
||||
}
|
||||
# if FMT_USE_RTTI
|
||||
EXPECT_THAT(fmt::format("{}", nested),
|
||||
testing::AllOf(testing::HasSubstr("outer"),
|
||||
testing::HasSubstr(": std::logic_error: inner")));
|
||||
# endif
|
||||
|
||||
// A non-std::exception object is formatted by its type name when available.
|
||||
auto int_eptr = std::make_exception_ptr(42);
|
||||
# if FMT_USE_RTTI && defined(FMT_HAS_ABI_CXA_DEMANGLE)
|
||||
EXPECT_EQ("int", fmt::format("{}", int_eptr));
|
||||
# else
|
||||
EXPECT_THAT(fmt::format("{}", int_eptr), StartsWith("<unknown exception>"));
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FMT_USE_RTTI
|
||||
TEST(std_test, type_info) {
|
||||
EXPECT_EQ(fmt::format("{}", typeid(std::runtime_error)),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user