mirror of
https://github.com/fmtlib/fmt.git
synced 2026-07-30 16:26:27 +08:00
Format nested exceptions in std::exception formatter (#4844)
Detect when a std::exception also derives from std::nested_exception
(e.g. via std::throw_with_nested) and unwind the whole chain, joining
each level with ": ". The type-name flag ({:t}) is honored at every
level, and the std::exception_ptr formatter inherits this behavior.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7077b016cc
commit
1a99c2630c
@ -637,7 +637,12 @@ struct formatter<
|
||||
template <typename Context>
|
||||
auto format(const std::exception& ex, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
return write(ctx.out(), ex);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename OutputIt>
|
||||
auto write(OutputIt out, const std::exception& ex) const -> OutputIt {
|
||||
#if FMT_USE_RTTI
|
||||
if (with_typename_) {
|
||||
out = detail::write_demangled_name(out, typeid(ex));
|
||||
@ -645,7 +650,24 @@ struct formatter<
|
||||
*out++ = ' ';
|
||||
}
|
||||
#endif
|
||||
return detail::write_bytes<char>(out, string_view(ex.what()));
|
||||
out = detail::write_bytes<char>(out, string_view(ex.what()));
|
||||
#if FMT_USE_RTTI
|
||||
// If the exception carries a nested exception (e.g. via
|
||||
// std::throw_with_nested), format the whole chain.
|
||||
if (auto* nested = dynamic_cast<const std::nested_exception*>(&ex)) {
|
||||
if (auto ep = nested->nested_ptr()) {
|
||||
out = detail::write(out, string_view(": "));
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (const std::exception& nested_ex) {
|
||||
out = write(out, nested_ex);
|
||||
} catch (...) {
|
||||
out = detail::write(out, string_view("unknown exception"));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -402,6 +402,34 @@ TEST(std_test, exception) {
|
||||
StartsWith("std::filesystem::filesystem_error: "));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FMT_USE_RTTI
|
||||
// Nested exceptions (e.g. from std::throw_with_nested) are unwound.
|
||||
try {
|
||||
try {
|
||||
throw std::runtime_error("inner");
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::runtime_error("outer"));
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
EXPECT_EQ("outer: inner", fmt::format("{}", ex));
|
||||
}
|
||||
|
||||
// Multiple levels of nesting.
|
||||
try {
|
||||
try {
|
||||
try {
|
||||
throw std::runtime_error("level 3");
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::runtime_error("level 2"));
|
||||
}
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::runtime_error("level 1"));
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
EXPECT_EQ("level 1: level 2: level 3", fmt::format("{}", ex));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(std_test, exception_ptr) {
|
||||
@ -421,6 +449,19 @@ TEST(std_test, exception_ptr) {
|
||||
#if FMT_USE_RTTI
|
||||
EXPECT_EQ(fmt::format("{:t}", p2),
|
||||
"my_ns1::my_ns2::my_exception: My Exception");
|
||||
|
||||
// Nested exceptions are unwound through an exception_ptr too.
|
||||
std::exception_ptr p3;
|
||||
try {
|
||||
try {
|
||||
throw std::runtime_error("inner");
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::runtime_error("outer"));
|
||||
}
|
||||
} catch (...) {
|
||||
p3 = std::current_exception();
|
||||
}
|
||||
EXPECT_EQ(fmt::format("{}", p3), "outer: inner");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user