Skip to content

Commit 38a0803

Browse files
committed
Merge branch 'dev' into jr/abi
2 parents 0d1041e + f9489a6 commit 38a0803

File tree

9 files changed

+194
-7
lines changed

9 files changed

+194
-7
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ target_sources(
150150
src/utils/io/memory_file_view.cpp
151151
src/utils/error.cpp
152152
src/utils/microfmt.cpp
153+
src/utils/replace_all.cpp
153154
src/utils/string_view.cpp
154155
src/utils/utils.cpp
155156
src/platform/dbghelp_utils.cpp

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
2525
- [Utilities](#utilities)
2626
- [Formatting](#formatting)
2727
- [Transforms](#transforms)
28+
- [Formatting Utilities](#formatting-utilities)
2829
- [Configuration](#configuration)
2930
- [Logging](#logging)
3031
- [Traces From All Exceptions](#traces-from-all-exceptions)
@@ -350,6 +351,7 @@ namespace cpptrace {
350351
formatter& snippets(bool);
351352
formatter& snippet_context(int);
352353
formatter& columns(bool);
354+
formatter& prettify_symbols(bool);
353355
formatter& filtered_frame_placeholders(bool);
354356
formatter& filter(std::function<bool(const stacktrace_frame&)>);
355357
formatter& transform(std::function<stacktrace_frame(stacktrace_frame)>);
@@ -387,6 +389,7 @@ Options:
387389
| `snippets` | Whether to include source code snippets | `false` |
388390
| `snippet_context` | How many lines of source context to show in a snippet | `2` |
389391
| `columns` | Whether to include column numbers if present | `true` |
392+
| `prettify_symbols` | Whether to attempt to clean up long symbol names | `false` |
390393
| `filtered_frame_placeholders` | Whether to still print filtered frames as just `#n (filtered)` | `true` |
391394
| `filter` | A predicate to filter frames with | None |
392395
| `transform` | A transformer which takes a stacktrace frame and modifies it | None |
@@ -396,6 +399,10 @@ colors for the `formatter::format` method and it may not be able to detect if so
396399
not. For this reason, `formatter::format` and `formatter::print` methods have overloads taking a color parameter. This
397400
color parameter will override configured color mode.
398401
402+
The `prettify_symbols` option applies a number of simple rewrite rules to symbols in an attempt to clean them up, e.g.
403+
it rewrites `foo(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >)`
404+
as `foo(std::vector<std::string>)`.
405+
399406
Recommended practice with formatters: It's generally preferable to create formatters objects that are long-lived rather
400407
than to create them on the fly every time a trace needs to be formatted.
401408
@@ -419,6 +426,18 @@ auto formatter = cpptrace::formatter{}
419426
});
420427
```
421428
429+
### Formatting Utilities
430+
431+
Cpptrace exports a couple formatting utilities used internally which might be useful for custom formatters that don't
432+
use `cpptrace::formatter`:
433+
434+
```cpp
435+
namespace cpptrace {
436+
std::string basename(const std::string& path);
437+
std::string prettify_symbol(std::string symbol);
438+
}
439+
```
440+
422441
## Configuration
423442

424443
`cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues.

include/cpptrace/basic.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ CPPTRACE_BEGIN_NAMESPACE
166166

167167
std::string to_string() const;
168168
std::string to_string(bool color) const;
169-
friend std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame);
169+
friend CPPTRACE_EXPORT std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame);
170170
};
171171

172172
struct CPPTRACE_EXPORT stacktrace {
@@ -182,7 +182,7 @@ CPPTRACE_BEGIN_NAMESPACE
182182
void clear();
183183
bool empty() const noexcept;
184184
std::string to_string(bool color = false) const;
185-
friend std::ostream& operator<<(std::ostream& stream, const stacktrace& trace);
185+
friend CPPTRACE_EXPORT std::ostream& operator<<(std::ostream& stream, const stacktrace& trace);
186186

187187
using iterator = std::vector<stacktrace_frame>::iterator;
188188
using const_iterator = std::vector<stacktrace_frame>::const_iterator;

include/cpptrace/formatting.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
#include <functional>
88

99
CPPTRACE_BEGIN_NAMESPACE
10+
CPPTRACE_EXPORT std::string basename(const std::string& path);
11+
CPPTRACE_EXPORT std::string prettify_symbol(std::string symbol);
12+
1013
class CPPTRACE_EXPORT formatter {
1114
class impl;
1215
// can't be a std::unique_ptr due to msvc awfulness with dllimport/dllexport and https://stackoverflow.com/q/4145605/15675011
@@ -44,6 +47,7 @@ CPPTRACE_BEGIN_NAMESPACE
4447
formatter& snippets(bool);
4548
formatter& snippet_context(int);
4649
formatter& columns(bool);
50+
formatter& prettify_symbols(bool);
4751
formatter& filtered_frame_placeholders(bool);
4852
formatter& filter(std::function<bool(const stacktrace_frame&)>);
4953
formatter& transform(std::function<stacktrace_frame(stacktrace_frame)>);

src/formatting.cpp

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,60 @@
33

44
#include "utils/optional.hpp"
55
#include "utils/utils.hpp"
6+
#include "utils/replace_all.hpp"
67
#include "snippets/snippet.hpp"
78

8-
#include <memory>
99
#include <cstdio>
1010
#include <string>
1111
#include <functional>
1212
#include <iostream>
1313
#include <sstream>
14+
#include <regex>
1415

1516
CPPTRACE_BEGIN_NAMESPACE
17+
std::string basename(const std::string& path) {
18+
return internal::basename(path, true);
19+
}
20+
21+
std::string prettify_symbol(std::string symbol) {
22+
// > > -> >> replacement
23+
// could put in analysis:: but the replacement is basic and this is more convenient for
24+
// using in the stringifier too
25+
internal::replace_all_dynamic(symbol, "> >", ">>");
26+
// "," -> ", " and " ," -> ", "
27+
static const std::regex comma_re(R"(\s*,\s*)");
28+
internal::replace_all(symbol, comma_re, ", ");
29+
// class C -> C for msvc
30+
static const std::regex class_re(R"(\b(class|struct)\s+)");
31+
internal::replace_all(symbol, class_re, "");
32+
// `anonymous namespace' -> (anonymous namespace) for msvc
33+
// this brings it in-line with other compilers and prevents any tokenization/highlighting issues
34+
static const std::regex msvc_anonymous_namespace("`anonymous namespace'");
35+
internal::replace_all(symbol, msvc_anonymous_namespace, "(anonymous namespace)");
36+
// rules to replace std::basic_string -> std::string and std::basic_string_view -> std::string
37+
// rule to replace ", std::allocator<whatever>"
38+
static const std::pair<std::regex, std::string> basic_string = {
39+
std::regex(R"(std(::[a-zA-Z0-9_]+)?::basic_string<char)"), "std::string"
40+
};
41+
internal::replace_all_template(symbol, basic_string);
42+
static const std::pair<std::regex, std::string> basic_string_view = {
43+
std::regex(R"(std(::[a-zA-Z0-9_]+)?::basic_string_view<char)"), "std::string_view"
44+
};
45+
internal::replace_all_template(symbol, basic_string_view);
46+
static const std::pair<std::regex, std::string> allocator = {
47+
std::regex(R"(,\s*std(::[a-zA-Z0-9_]+)?::allocator<)"), ""
48+
};
49+
internal::replace_all_template(symbol, allocator);
50+
static const std::pair<std::regex, std::string> default_delete = {
51+
std::regex(R"(,\s*std(::[a-zA-Z0-9_]+)?::default_delete<)"), ""
52+
};
53+
internal::replace_all_template(symbol, default_delete);
54+
// replace std::__cxx11 -> std:: for gcc dual abi
55+
// https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html
56+
internal::replace_all_dynamic(symbol, "std::__cxx11::", "std::");
57+
return symbol;
58+
}
59+
1660
class formatter::impl {
1761
struct {
1862
std::string header = "Stack trace (most recent call first):";
@@ -22,6 +66,7 @@ CPPTRACE_BEGIN_NAMESPACE
2266
bool snippets = false;
2367
int context_lines = 2;
2468
bool columns = true;
69+
bool prettify_symbols = false;
2570
bool show_filtered_frames = true;
2671
std::function<bool(const stacktrace_frame&)> filter;
2772
std::function<stacktrace_frame(stacktrace_frame)> transform;
@@ -49,6 +94,9 @@ CPPTRACE_BEGIN_NAMESPACE
4994
void columns(bool columns) {
5095
options.columns = columns;
5196
}
97+
void prettify_symbols(bool prettify) {
98+
options.prettify_symbols = prettify;
99+
}
52100
void filtered_frame_placeholders(bool show) {
53101
options.show_filtered_frames = show;
54102
}
@@ -231,7 +279,10 @@ CPPTRACE_BEGIN_NAMESPACE
231279
microfmt::print(stream, "{}0x{>{}:0h}{} ", blue, 2 * sizeof(frame_ptr), address, reset);
232280
}
233281
if(!frame.symbol.empty()) {
234-
microfmt::print(stream, "in {}{}{}", yellow, frame.symbol, reset);
282+
microfmt::print(
283+
stream, "in {}{}{}",
284+
yellow, options.prettify_symbols ? prettify_symbol(frame.symbol) : frame.symbol, reset
285+
);
235286
}
236287
if(!frame.filename.empty()) {
237288
microfmt::print(
@@ -302,6 +353,10 @@ CPPTRACE_BEGIN_NAMESPACE
302353
pimpl->columns(columns);
303354
return *this;
304355
}
356+
formatter& formatter::prettify_symbols(bool prettify) {
357+
pimpl->prettify_symbols(prettify);
358+
return *this;
359+
}
305360
formatter& formatter::filtered_frame_placeholders(bool show) {
306361
pimpl->filtered_frame_placeholders(show);
307362
return *this;

src/utils/replace_all.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include "utils/replace_all.hpp"
2+
3+
namespace cpptrace {
4+
namespace internal {
5+
void replace_all(std::string& str, string_view substr, string_view replacement) {
6+
std::string::size_type pos = 0;
7+
while((pos = str.find(substr.data(), pos, substr.size())) != std::string::npos) {
8+
str.replace(pos, substr.size(), replacement.data(), replacement.size());
9+
pos += replacement.size();
10+
}
11+
}
12+
13+
void replace_all(std::string& str, const std::regex& re, string_view replacement) {
14+
std::smatch match;
15+
std::size_t i = 0;
16+
while(std::regex_search(str.cbegin() + i, str.cend(), match, re)) {
17+
str.replace(i + match.position(), match.length(), replacement.data(), replacement.size());
18+
i += match.position() + replacement.size();
19+
}
20+
}
21+
22+
void replace_all_dynamic(std::string& str, string_view substr, string_view replacement) {
23+
std::string::size_type pos = 0;
24+
while((pos = str.find(substr.data(), pos, substr.size())) != std::string::npos) {
25+
str.replace(pos, substr.size(), replacement.data(), replacement.size());
26+
// advancing by one rather than replacement.length() in case replacement leads to
27+
// another replacement opportunity, e.g. folding > > > to >> > then >>>
28+
pos++;
29+
}
30+
}
31+
32+
void replace_all_template(std::string& str, const std::pair<std::regex, string_view>& rule) {
33+
const auto& re = rule.first;
34+
const auto& replacement = rule.second;
35+
std::smatch match;
36+
std::size_t cursor = 0;
37+
while(std::regex_search(str.cbegin() + cursor, str.cend(), match, re)) {
38+
// find matching >
39+
const std::size_t match_begin = cursor + match.position();
40+
std::size_t end = match_begin + match.length();
41+
for(int c = 1; end < str.size() && c > 0; end++) {
42+
if(str[end] == '<') {
43+
c++;
44+
} else if(str[end] == '>') {
45+
c--;
46+
}
47+
}
48+
// make the replacement
49+
str.replace(match_begin, end - match_begin, replacement.data(), replacement.size());
50+
cursor = match_begin + replacement.size();
51+
}
52+
}
53+
}
54+
}

src/utils/replace_all.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef REPLACE_ALL
2+
#define REPLACE_ALL
3+
4+
#include <string>
5+
#include <regex>
6+
7+
#include "utils/string_view.hpp"
8+
9+
namespace cpptrace {
10+
namespace internal {
11+
// replace all instances of substr with the replacement
12+
void replace_all(std::string& str, string_view substr, string_view replacement);
13+
14+
// replace all regex matches with the replacement
15+
void replace_all(std::string& str, const std::regex& re, string_view replacement);
16+
17+
// replace all instances of substr with the replacement, including new instances introduced by the replacement
18+
void replace_all_dynamic(std::string& str, string_view substr, string_view replacement);
19+
20+
// replace all matches of a regex including template parameters
21+
void replace_all_template(std::string& str, const std::pair<std::regex, string_view>& rule);
22+
}
23+
}
24+
25+
#endif

test/link_test.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
#include "cpptrace/basic.hpp"
2-
#include "cpptrace/utils.hpp"
3-
#include <cpptrace/cpptrace.hpp>
1+
#include <cpptrace/basic.hpp>
2+
#include <cpptrace/utils.hpp>
3+
#include <cpptrace/formatting.hpp>
4+
#include <cpptrace/exceptions.hpp>
45

56
#include <iostream>
67

@@ -79,4 +80,7 @@ int main() {
7980

8081
cpptrace::experimental::set_dwarf_resolver_line_table_cache_size(100);
8182
cpptrace::experimental::set_dwarf_resolver_disable_aranges(true);
83+
84+
cpptrace::basename("");
85+
cpptrace::prettify_symbol("");
8286
}

test/unit/lib/formatting.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,4 +369,29 @@ TEST(FormatterTest, CopySemantics) {
369369
);
370370
}
371371

372+
TEST(FormatterTest, PrettySymbols) {
373+
auto normal_formatter = cpptrace::formatter{}
374+
.prettify_symbols(false);
375+
cpptrace::stacktrace trace;
376+
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "foo.cpp", "foo(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)", false});
377+
auto res = split(normal_formatter.format(trace), "\n");
378+
EXPECT_THAT(
379+
res,
380+
ElementsAre(
381+
"Stack trace (most recent call first):",
382+
"#0 0x0000000000000001 in foo(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) at foo.cpp:20:30"
383+
)
384+
);
385+
auto pretty_formatter = cpptrace::formatter{}
386+
.prettify_symbols(true);
387+
res = split(pretty_formatter.format(trace), "\n");
388+
EXPECT_THAT(
389+
res,
390+
ElementsAre(
391+
"Stack trace (most recent call first):",
392+
"#0 0x0000000000000001 in foo(std::string) at foo.cpp:20:30"
393+
)
394+
);
395+
}
396+
372397
}

0 commit comments

Comments
 (0)