mirror of
https://github.com/fmtlib/fmt.git
synced 2026-10-29 10:27:38 +08:00
Add formatter for std::stacktrace and std::stacktrace_entry (C++23)
Adds support for formatting std::stacktrace and std::stacktrace_entry from <stacktrace> (C++23, __cpp_lib_stacktrace). - stacktrace_entry formats as 'function at file:line' when symbols are available, or as a hex address otherwise - stacktrace formats one indexed entry per line: '#N description' - Both formatters inherit string_view specs (width, fill, align) - Guarded by __cpp_lib_stacktrace for portability
This commit is contained in:
parent
35c58f084e
commit
cfa0f66ffb
@ -512,11 +512,18 @@ chrono-format-spec).
|
||||
- [`std::monostate`](
|
||||
https://en.cppreference.com/w/cpp/utility/variant/monostate)
|
||||
- [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional)
|
||||
- `std::stacktrace`
|
||||
- `std::stacktrace_entry`
|
||||
- [`std::source_location`](
|
||||
https://en.cppreference.com/w/cpp/utility/source_location)
|
||||
- [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id)
|
||||
- [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant)
|
||||
|
||||
When the C++23 stacktrace feature is available, `std::stacktrace_entry` is
|
||||
formatted as a function description followed by its source location when
|
||||
available, or its address when symbols are unavailable. `std::stacktrace`
|
||||
formats one indexed entry per line. Both support width and alignment specs.
|
||||
|
||||
::: ptr(const std::unique_ptr<T, Deleter>&)
|
||||
|
||||
::: ptr(const std::shared_ptr<T>&)
|
||||
|
||||
@ -11,6 +11,10 @@
|
||||
#include "format.h"
|
||||
#include "ostream.h"
|
||||
|
||||
#if FMT_HAS_INCLUDE(<version>)
|
||||
# include <version>
|
||||
#endif
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <atomic>
|
||||
# include <bitset>
|
||||
@ -45,12 +49,12 @@
|
||||
# if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE(<expected>)
|
||||
# include <expected>
|
||||
# endif
|
||||
# if FMT_CPLUSPLUS >= 202302L && FMT_HAS_INCLUDE(<stacktrace>) && \
|
||||
defined(__cpp_lib_stacktrace)
|
||||
# include <stacktrace>
|
||||
# endif
|
||||
#endif // FMT_MODULE
|
||||
|
||||
#if FMT_HAS_INCLUDE(<version>)
|
||||
# include <version>
|
||||
#endif
|
||||
|
||||
// GCC 4 does not support FMT_HAS_INCLUDE.
|
||||
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
|
||||
# include <cxxabi.h>
|
||||
@ -467,6 +471,69 @@ template <> struct formatter<std::source_location> {
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef __cpp_lib_stacktrace
|
||||
namespace detail {
|
||||
inline auto write_stacktrace_address(appender out,
|
||||
const std::stacktrace_entry& entry)
|
||||
-> appender {
|
||||
return format_to(out, "{:#x}",
|
||||
static_cast<std::uintptr_t>(
|
||||
reinterpret_cast<std::uintptr_t>(entry.native_handle())));
|
||||
}
|
||||
|
||||
inline auto write_stacktrace_entry(appender out,
|
||||
const std::stacktrace_entry& entry)
|
||||
-> appender {
|
||||
auto description = entry.description();
|
||||
if (description.empty())
|
||||
return write_stacktrace_address(out, entry);
|
||||
|
||||
out = write(out, string_view(description));
|
||||
auto source_file = entry.source_file();
|
||||
auto source_line = entry.source_line();
|
||||
if (!source_file.empty() && source_line != 0) {
|
||||
out = write(out, " at ");
|
||||
out = write(out, string_view(source_file));
|
||||
out = write(out, ':');
|
||||
out = write<char>(out, source_line);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
FMT_EXPORT template <> struct formatter<std::stacktrace_entry>
|
||||
: formatter<string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::stacktrace_entry& entry, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto buf = memory_buffer();
|
||||
detail::write_stacktrace_entry(appender(buf), entry);
|
||||
return formatter<string_view>::format(string_view(buf.data(), buf.size()),
|
||||
ctx);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT template <> struct formatter<std::stacktrace>
|
||||
: formatter<string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::stacktrace& trace, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto buf = memory_buffer();
|
||||
auto out = appender(buf);
|
||||
auto index = std::size_t();
|
||||
for (const auto& entry : trace) {
|
||||
if (index != 0) *out++ = '\n';
|
||||
out = detail::write(out, '#');
|
||||
out = detail::write<char>(out, index++);
|
||||
out = detail::write(out, ' ');
|
||||
out = detail::write_stacktrace_entry(out, entry);
|
||||
}
|
||||
return formatter<string_view>::format(string_view(buf.data(), buf.size()),
|
||||
ctx);
|
||||
}
|
||||
};
|
||||
#endif // __cpp_lib_stacktrace
|
||||
|
||||
#if FMT_CPP_LIB_VARIANT
|
||||
|
||||
template <typename T> struct is_variant_like {
|
||||
|
||||
@ -120,6 +120,74 @@ TEST(std_test, source_location) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cpp_lib_stacktrace
|
||||
TEST(std_test, stacktrace_entry) {
|
||||
auto trace = std::stacktrace::current();
|
||||
ASSERT_FALSE(trace.empty());
|
||||
|
||||
const auto& entry = trace[0];
|
||||
auto formatted = fmt::format("{}", entry);
|
||||
EXPECT_FALSE(formatted.empty());
|
||||
|
||||
auto description = entry.description();
|
||||
if (!description.empty())
|
||||
EXPECT_NE(formatted.find(description), std::string::npos);
|
||||
auto source_file = entry.source_file();
|
||||
auto source_line = entry.source_line();
|
||||
if (!source_file.empty() && source_line != 0) {
|
||||
auto location = fmt::format("{}:{}", source_file, source_line);
|
||||
EXPECT_NE(formatted.find(location), std::string::npos);
|
||||
if (!description.empty()) {
|
||||
auto expected = fmt::format("{} at {}", description, location);
|
||||
EXPECT_NE(formatted.find(expected), std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
auto expected = formatted;
|
||||
if (expected.size() < 40) expected.insert(0, 40 - expected.size(), ' ');
|
||||
EXPECT_EQ(fmt::format("{:>40}", entry), expected);
|
||||
expected = formatted;
|
||||
if (expected.size() < 40) expected.append(40 - expected.size(), ' ');
|
||||
EXPECT_EQ(fmt::format("{:<40}", entry), expected);
|
||||
EXPECT_EQ(fmt::format("{:>{}}", entry, formatted.size() + 2),
|
||||
" " + formatted);
|
||||
}
|
||||
|
||||
TEST(std_test, stacktrace) {
|
||||
auto trace = std::stacktrace::current();
|
||||
ASSERT_FALSE(trace.empty());
|
||||
|
||||
auto formatted = fmt::format("{}", trace);
|
||||
EXPECT_FALSE(formatted.empty());
|
||||
EXPECT_NE(formatted.find(fmt::format("#0 {}", trace[0])),
|
||||
std::string::npos);
|
||||
if (trace.size() > 1)
|
||||
EXPECT_NE(formatted.find("\n#1 "), std::string::npos);
|
||||
|
||||
auto line_count = std::size_t();
|
||||
for (auto c : formatted)
|
||||
if (c == '\n') ++line_count;
|
||||
EXPECT_EQ(line_count + 1, trace.size());
|
||||
|
||||
auto expected = formatted;
|
||||
if (expected.size() < 40) expected.insert(0, 40 - expected.size(), ' ');
|
||||
EXPECT_EQ(fmt::format("{:>40}", trace), expected);
|
||||
expected = formatted;
|
||||
if (expected.size() < 40) expected.append(40 - expected.size(), ' ');
|
||||
EXPECT_EQ(fmt::format("{:<40}", trace), expected);
|
||||
EXPECT_EQ(fmt::format("{:>{}}", trace, formatted.size() + 2),
|
||||
" " + formatted);
|
||||
}
|
||||
|
||||
TEST(std_test, stacktrace_empty) {
|
||||
// An empty stacktrace should format to an empty string without crashing.
|
||||
auto trace = std::stacktrace();
|
||||
EXPECT_EQ(fmt::format("{}", trace), "");
|
||||
// Width/align on empty trace should pad correctly.
|
||||
EXPECT_EQ(fmt::format("{:>5}", trace), " ");
|
||||
}
|
||||
#endif // __cpp_lib_stacktrace
|
||||
|
||||
TEST(std_test, optional) {
|
||||
#ifdef __cpp_lib_optional
|
||||
EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user