diff --git a/include/cpptrace/formatting.hpp b/include/cpptrace/formatting.hpp index 060c0eb3..eb006b2a 100644 --- a/include/cpptrace/formatting.hpp +++ b/include/cpptrace/formatting.hpp @@ -56,9 +56,12 @@ CPPTRACE_BEGIN_NAMESPACE formatter& filtered_frame_placeholders(bool); formatter& filter(std::function); formatter& transform(std::function); + 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; @@ -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; diff --git a/src/formatting.cpp b/src/formatting.cpp index 02abad3e..631e8587 100644 --- a/src/formatting.cpp +++ b/src/formatting.cpp @@ -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; @@ -106,13 +107,17 @@ CPPTRACE_BEGIN_NAMESPACE void transform(std::function 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 color_override = detail::nullopt + detail::optional 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(); } @@ -128,17 +133,19 @@ CPPTRACE_BEGIN_NAMESPACE void print( std::ostream& stream, const stacktrace_frame& frame, - detail::optional color_override = detail::nullopt + detail::optional 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 color_override = detail::nullopt + detail::optional 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); } @@ -206,7 +213,7 @@ CPPTRACE_BEGIN_NAMESPACE return do_color; } - void print_internal(std::ostream& stream, const stacktrace_frame& input_frame, detail::optional color_override) const { + void print_internal(std::ostream& stream, const stacktrace_frame& input_frame, detail::optional color_override, size_t col_indent) const { bool color = should_do_color(stream, color_override); maybe_ensure_virtual_terminal_processing(stream, color); detail::optional transformed_frame; @@ -214,7 +221,7 @@ CPPTRACE_BEGIN_NAMESPACE 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 color_override) const { @@ -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'; @@ -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, @@ -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 { @@ -399,6 +424,10 @@ 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); @@ -406,6 +435,9 @@ CPPTRACE_BEGIN_NAMESPACE 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); @@ -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; diff --git a/test/unit/lib/formatting.cpp b/test/unit/lib/formatting.cpp index ac02ba70..3e67b1c0 100644 --- a/test/unit/lib/formatting.cpp +++ b/test/unit/lib/formatting.cpp @@ -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; } @@ -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}); diff --git a/tools/symbol_tables/main.cpp b/tools/symbol_tables/main.cpp index 7126f06c..d8e641dd 100644 --- a/tools/symbol_tables/main.cpp +++ b/tools/symbol_tables/main.cpp @@ -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