Skip to content

Commit

Permalink
Expand cpptrace API (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy-rifkin authored Sep 19, 2023
1 parent 43a50c7 commit 0b32df6
Show file tree
Hide file tree
Showing 28 changed files with 513 additions and 126 deletions.
2 changes: 1 addition & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
Checks: '-*,clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-cppcoreguidelines-macro-usage,-modernize-use-trailing-return-type,-misc-non-private-member-variables-in-classes,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-c-arrays,-modernize-avoid-c-arrays,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-readability-else-after-return,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cert-dcl50-cpp,-cppcoreguidelines-init-variables,-readability-implicit-bool-conversion,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-type-member-init,-readability-isolate-declaration,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-cstyle-cast,-readability-named-parameter,-cppcoreguidelines-avoid-goto,-readability-uppercase-literal-suffix,-performance-avoid-endl,-bugprone-easily-swappable-parameters'
Checks: '-*,clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-cppcoreguidelines-macro-usage,-modernize-use-trailing-return-type,-misc-non-private-member-variables-in-classes,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-c-arrays,-modernize-avoid-c-arrays,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-readability-else-after-return,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cert-dcl50-cpp,-cppcoreguidelines-init-variables,-readability-implicit-bool-conversion,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-type-member-init,-readability-isolate-declaration,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-cstyle-cast,-readability-named-parameter,-cppcoreguidelines-avoid-goto,-readability-uppercase-literal-suffix,-performance-avoid-endl,-bugprone-easily-swappable-parameters,-cppcoreguidelines-non-private-member-variables-in-classes'
WarningsAsErrors: '*'
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
Expand Down
120 changes: 114 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,40 @@ Generating traces is as easy as calling `cpptrace::print_trace`:
#include <cpptrace/cpptrace.hpp>

void trace() {
cpptrace::print_trace();
cpptrace::generate_trace().print();
}

// ...
/* other stuff */
```

![Screenshot](res/screenshot.png)

Cpptrace provides access to resolved stack traces as well as lightweight raw traces (just addresses) that can be
resolved later:

```cpp
const auto raw_trace = cpptrace::generate_raw_trace();
// then later
raw_trace.resolve().print();
```

Cpptrace also provides exception types that store stack traces:
```cpp
#include <cpptrace/cpptrace.hpp>

void trace() {
throw cpptrace::exception();
}

/* other stuff */
// terminate called after throwing an instance of 'cpptrace::exception'
// what(): cpptrace::exception:
// Stack trace (most recent call first):
// #0 0x00005641c715a1b6 in trace() at demo.cpp:9
// #1 0x00005641c715a229 in foo(int) at demo.cpp:16
// #2 0x00005641c715a2ba in main at demo.cpp:34
```

## CMake FetchContent Usage

```cmake
Expand All @@ -73,8 +99,9 @@ On windows and macos some extra work is required, see [below](#platform-logistic

## API

`cpptrace::print_trace()` can be used to print a stacktrace at the current call site, `cpptrace::generate_trace()` can
be used to get raw frame information for custom use.
`cpptrace::generate_trace()` can used to generate a stacktrace object at the current call site. Resolved frames can be
accessed from this object with `.frames` and also the trace can be printed with `.print()`. Cpptrace also provides a
method to get lightweight raw traces, which are just vectors of program counters, which can be resolved at a later time.

**Note:** Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`) is generally required for good trace information.

Expand All @@ -83,15 +110,96 @@ for generating these is included above.

```cpp
namespace cpptrace {
/*
* Raw trace access
*/
struct raw_trace {
std::vector<uintptr_t> frames;
explicit raw_trace(std::vector<uintptr_t>&& frames);
object_trace resolve_object_trace() const;
stacktrace resolve() const;
void clear();
/* iterators exist for this object */
};

/*
* Object trace with object file information for each frame, any accessible symbol information, and the address in
* the object file for the frame's program counter.
*/
struct object_frame {
std::string obj_path;
std::string symbol;
uintptr_t raw_address = 0;
uintptr_t obj_address = 0;
};

struct object_trace {
std::vector<object_frame> frames;
explicit object_trace(std::vector<object_frame>&& frames);
stacktrace resolve() const;
void clear();
/* iterators exist for this object */
};

/*
* Resolved stacktrace object.
*/
struct stacktrace_frame {
uintptr_t address;
std::uint_least32_t line;
std::uint_least32_t col;
std::string filename;
std::string symbol;
bool operator==(const stacktrace_frame& other) const;
bool operator!=(const stacktrace_frame& other) const;
};
std::vector<stacktrace_frame> generate_trace(std::uint32_t skip = 0);
void print_trace(std::uint32_t skip = 0);

struct stacktrace {
std::vector<stacktrace_frame> frames;
explicit stacktrace(std::vector<stacktrace_frame>&& frames);
void print() const;
void print(std::ostream& stream) const;
void print(std::ostream& stream, bool color) const;
std::string to_string() const;
void clear();
/* iterators exist for this object */
};

/*
* Trace generation
*/
raw_trace generate_raw_trace(std::uint32_t skip = 0);
object_trace generate_object_trace(std::uint32_t skip = 0);
stacktrace generate_trace(std::uint32_t skip = 0);

/*
* Utilities
*/
std::string demangle(const std::string& name);

// Traced exception class
class exception : public std::exception {
public:
explicit exception();
const char* what() const noexcept override;
};

class exception_with_message : public exception {
public:
explicit exception_with_message(std::string&& message_arg);
const char* what() const noexcept override;
};

// All stdexcept errors have analogs here. Same constructor as exception_with_message.
class logic_error : exception_with_message { ... };
class domain_error : exception_with_message { ... };
class invalid_argument : exception_with_message { ... };
class length_error : exception_with_message { ... };
class out_of_range : exception_with_message { ... };
class runtime_error : exception_with_message { ... };
class range_error : exception_with_message { ... };
class overflow_error : exception_with_message { ... };
class underflow_error : exception_with_message { ... };
}
```
Expand Down
162 changes: 160 additions & 2 deletions include/cpptrace/cpptrace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#define CPPTRACE_HPP

#include <cstdint>
#include <exception>
#include <ostream>
#include <string>
#include <vector>

Expand All @@ -12,15 +14,171 @@
#endif

namespace cpptrace {
struct object_trace;
struct stacktrace;

struct raw_trace {
std::vector<uintptr_t> frames;
explicit raw_trace(std::vector<uintptr_t>&& frames_) : frames(frames_) {}
CPPTRACE_API object_trace resolve_object_trace() const;
CPPTRACE_API stacktrace resolve() const;
CPPTRACE_API void clear();

using iterator = std::vector<uintptr_t>::iterator;
using const_iterator = std::vector<uintptr_t>::const_iterator;
inline iterator begin() noexcept { return frames.begin(); }
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline iterator end() noexcept { return frames.end(); }
inline const_iterator cend() const noexcept { return frames.cend(); }
};

struct object_frame {
std::string obj_path;
std::string symbol;
uintptr_t raw_address = 0;
uintptr_t obj_address = 0;
};

struct object_trace {
std::vector<object_frame> frames;
explicit object_trace(std::vector<object_frame>&& frames_) : frames(frames_) {}
CPPTRACE_API stacktrace resolve() const;
CPPTRACE_API void clear();

using iterator = std::vector<object_frame>::iterator;
using const_iterator = std::vector<object_frame>::const_iterator;
inline iterator begin() noexcept { return frames.begin(); }
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline iterator end() noexcept { return frames.end(); }
inline const_iterator cend() const noexcept { return frames.cend(); }
};

struct stacktrace_frame {
uintptr_t address;
std::uint_least32_t line;
std::uint_least32_t col;
std::string filename;
std::string symbol;
bool operator==(const stacktrace_frame& other) const {
return address == other.address
&& line == other.line
&& col == other.col
&& filename == other.filename
&& symbol == other.symbol;
}
bool operator!=(const stacktrace_frame& other) const {
return !operator==(other);
}
};

struct stacktrace {
std::vector<stacktrace_frame> frames;
explicit stacktrace(std::vector<stacktrace_frame>&& frames_) : frames(frames_) {}
CPPTRACE_API void print() const;
CPPTRACE_API void print(std::ostream& stream) const;
CPPTRACE_API void print(std::ostream& stream, bool color) const;
CPPTRACE_API std::string to_string() const;
CPPTRACE_API void clear();

using iterator = std::vector<stacktrace_frame>::iterator;
using const_iterator = std::vector<stacktrace_frame>::const_iterator;
inline iterator begin() noexcept { return frames.begin(); }
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline iterator end() noexcept { return frames.end(); }
inline const_iterator cend() const noexcept { return frames.cend(); }
};

CPPTRACE_API raw_trace generate_raw_trace(std::uint32_t skip = 0);
CPPTRACE_API object_trace generate_object_trace(std::uint32_t skip = 0);
CPPTRACE_API stacktrace generate_trace(std::uint32_t skip = 0);

// utilities:
CPPTRACE_API std::string demangle(const std::string& name);

class exception : public std::exception {
protected:
mutable raw_trace trace;
mutable std::string resolved_message;
explicit exception(uint32_t skip) : trace(generate_raw_trace(skip + 1)) {}
virtual const std::string& get_resolved_message() const {
if(resolved_message.empty()) {
resolved_message = "cpptrace::exception:\n" + trace.resolve().to_string();
trace.clear();
}
return resolved_message;
}
public:
explicit exception() : exception(1) {}
const char* what() const noexcept override {
return get_resolved_message().c_str();
}
};

class exception_with_message : public exception {
protected:
mutable std::string message;
explicit exception_with_message(std::string&& message_arg, uint32_t skip)
: exception(skip + 1), message(std::move(message_arg)) {}
const std::string& get_resolved_message() const override {
if(resolved_message.empty()) {
resolved_message = message + "\n" + trace.resolve().to_string();
trace.clear();
message.clear();
}
return resolved_message;
}
public:
explicit exception_with_message(std::string&& message_arg)
: exception_with_message(std::move(message_arg), 1) {}
const char* what() const noexcept override {
return get_resolved_message().c_str();
}
};

class logic_error : public exception_with_message {
public:
explicit logic_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};

class domain_error : public exception_with_message {
public:
explicit domain_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};

class invalid_argument : public exception_with_message {
public:
explicit invalid_argument(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};

class length_error : public exception_with_message {
public:
explicit length_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};

class out_of_range : public exception_with_message {
public:
explicit out_of_range(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};

class runtime_error : public exception_with_message {
public:
explicit runtime_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};

class range_error : public exception_with_message {
public:
explicit range_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};

class overflow_error : public exception_with_message {
public:
explicit overflow_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};

class underflow_error : public exception_with_message {
public:
explicit underflow_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
CPPTRACE_API std::vector<stacktrace_frame> generate_trace(std::uint32_t skip = 0);
CPPTRACE_API void print_trace(std::uint32_t skip = 0);
}

#endif
Loading

0 comments on commit 0b32df6

Please sign in to comment.