Skip to content

Commit

Permalink
Add class name output to formatter for std::exception (#3076)
Browse files Browse the repository at this point in the history
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>

Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
  • Loading branch information
phprus committed Sep 10, 2022
1 parent ecffca6 commit 21c2137
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 18 deletions.
69 changes: 63 additions & 6 deletions include/fmt/std.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
#ifndef FMT_STD_H_
#define FMT_STD_H_

#include <cstdlib>
#include <exception>
#include <memory>
#include <thread>
#include <type_traits>
#include <typeinfo>
#include <utility>

#include "ostream.h"
Expand All @@ -28,6 +31,16 @@
# endif
#endif

// GCC 4 does not support FMT_HAS_INCLUDE.
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
# include <cxxabi.h>
// Android NDK with gabi++ library on some archtectures does not implement
// abi::__cxa_demangle().
# ifndef __GABIXX_CXXABI_H__
# define FMT_HAS_ABI_CXA_DEMANGLE
# endif
#endif

#ifdef __cpp_lib_filesystem
FMT_BEGIN_NAMESPACE

Expand Down Expand Up @@ -170,12 +183,56 @@ FMT_BEGIN_NAMESPACE
template <typename T, typename Char>
struct formatter<
T, Char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type>
: formatter<string_view> {
template <typename FormatContext>
auto format(const std::exception& ex, FormatContext& ctx) const ->
typename FormatContext::iterator {
return fmt::formatter<string_view>::format(ex.what(), ctx);
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;

public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
if (*it == 't') {
++it;
with_typename_ = true;
}
return it;
}

template <typename OutputIt>
auto format(const std::exception& ex,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
basic_format_specs<Char> spec;
auto out = ctx.out();
if (!with_typename_)
return detail::write_bytes(out, string_view(ex.what()), spec);

const std::type_info& ti = typeid(ex);
#ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
out = detail::write_bytes(
out,
string_view(demangled_name_ptr ? demangled_name_ptr.get() : ti.name()),
spec);
#elif FMT_MSC_VERSION
string_view demangled_name_view(ti.name());
if (demangled_name_view.starts_with("class "))
demangled_name_view.remove_prefix(6);
else if (demangled_name_view.starts_with("struct "))
demangled_name_view.remove_prefix(7);
out = detail::write_bytes(out, demangled_name_view, spec);
#else
out = detail::write_bytes(out, string_view(ti.name()), spec);
#endif
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, Char(' '));
out = detail::write_bytes(out, string_view(ex.what()), spec);

return out;
}
};
FMT_END_NAMESPACE
Expand Down
42 changes: 30 additions & 12 deletions test/std-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,39 @@ TEST(std_test, variant) {
#endif
}

TEST(std_test, exception) {
std::string str("Test Exception");
std::string escstr = fmt::format("\"{}\"", str);

template <typename Catch> void exception_test() {
try {
throw std::runtime_error(str);
} catch (const std::exception& ex) {
EXPECT_EQ(fmt::format("{}", ex), str);
EXPECT_EQ(fmt::format("{:?}", ex), escstr);
throw std::runtime_error("Test Exception");
} catch (const Catch& ex) {
EXPECT_EQ("Test Exception", fmt::format("{}", ex));
EXPECT_EQ("std::runtime_error: Test Exception", fmt::format("{:t}", ex));
}
}

namespace my_ns1 {
namespace my_ns2 {
struct my_exception : public std::exception {
private:
std::string msg;

public:
my_exception(const std::string& s) : msg(s) {}
const char* what() const noexcept override;
};
const char* my_exception::what() const noexcept { return msg.c_str(); }
} // namespace my_ns2
} // namespace my_ns1

TEST(std_test, exception) {
exception_test<std::exception>();
exception_test<std::runtime_error>();

try {
throw std::runtime_error(str);
} catch (const std::runtime_error& ex) {
EXPECT_EQ(fmt::format("{}", ex), str);
EXPECT_EQ(fmt::format("{:?}", ex), escstr);
using namespace my_ns1::my_ns2;
throw my_exception("My Exception");
} catch (const std::exception& ex) {
EXPECT_EQ("my_ns1::my_ns2::my_exception: My Exception",
fmt::format("{:t}", ex));
EXPECT_EQ("My Exception", fmt::format("{:}", ex));
}
}

0 comments on commit 21c2137

Please sign in to comment.