parent
3557d7b885
commit
e2b1252438
@ -113,6 +113,7 @@ target_sources(
|
||||
src/ctrace.cpp
|
||||
src/exceptions.cpp
|
||||
src/from_current.cpp
|
||||
src/formatting.cpp
|
||||
src/options.cpp
|
||||
src/utils.cpp
|
||||
src/demangle/demangle_with_cxxabi.cpp
|
||||
|
||||
@ -182,8 +182,6 @@ namespace cpptrace {
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
private:
|
||||
void print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
|
||||
void print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
|
||||
friend void print_terminate_trace();
|
||||
};
|
||||
|
||||
|
||||
67
include/cpptrace/formatting.hpp
Normal file
67
include/cpptrace/formatting.hpp
Normal file
@ -0,0 +1,67 @@
|
||||
#ifndef CPPTRACE_FORMATTING_HPP
|
||||
#define CPPTRACE_FORMATTING_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace cpptrace {
|
||||
class CPPTRACE_EXPORT formatter {
|
||||
class impl;
|
||||
// can't be a std::unique_ptr due to msvc awfulness with dllimport/dllexport and https://stackoverflow.com/q/4145605/15675011
|
||||
impl* pimpl;
|
||||
|
||||
public:
|
||||
formatter();
|
||||
~formatter();
|
||||
|
||||
formatter(formatter&&);
|
||||
formatter(const formatter&);
|
||||
formatter& operator=(formatter&&);
|
||||
formatter& operator=(const formatter&);
|
||||
|
||||
formatter& set_header(std::string);
|
||||
enum class color_mode {
|
||||
always,
|
||||
none,
|
||||
automatic,
|
||||
};
|
||||
formatter& set_color_mode(color_mode);
|
||||
enum class address_mode {
|
||||
raw,
|
||||
object,
|
||||
none,
|
||||
};
|
||||
formatter& set_address_mode(address_mode);
|
||||
formatter& set_snippets(bool);
|
||||
formatter& set_snippet_context(int);
|
||||
formatter& include_column(bool);
|
||||
formatter& set_filter(std::function<bool(const stacktrace_frame&)>);
|
||||
|
||||
std::string format(const stacktrace_frame&) const;
|
||||
std::string format(const stacktrace_frame&, bool color) const;
|
||||
|
||||
std::string format(const stacktrace&) const;
|
||||
std::string format(const stacktrace&, bool color) const;
|
||||
|
||||
void print(const stacktrace_frame&) const;
|
||||
void print(const stacktrace_frame&, bool color) const;
|
||||
void print(std::ostream&, const stacktrace_frame&) const;
|
||||
void print(std::ostream&, const stacktrace_frame&, bool color) const;
|
||||
void print(std::FILE*, const stacktrace_frame&) const;
|
||||
void print(std::FILE*, const stacktrace_frame&, bool color) const;
|
||||
|
||||
void print(const stacktrace&) const;
|
||||
void print(const stacktrace&, bool color) const;
|
||||
void print(std::ostream&, const stacktrace&) const;
|
||||
void print(std::ostream&, const stacktrace&, bool color) const;
|
||||
void print(std::FILE*, const stacktrace&) const;
|
||||
void print(std::FILE*, const stacktrace&, bool color) const;
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT const formatter& get_default_formatter();
|
||||
}
|
||||
|
||||
#endif
|
||||
116
src/cpptrace.cpp
116
src/cpptrace.cpp
@ -1,4 +1,5 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@ -9,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cpptrace/basic.hpp"
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
@ -129,41 +131,13 @@ namespace cpptrace {
|
||||
return detail::get_frame_object_info(raw_address);
|
||||
}
|
||||
|
||||
static std::string frame_to_string(
|
||||
bool color,
|
||||
const stacktrace_frame& frame
|
||||
) {
|
||||
const auto reset = color ? RESET : "";
|
||||
const auto green = color ? GREEN : "";
|
||||
const auto yellow = color ? YELLOW : "";
|
||||
const auto blue = color ? BLUE : "";
|
||||
std::string str;
|
||||
if(frame.is_inline) {
|
||||
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
|
||||
} else {
|
||||
str += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), frame.raw_address, reset);
|
||||
}
|
||||
if(!frame.symbol.empty()) {
|
||||
str += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
|
||||
}
|
||||
if(!frame.filename.empty()) {
|
||||
str += microfmt::format(" at {}{}{}", green, frame.filename, reset);
|
||||
if(frame.line.has_value()) {
|
||||
str += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
|
||||
if(frame.column.has_value()) {
|
||||
str += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string stacktrace_frame::to_string() const {
|
||||
return to_string(false);
|
||||
}
|
||||
|
||||
std::string stacktrace_frame::to_string(bool color) const {
|
||||
return frame_to_string(color, *this);
|
||||
// return frame_to_string(color, *this);
|
||||
return get_default_formatter().format(*this, color);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
|
||||
@ -195,89 +169,34 @@ namespace cpptrace {
|
||||
}
|
||||
|
||||
void stacktrace::print() const {
|
||||
print(std::cerr, true);
|
||||
get_default_formatter().print(*this);
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream) const {
|
||||
print(stream, true);
|
||||
get_default_formatter().print(stream, *this);
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream, bool color) const {
|
||||
print(stream, color, true, nullptr);
|
||||
get_default_formatter().print(stream, *this, color);
|
||||
}
|
||||
|
||||
static void print_frame(
|
||||
std::ostream& stream,
|
||||
bool color,
|
||||
unsigned frame_number_width,
|
||||
std::size_t counter,
|
||||
const stacktrace_frame& frame
|
||||
) {
|
||||
std::string line = microfmt::format("#{<{}} {}", frame_number_width, counter, frame.to_string(color));
|
||||
stream << line;
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
|
||||
if(
|
||||
color && (
|
||||
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
|
||||
)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
|
||||
std::size_t counter = 0;
|
||||
if(frames.empty()) {
|
||||
stream << "<empty trace>\n";
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
print_frame(stream, color, frame_number_width, counter, frame);
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
counter++;
|
||||
namespace detail {
|
||||
const formatter& get_default_snippet_formatter() {
|
||||
static formatter snippet_formatter = formatter{}.set_snippets(true);
|
||||
return snippet_formatter;
|
||||
}
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets() const {
|
||||
print_with_snippets(std::cerr, true);
|
||||
detail::get_default_snippet_formatter().print(*this);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream) const {
|
||||
print_with_snippets(stream, true);
|
||||
detail::get_default_snippet_formatter().print(stream, *this);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
|
||||
print_with_snippets(stream, color, true, nullptr);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
|
||||
if(
|
||||
color && (
|
||||
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
|
||||
)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
|
||||
std::size_t counter = 0;
|
||||
if(frames.empty()) {
|
||||
stream << "<empty trace>" << '\n';
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
print_frame(stream, color, frame_number_width, counter, frame);
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
if(frame.line.has_value() && !frame.filename.empty()) {
|
||||
stream << detail::get_snippet(frame.filename, frame.line.value(), 2, color);
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
detail::get_default_snippet_formatter().print(stream, *this, color);
|
||||
}
|
||||
|
||||
void stacktrace::clear() {
|
||||
@ -289,13 +208,12 @@ namespace cpptrace {
|
||||
}
|
||||
|
||||
std::string stacktrace::to_string(bool color) const {
|
||||
std::ostringstream oss;
|
||||
print(oss, color, false, nullptr);
|
||||
return std::move(oss).str();
|
||||
return get_default_formatter().format(*this, color);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
|
||||
return stream << trace.to_string();
|
||||
get_default_formatter().print(stream, trace);
|
||||
return stream;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
|
||||
322
src/formatting.cpp
Normal file
322
src/formatting.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
#include <cpptrace/formatting.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include "utils/optional.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace cpptrace {
|
||||
class formatter::impl {
|
||||
struct {
|
||||
std::string header = "Stack trace (most recent call first):";
|
||||
color_mode color = color_mode::automatic;
|
||||
address_mode addresses = address_mode::raw;
|
||||
bool snippets = false;
|
||||
int context_lines = 2;
|
||||
bool columns = true;
|
||||
std::function<bool(const stacktrace_frame&)> filter;
|
||||
} options;
|
||||
|
||||
public:
|
||||
void set_header(std::string header) {
|
||||
options.header = std::move(header);
|
||||
}
|
||||
void set_color_mode(formatter::color_mode mode) {
|
||||
options.color = mode;
|
||||
}
|
||||
void set_address_mode(formatter::address_mode mode) {
|
||||
options.addresses = mode;
|
||||
}
|
||||
void set_snippets(bool snippets) {
|
||||
options.snippets = snippets;
|
||||
}
|
||||
void set_snippet_context(int lines) {
|
||||
options.context_lines = lines;
|
||||
}
|
||||
void include_column(bool columns) {
|
||||
options.columns = columns;
|
||||
}
|
||||
void set_filter(std::function<bool(const stacktrace_frame&)> filter) {
|
||||
options.filter = filter;
|
||||
}
|
||||
|
||||
std::string format(const stacktrace_frame& frame, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
return frame_to_string(frame, color_override.value_or(options.color == color_mode::always));
|
||||
}
|
||||
|
||||
std::string format(const stacktrace& trace, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
std::ostringstream oss;
|
||||
print_internal(oss, trace, false, color_override);
|
||||
return std::move(oss).str();
|
||||
}
|
||||
|
||||
void print(const stacktrace_frame& frame, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
print(std::cout, frame, color_override);
|
||||
}
|
||||
void print(
|
||||
std::ostream& stream,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
print_frame_internal(stream, frame, color_override);
|
||||
}
|
||||
void print(
|
||||
std::FILE* file,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
auto str = format(frame, color_override);
|
||||
std::fwrite(str.data(), 1, str.size(), file);
|
||||
}
|
||||
|
||||
void print(const stacktrace& trace, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
print(std::cout, trace, color_override);
|
||||
}
|
||||
void print(
|
||||
std::ostream& stream,
|
||||
const stacktrace& trace,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
print_internal(stream, trace, true, color_override);
|
||||
}
|
||||
void print(
|
||||
std::FILE* file,
|
||||
const stacktrace& trace,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
auto str = format(trace, color_override);
|
||||
std::fwrite(str.data(), 1, str.size(), file);
|
||||
}
|
||||
|
||||
private:
|
||||
bool stream_is_tty(std::ostream& stream) const {
|
||||
// not great, but it'll have to do
|
||||
return (&stream == &std::cout && isatty(stdout_fileno))
|
||||
|| (&stream == &std::cerr && isatty(stderr_fileno));
|
||||
}
|
||||
|
||||
void maybe_ensure_virtual_terminal_processing(std::ostream& stream, bool color) const {
|
||||
if(color && stream_is_tty(stream)) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
bool should_do_color(std::ostream& stream, detail::optional<bool> color_override) const {
|
||||
bool do_color = options.color == color_mode::always || color_override.value_or(false);
|
||||
if(
|
||||
(options.color == color_mode::automatic || options.color == color_mode::always) &&
|
||||
(!color_override || color_override.unwrap() != false) &&
|
||||
stream_is_tty(stream)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
do_color = true;
|
||||
}
|
||||
return do_color;
|
||||
}
|
||||
|
||||
void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, detail::optional<bool> color_override) const {
|
||||
bool do_color = should_do_color(stream, color_override);
|
||||
maybe_ensure_virtual_terminal_processing(stream, do_color);
|
||||
print_internal(stream, trace, newline_at_end, do_color);
|
||||
}
|
||||
|
||||
void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, bool color) const {
|
||||
if(!options.header.empty()) {
|
||||
stream << options.header << '\n';
|
||||
}
|
||||
std::size_t counter = 0;
|
||||
const auto& frames = trace.frames;
|
||||
if(frames.empty()) {
|
||||
stream << "<empty trace>\n";
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
if(options.filter && !options.filter(frame)) {
|
||||
counter++;
|
||||
continue;
|
||||
}
|
||||
print_frame_internal(stream, frame, color, frame_number_width, counter);
|
||||
if(frame.line.has_value() && !frame.filename.empty() && options.snippets) {
|
||||
auto snippet = detail::get_snippet(
|
||||
frame.filename,
|
||||
frame.line.value(),
|
||||
options.context_lines,
|
||||
color
|
||||
);
|
||||
if(!snippet.empty()) {
|
||||
stream << '\n';
|
||||
stream << snippet;
|
||||
}
|
||||
}
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
void print_frame_internal(
|
||||
std::ostream& stream,
|
||||
const stacktrace_frame& frame,
|
||||
bool color,
|
||||
unsigned frame_number_width,
|
||||
std::size_t counter
|
||||
) const {
|
||||
std::string line = microfmt::format("#{<{}} {}", frame_number_width, counter, frame_to_string(frame, color));
|
||||
stream << line;
|
||||
}
|
||||
|
||||
void print_frame_internal(
|
||||
std::ostream& stream,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override
|
||||
) const {
|
||||
bool do_color = should_do_color(stream, color_override);
|
||||
maybe_ensure_virtual_terminal_processing(stream, do_color);
|
||||
stream << frame_to_string(frame, do_color);
|
||||
}
|
||||
|
||||
std::string frame_to_string(const stacktrace_frame& frame, bool color) const {
|
||||
const auto reset = color ? RESET : "";
|
||||
const auto green = color ? GREEN : "";
|
||||
const auto yellow = color ? YELLOW : "";
|
||||
const auto blue = color ? BLUE : "";
|
||||
std::string str;
|
||||
if(frame.is_inline) {
|
||||
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
|
||||
} else {
|
||||
auto address = options.addresses == address_mode::raw ? frame.raw_address : frame.object_address;
|
||||
str += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), address, reset);
|
||||
}
|
||||
if(!frame.symbol.empty()) {
|
||||
str += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
|
||||
}
|
||||
if(!frame.filename.empty()) {
|
||||
str += microfmt::format(" at {}{}{}", green, frame.filename, reset);
|
||||
if(frame.line.has_value()) {
|
||||
str += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
|
||||
if(frame.column.has_value() && options.columns) {
|
||||
str += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
formatter::formatter() : pimpl(new impl) {}
|
||||
formatter::~formatter() {
|
||||
delete pimpl;
|
||||
}
|
||||
|
||||
formatter::formatter(formatter&& other) : pimpl(detail::exchange(other.pimpl, nullptr)) {}
|
||||
formatter::formatter(const formatter& other) : pimpl(new impl(*other.pimpl)) {}
|
||||
formatter& formatter::operator=(formatter&& other) {
|
||||
if(pimpl) {
|
||||
delete pimpl;
|
||||
}
|
||||
pimpl = detail::exchange(other.pimpl, nullptr);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::operator=(const formatter& other) {
|
||||
if(pimpl) {
|
||||
delete pimpl;
|
||||
}
|
||||
pimpl = new impl(*other.pimpl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
formatter& formatter::set_header(std::string header) {
|
||||
pimpl->set_header(std::move(header));
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::set_color_mode(color_mode mode) {
|
||||
pimpl->set_color_mode(mode);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::set_address_mode(address_mode mode) {
|
||||
pimpl->set_address_mode(mode);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::set_snippets(bool snippets) {
|
||||
pimpl->set_snippets(snippets);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::set_snippet_context(int lines) {
|
||||
pimpl->set_snippet_context(lines);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::include_column(bool columns) {
|
||||
pimpl->include_column(columns);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::set_filter(std::function<bool(const stacktrace_frame&)> filter) {
|
||||
pimpl->set_filter(std::move(filter));
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string formatter::format(const stacktrace_frame& frame) const {
|
||||
return pimpl->format(frame);
|
||||
}
|
||||
std::string formatter::format(const stacktrace_frame& frame, bool color) const {
|
||||
return pimpl->format(frame, color);
|
||||
}
|
||||
|
||||
std::string formatter::format(const stacktrace& trace) const {
|
||||
return pimpl->format(trace);
|
||||
}
|
||||
std::string formatter::format(const stacktrace& trace, bool color) const {
|
||||
return pimpl->format(trace, color);
|
||||
}
|
||||
|
||||
void formatter::print(const stacktrace& trace) const {
|
||||
pimpl->print(trace);
|
||||
}
|
||||
void formatter::print(const stacktrace& trace, bool color) const {
|
||||
pimpl->print(trace, color);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace& trace) const {
|
||||
pimpl->print(stream, trace);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace& trace, bool color) const {
|
||||
pimpl->print(stream, trace, color);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace& trace) const {
|
||||
pimpl->print(file, trace);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace& trace, bool color) const {
|
||||
pimpl->print(file, trace, color);
|
||||
}
|
||||
|
||||
void formatter::print(const stacktrace_frame& frame) const {
|
||||
pimpl->print(frame);
|
||||
}
|
||||
void formatter::print(const stacktrace_frame& frame, bool color) const {
|
||||
pimpl->print(frame, color);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace_frame& frame) const {
|
||||
pimpl->print(stream, frame);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace_frame& frame, bool color) const {
|
||||
pimpl->print(stream, frame, color);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace_frame& frame) const {
|
||||
pimpl->print(file, frame);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace_frame& frame, bool color) const {
|
||||
pimpl->print(file, frame, color);
|
||||
}
|
||||
|
||||
const formatter& get_default_formatter() {
|
||||
static formatter formatter;
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
@ -135,7 +135,10 @@ namespace detail {
|
||||
if(color && line == target_line) {
|
||||
snippet += RESET;
|
||||
}
|
||||
snippet += lines[line - original_begin] + "\n";
|
||||
snippet += lines[line - original_begin];
|
||||
if(line != end) {
|
||||
snippet += '\n';
|
||||
}
|
||||
}
|
||||
return snippet;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <cpptrace/utils.hpp>
|
||||
#include <cpptrace/exceptions.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@ -27,12 +28,9 @@ namespace cpptrace {
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
generate_trace(1).print(
|
||||
std::cerr,
|
||||
isatty(stderr_fileno),
|
||||
true,
|
||||
"Stack trace to reach terminate handler (most recent call first):"
|
||||
);
|
||||
formatter{}
|
||||
.set_header("Stack trace to reach terminate handler (most recent call first):")
|
||||
.print(std::cerr, generate_trace(1));
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
|
||||
@ -189,6 +189,13 @@ namespace detail {
|
||||
return static_cast<U>(v);
|
||||
}
|
||||
|
||||
template<typename T, typename U = T>
|
||||
T exchange(T& obj, U&& value) {
|
||||
T old = std::move(obj);
|
||||
obj = std::forward<U>(value);
|
||||
return old;
|
||||
}
|
||||
|
||||
struct monostate {};
|
||||
|
||||
// TODO: Rework some stuff here. Not sure deleters should be optional or moved.
|
||||
|
||||
258
test/unit/lib/formatting.cpp
Normal file
258
test/unit/lib/formatting.cpp
Normal file
@ -0,0 +1,258 @@
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gtest/gtest-matchers.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using cpptrace::detail::split;
|
||||
using testing::ElementsAre;
|
||||
|
||||
namespace {
|
||||
|
||||
cpptrace::stacktrace make_test_stacktrace() {
|
||||
cpptrace::stacktrace trace;
|
||||
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "foo.cpp", "foo()", false});
|
||||
trace.frames.push_back({0x2, 0x1002, {30}, {40}, "bar.cpp", "bar()", false});
|
||||
trace.frames.push_back({0x3, 0x1003, {40}, {25}, "foo.cpp", "main", false});
|
||||
return trace;
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Basic) {
|
||||
auto res = split(cpptrace::get_default_formatter().format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Inlines) {
|
||||
auto trace = make_test_stacktrace();
|
||||
trace.frames[1].is_inline = true;
|
||||
trace.frames[1].raw_address = 0;
|
||||
trace.frames[1].object_address = 0;
|
||||
auto res = split(cpptrace::get_default_formatter().format(trace), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 (inlined) in bar() at bar.cpp:30:40",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Header) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.set_header("Stack trace:");
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace:",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, NoColumn) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.include_column(false);
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20",
|
||||
"#1 0x0000000000000002 in bar() at bar.cpp:30",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, ObjectAddresses) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.set_address_mode(cpptrace::formatter::address_mode::object);
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000001001 in foo() at foo.cpp:20:30",
|
||||
"#1 0x0000000000001002 in bar() at bar.cpp:30:40",
|
||||
"#2 0x0000000000001003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Snippets) {
|
||||
cpptrace::stacktrace trace;
|
||||
unsigned line = __LINE__ + 1;
|
||||
trace.frames.push_back({0x1, 0x1001, {line}, {20}, __FILE__, "foo()", false});
|
||||
trace.frames.push_back({0x2, 0x1002, {line + 1}, {20}, __FILE__, "foo()", false});
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.set_snippets(true);
|
||||
auto res = split(formatter.format(trace), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
// frame 1
|
||||
cpptrace::microfmt::format("#0 0x0000000000000001 in foo() at {}:{}:20", __FILE__, line),
|
||||
cpptrace::microfmt::format(" {}: cpptrace::stacktrace trace;", line - 2),
|
||||
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line
|
||||
),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line + 1
|
||||
),
|
||||
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2),
|
||||
// frame 2
|
||||
cpptrace::microfmt::format("#1 0x0000000000000002 in foo() at {}:{}:20", __FILE__, line + 1),
|
||||
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line
|
||||
),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line + 1
|
||||
),
|
||||
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2),
|
||||
cpptrace::microfmt::format(" {}: .set_snippets(true);", line + 3)
|
||||
)
|
||||
);
|
||||
formatter.set_snippet_context(1);
|
||||
res = split(formatter.format(trace), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
// frame 1
|
||||
cpptrace::microfmt::format("#0 0x0000000000000001 in foo() at {}:{}:20", __FILE__, line),
|
||||
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line
|
||||
),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line + 1
|
||||
),
|
||||
// frame 2
|
||||
cpptrace::microfmt::format("#1 0x0000000000000002 in foo() at {}:{}:20", __FILE__, line + 1),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line
|
||||
),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line + 1
|
||||
),
|
||||
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Colors) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.set_color_mode(cpptrace::formatter::color_mode::always);
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 \x1B[34m0x0000000000000001\x1B[0m in \x1B[33mfoo()\x1B[0m at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m20\x1B[0m:\x1B[34m30\x1B[0m",
|
||||
"#1 \x1B[34m0x0000000000000002\x1B[0m in \x1B[33mbar()\x1B[0m at \x1B[32mbar.cpp\x1B[0m:\x1B[34m30\x1B[0m:\x1B[34m40\x1B[0m",
|
||||
"#2 \x1B[34m0x0000000000000003\x1B[0m in \x1B[33mmain\x1B[0m at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m40\x1B[0m:\x1B[34m25\x1B[0m"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Filtering) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.set_filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
||||
});
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, MoveSemantics) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.set_filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
||||
});
|
||||
auto formatter2 = std::move(formatter);
|
||||
auto res = split(formatter2.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
cpptrace::formatter formatter3;
|
||||
formatter3 = std::move(formatter);
|
||||
auto res2 = split(formatter2.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res2,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, CopySemantics) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.set_filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
||||
});
|
||||
auto formatter2 = formatter;
|
||||
auto res = split(formatter2.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
cpptrace::formatter formatter3;
|
||||
formatter3 = formatter;
|
||||
auto res2 = split(formatter2.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res2,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user