Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add class name output to formatter for std::exception #3076

Merged
merged 1 commit into from
Sep 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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__)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need __GLIBCXX__ check? Isn't FMT_HAS_INCLUDE enough?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GCC 4 does not support FMT_HAS_INCLUDE, but it does support demangling.
It is possible to check for the presence of libstdc++ instead of gcc version, since all libstdc++ support demangling.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment that the __GLIBCXX__ is for gcc 4 here then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

# include <cxxabi.h>
// Android NDK with gabi++ library on some archtectures does not implement
// abi::__cxa_demangle().
# ifndef __GABIXX_CXXABI_H__
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is __GABIXX_CXXABI_H__ check for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Android.
See comment from Boost:

// For some archtectures (mips, mips64, x86, x86_64) cxxabi.h in Android NDK is implemented by gabi++ library
// (https://android.googlesource.com/platform/ndk/+/master/sources/cxx-stl/gabi++/), which does not implement
// abi::__cxa_demangle(). We detect this implementation by checking the include guard here.

https://github.com/boostorg/core/blob/162a4e1d24d1cab4f24cbca9489203d891f352e6/include/boost/core/demangle.hpp#L33-L35

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.
And I renamed FMT_HAS_CXXABI_H to FMT_HAS_ABI_CXA_DEMANGLE.

# 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));
}
}