Skip to content
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
7 changes: 7 additions & 0 deletions include/cpptrace/formatting.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ CPPTRACE_BEGIN_NAMESPACE
formatter& filtered_frame_placeholders(bool);
formatter& filter(std::function<bool(const stacktrace_frame&)>);
formatter& transform(std::function<stacktrace_frame(stacktrace_frame)>);
formatter& break_before_filename(bool do_break = true);

std::string format(const stacktrace_frame&) const;
std::string format(const stacktrace_frame&, bool color) const;
// The last argument is the indent to use for the filename, if break_before_filename is set
std::string format(const stacktrace_frame&, bool color, size_t filename_indent) const;

std::string format(const stacktrace&) const;
std::string format(const stacktrace&, bool color) const;
Expand All @@ -67,8 +70,12 @@ CPPTRACE_BEGIN_NAMESPACE
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;
// The last argument is the indent to use for the filename, if break_before_filename is set
void print(std::ostream&, const stacktrace_frame&, bool color, size_t filename_indent) const;
void print(std::FILE*, const stacktrace_frame&) const;
void print(std::FILE*, const stacktrace_frame&, bool color) const;
// The last argument is the indent to use for the filename, if break_before_filename is set
void print(std::FILE*, const stacktrace_frame&, bool color, size_t filename_indent) const;

void print(const stacktrace&) const;
void print(const stacktrace&, bool color) const;
Expand Down
66 changes: 52 additions & 14 deletions src/formatting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ CPPTRACE_BEGIN_NAMESPACE
address_mode addresses = address_mode::raw;
path_mode paths = path_mode::full;
bool snippets = false;
bool break_before_filename = false;
int context_lines = 2;
bool columns = true;
symbol_mode symbols = symbol_mode::full;
Expand Down Expand Up @@ -106,13 +107,17 @@ CPPTRACE_BEGIN_NAMESPACE
void transform(std::function<stacktrace_frame(stacktrace_frame)> transform) {
options.transform = std::move(transform);
}
void break_before_filename(bool do_break) {
options.break_before_filename = do_break;
}

std::string format(
const stacktrace_frame& frame,
detail::optional<bool> color_override = detail::nullopt
detail::optional<bool> color_override = detail::nullopt,
size_t filename_indent = 0
) const {
std::ostringstream oss;
print_internal(oss, frame, color_override.value_or(options.color == color_mode::always));
print_internal(oss, frame, color_override.value_or(options.color == color_mode::always), filename_indent);
return std::move(oss).str();
}

Expand All @@ -128,17 +133,19 @@ CPPTRACE_BEGIN_NAMESPACE
void print(
std::ostream& stream,
const stacktrace_frame& frame,
detail::optional<bool> color_override = detail::nullopt
detail::optional<bool> color_override = detail::nullopt,
size_t filename_indent = 0
) const {
print_internal(stream, frame, color_override);
print_internal(stream, frame, color_override, filename_indent);
stream << "\n";
}
void print(
std::FILE* file,
const stacktrace_frame& frame,
detail::optional<bool> color_override = detail::nullopt
detail::optional<bool> color_override = detail::nullopt,
size_t filename_indent = 0
) const {
auto str = format(frame, color_override);
auto str = format(frame, color_override, filename_indent);
str += "\n";
std::fwrite(str.data(), 1, str.size(), file);
}
Expand Down Expand Up @@ -206,15 +213,15 @@ CPPTRACE_BEGIN_NAMESPACE
return do_color;
}

void print_internal(std::ostream& stream, const stacktrace_frame& input_frame, detail::optional<bool> color_override) const {
void print_internal(std::ostream& stream, const stacktrace_frame& input_frame, detail::optional<bool> color_override, size_t col_indent) const {
bool color = should_do_color(stream, color_override);
maybe_ensure_virtual_terminal_processing(stream, color);
detail::optional<stacktrace_frame> transformed_frame;
if(options.transform) {
transformed_frame = options.transform(input_frame);
}
const stacktrace_frame& frame = options.transform ? transformed_frame.unwrap() : input_frame;
write_frame(stream, frame, color);
write_frame(stream, frame, color, col_indent);
}

void print_internal(std::ostream& stream, const stacktrace& trace, detail::optional<bool> color_override) const {
Expand All @@ -223,6 +230,7 @@ CPPTRACE_BEGIN_NAMESPACE
write_trace(stream, trace, color);
}


void write_trace(std::ostream& stream, const stacktrace& trace, bool color) const {
if(!options.header.empty()) {
stream << options.header << '\n';
Expand All @@ -245,11 +253,12 @@ CPPTRACE_BEGIN_NAMESPACE
counter++;
continue;
}
microfmt::print(stream, "#{<{}} ", frame_number_width, counter);

size_t filename_indent = write_frame_number(stream, frame_number_width, counter);
if(filter_out_frame) {
microfmt::print(stream, "(filtered)");
} else {
write_frame(stream, frame, color);
write_frame(stream, frame, color, filename_indent);
if(frame.line.has_value() && !frame.filename.empty() && options.snippets) {
auto snippet = detail::get_snippet(
frame.filename,
Expand All @@ -270,29 +279,45 @@ CPPTRACE_BEGIN_NAMESPACE
}
}

void write_frame(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
write_address(stream, frame, color);
/// Write the frame number, and return the number of characters written
size_t write_frame_number(std::ostream& stream, unsigned int frame_number_width, size_t counter) const
{
microfmt::print(stream, "#{<{}} ", frame_number_width, counter);
return 2 + frame_number_width;
}

void write_frame(std::ostream& stream, const stacktrace_frame& frame, color_setting color, size_t col) const {
col += write_address(stream, frame, color);
if(frame.is_inline || options.addresses != address_mode::none) {
stream << ' ';
col += 1;
}
if(!frame.symbol.empty()) {
write_symbol(stream, frame, color);
}
if(!frame.symbol.empty() && !frame.filename.empty()) {
stream << ' ';
if(options.break_before_filename) {
microfmt::print(stream, "\n{<{}}", col, "");
} else {
stream << ' ';
}
}
if(!frame.filename.empty()) {
write_source_location(stream, frame, color);
}
}

void write_address(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
/// Write the address of the frame, return the number of characters written
size_t write_address(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
if(frame.is_inline) {
microfmt::print(stream, "{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
return 2 * sizeof(frame_ptr) + 2;
} else if(options.addresses != address_mode::none) {
auto address = options.addresses == address_mode::raw ? frame.raw_address : frame.object_address;
microfmt::print(stream, "{}0x{>{}:0h}{}", color.blue(), 2 * sizeof(frame_ptr), address, color.reset());
return 2 * sizeof(frame_ptr) + 2;
}
return 0;
}

void write_symbol(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
Expand Down Expand Up @@ -399,13 +424,20 @@ CPPTRACE_BEGIN_NAMESPACE
pimpl->transform(std::move(transform));
return *this;
}
formatter& formatter::break_before_filename(bool do_break) {
pimpl->break_before_filename(do_break);
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_frame& frame, bool color, size_t filename_indent) const {
return pimpl->format(frame, color, filename_indent);
}

std::string formatter::format(const stacktrace& trace) const {
return pimpl->format(trace);
Expand Down Expand Up @@ -445,12 +477,18 @@ CPPTRACE_BEGIN_NAMESPACE
void formatter::print(std::ostream& stream, const stacktrace_frame& frame, bool color) const {
pimpl->print(stream, frame, color);
}
void formatter::print(std::ostream& stream, const stacktrace_frame& frame, bool color, size_t filename_indent) const {
pimpl->print(stream, frame, color, filename_indent);
}
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);
}
void formatter::print(std::FILE* file, const stacktrace_frame& frame, bool color, size_t filename_indent) const {
pimpl->print(file, frame, color, filename_indent);
}

const formatter& get_default_formatter() {
static formatter formatter;
Expand Down
130 changes: 126 additions & 4 deletions test/unit/lib/formatting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@ namespace {

#if UINTPTR_MAX > 0xffffffff
#define ADDR_PREFIX "00000000"
#define PADDING_TAG " "
#define INLINED_TAG "(inlined) "
#else
#define PADDING_TAG ""
#define ADDR_PREFIX ""
#define INLINED_TAG "(inlined) "
#endif

cpptrace::stacktrace make_test_stacktrace() {
cpptrace::stacktrace make_test_stacktrace(size_t count = 1) {
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});
for(size_t i = 0; i < count; i++) {
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;
}

Expand Down Expand Up @@ -125,6 +129,124 @@ TEST(FormatterTest, NoAddresses) {
);
}

TEST(FormatterTest, BreakBeforeFilename) {
auto formatter = cpptrace::formatter{}
.break_before_filename(true);
EXPECT_THAT(
split(formatter.format(make_test_stacktrace()), "\n"),
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x" ADDR_PREFIX "00000001 in foo()",
" " PADDING_TAG " at foo.cpp:20:30",
"#1 0x" ADDR_PREFIX "00000002 in bar()",
" " PADDING_TAG " at bar.cpp:30:40",
"#2 0x" ADDR_PREFIX "00000003 in main",
" " PADDING_TAG " at foo.cpp:40:25"
)
);
}

TEST(FormatterTest, BreakBeforeFilenameNoAddresses) {
auto formatter = cpptrace::formatter{}
.break_before_filename(true)
.addresses(cpptrace::formatter::address_mode::none);
// Check that if no address is present, the filename indent is reduced
EXPECT_THAT(
split(formatter.format(make_test_stacktrace()), "\n"),
ElementsAre(
"Stack trace (most recent call first):",
"#0 in foo()",
" at foo.cpp:20:30",
"#1 in bar()",
" at bar.cpp:30:40",
"#2 in main",
" at foo.cpp:40:25"
)
);
}

TEST(FormatterTest, BreakBeforeFilenameInlines) {
auto formatter = cpptrace::formatter{}
.break_before_filename(true);

// Check that indentation is computed correctly when elements are inlined
auto trace = make_test_stacktrace();
trace.frames[1].is_inline = true;
EXPECT_THAT(
split(formatter.format(trace), "\n"),
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x" ADDR_PREFIX "00000001 in foo()",
" " PADDING_TAG " at foo.cpp:20:30",
"#1 " INLINED_TAG " in bar()",
" " PADDING_TAG " at bar.cpp:30:40",
"#2 0x" ADDR_PREFIX "00000003 in main",
" " PADDING_TAG " at foo.cpp:40:25"
)
);
}

TEST(FormatterTest, BreakBeforeFilenameLongTrace) {
// Check that indentation is computed correctly for longer traces (where the
// frame number is padded)
auto formatter = cpptrace::formatter{}
.break_before_filename(true);

EXPECT_THAT(
split(formatter.format(make_test_stacktrace(4)), "\n"),
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x" ADDR_PREFIX "00000001 in foo()",
" " PADDING_TAG " at foo.cpp:20:30",
"#1 0x" ADDR_PREFIX "00000002 in bar()",
" " PADDING_TAG " at bar.cpp:30:40",
"#2 0x" ADDR_PREFIX "00000003 in main",
" " PADDING_TAG " at foo.cpp:40:25",
"#3 0x" ADDR_PREFIX "00000001 in foo()",
" " PADDING_TAG " at foo.cpp:20:30",
"#4 0x" ADDR_PREFIX "00000002 in bar()",
" " PADDING_TAG " at bar.cpp:30:40",
"#5 0x" ADDR_PREFIX "00000003 in main",
" " PADDING_TAG " at foo.cpp:40:25",
"#6 0x" ADDR_PREFIX "00000001 in foo()",
" " PADDING_TAG " at foo.cpp:20:30",
"#7 0x" ADDR_PREFIX "00000002 in bar()",
" " PADDING_TAG " at bar.cpp:30:40",
"#8 0x" ADDR_PREFIX "00000003 in main",
" " PADDING_TAG " at foo.cpp:40:25",
"#9 0x" ADDR_PREFIX "00000001 in foo()",
" " PADDING_TAG " at foo.cpp:20:30",
"#10 0x" ADDR_PREFIX "00000002 in bar()",
" " PADDING_TAG " at bar.cpp:30:40",
"#11 0x" ADDR_PREFIX "00000003 in main",
" " PADDING_TAG " at foo.cpp:40:25"
)
);
}

TEST(FormatterTest, BreakBeforeFilenameColors) {
// Check that indentation is computed correctly with colors enabled
// (If microfmt is updated to count the number of characters printed,
// it will need to _exclude_ colors for the purposes of computing
// alignment)
auto formatter = cpptrace::formatter{}
.break_before_filename(true)
.colors(cpptrace::formatter::color_mode::always);

EXPECT_THAT(
split(formatter.format(make_test_stacktrace()), "\n"),
ElementsAre(
"Stack trace (most recent call first):",
"#0 \x1B[34m0x" ADDR_PREFIX "00000001\x1B[0m in \x1B[33mfoo()\x1B[0m",
" " PADDING_TAG " at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m20\x1B[0m:\x1B[34m30\x1B[0m",
"#1 \x1B[34m0x" ADDR_PREFIX "00000002\x1B[0m in \x1B[33mbar()\x1B[0m",
" " PADDING_TAG " at \x1B[32mbar.cpp\x1B[0m:\x1B[34m30\x1B[0m:\x1B[34m40\x1B[0m",
"#2 \x1B[34m0x" ADDR_PREFIX "00000003\x1B[0m in \x1B[33mmain\x1B[0m",
" " PADDING_TAG " at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m40\x1B[0m:\x1B[34m25\x1B[0m"
)
);
}

TEST(FormatterTest, PathShortening) {
cpptrace::stacktrace trace;
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "/home/foo/foo.cpp", "foo()", false});
Expand Down
4 changes: 2 additions & 2 deletions tools/symbol_tables/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ void lookup_symbol(const options& options, cpptrace::frame_ptr address) {
}
}
#elif IS_APPLE
void dump_symbols(const std::filesystem::path&) {
void dump_symbols(const options&) {
fmt::println("Not implemented yet (TODO)");
}
void lookup_symbol(const std::filesystem::path&, cpptrace::frame_ptr) {
void lookup_symbol(const options&, cpptrace::frame_ptr) {
fmt::println("Not implemented yet (TODO)");
}
#else
Expand Down
Loading