cpptrace/src/cpptrace.cpp

519 lines
16 KiB
C++

#include <cpptrace/cpptrace.hpp>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "symbols/symbols.hpp"
#include "unwind/unwind.hpp"
#include "demangle/demangle.hpp"
#include "platform/common.hpp"
#include "platform/exception_type.hpp"
#include "platform/object.hpp"
#include "platform/utils.hpp"
#define ESC "\033["
#define RESET ESC "0m"
#define RED ESC "31m"
#define GREEN ESC "32m"
#define YELLOW ESC "33m"
#define BLUE ESC "34m"
#define MAGENTA ESC "35m"
#define CYAN ESC "36m"
namespace cpptrace {
CPPTRACE_FORCE_NO_INLINE
raw_trace raw_trace::from_buffer(frame_ptr* buffer, std::size_t size) {
return raw_trace{std::vector<cpptrace::frame_ptr>(buffer, buffer + size)};
}
CPPTRACE_FORCE_NO_INLINE
raw_trace raw_trace::current(std::size_t skip) {
return generate_raw_trace(skip + 1);
}
CPPTRACE_FORCE_NO_INLINE
raw_trace raw_trace::current(std::size_t skip, std::size_t max_depth) {
return generate_raw_trace(skip + 1, max_depth);
}
object_trace raw_trace::resolve_object_trace() const {
try {
return object_trace{detail::get_frames_object_info(frames)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
stacktrace raw_trace::resolve() const {
try {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
}
return stacktrace{std::move(trace)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace{};
}
}
void raw_trace::clear() {
frames.clear();
}
bool raw_trace::empty() const noexcept {
return frames.empty();
}
CPPTRACE_FORCE_NO_INLINE
object_trace object_trace::current(std::size_t skip) {
return generate_object_trace(skip + 1);
}
CPPTRACE_FORCE_NO_INLINE
object_trace object_trace::current(std::size_t skip, std::size_t max_depth) {
return generate_object_trace(skip + 1, max_depth);
}
stacktrace object_trace::resolve() const {
try {
return stacktrace{detail::resolve_frames(frames)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace();
}
}
void object_trace::clear() {
frames.clear();
}
bool object_trace::empty() const noexcept {
return frames.empty();
}
std::string stacktrace_frame::to_string() const {
std::ostringstream oss;
oss << *this;
return std::move(oss).str();
}
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
stream
<< std::hex
<< "0x"
<< std::setw(2 * sizeof(frame_ptr))
<< std::setfill('0')
<< frame.address
<< std::dec
<< std::setfill(' ');
if(!frame.symbol.empty()) {
stream
<< " in "
<< frame.symbol;
}
if(!frame.filename.empty()) {
stream
<< " at "
<< frame.filename;
if(frame.line.has_value()) {
stream
<< ":"
<< frame.line.value();
if(frame.column.has_value()) {
stream << frame.column.value();
}
}
}
return stream;
}
CPPTRACE_FORCE_NO_INLINE
stacktrace stacktrace::current(std::size_t skip) {
return generate_trace(skip + 1);
}
CPPTRACE_FORCE_NO_INLINE
stacktrace stacktrace::current(std::size_t skip, std::size_t max_depth) {
return generate_trace(skip + 1, max_depth);
}
void stacktrace::print() const {
print(std::cerr, true);
}
void stacktrace::print(std::ostream& stream) const {
print(stream, true);
}
void stacktrace::print(std::ostream& stream, bool color) const {
print(stream, color, true, nullptr);
}
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
if(color) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream<<(header ? header : "Stack trace (most recent call first):")<<std::endl;
std::size_t counter = 0;
if(frames.empty()) {
stream<<"<empty trace>"<<std::endl;
return;
}
const auto reset = color ? ESC "0m" : "";
const auto green = color ? ESC "32m" : "";
const auto yellow = color ? ESC "33m" : "";
const auto blue = color ? ESC "34m" : "";
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
stream
<< '#'
<< std::setw(static_cast<int>(frame_number_width))
<< std::left
<< counter
<< std::right
<< " ";
if(frame.is_inline) {
stream
<< std::setw(2 * sizeof(frame_ptr) + 2)
<< "(inlined)";
} else {
stream
<< std::hex
<< blue
<< "0x"
<< std::setw(2 * sizeof(frame_ptr))
<< std::setfill('0')
<< frame.address
<< std::dec
<< std::setfill(' ')
<< reset;
}
if(!frame.symbol.empty()) {
stream
<< " in "
<< yellow
<< frame.symbol
<< reset;
}
if(!frame.filename.empty()) {
stream
<< " at "
<< green
<< frame.filename
<< reset;
if(frame.line.has_value()) {
stream
<< ":"
<< blue
<< frame.line.value()
<< reset;
if(frame.column.has_value()) {
stream << ':'
<< blue
<< std::to_string(frame.column.value())
<< reset;
}
}
}
if(newline_at_end || &frame != &frames.back()) {
stream << std::endl;
}
counter++;
}
}
void stacktrace::clear() {
frames.clear();
}
bool stacktrace::empty() const noexcept {
return frames.empty();
}
std::string stacktrace::to_string(bool color) const {
std::ostringstream oss;
print(oss, color, false, nullptr);
return std::move(oss).str();
}
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
return stream << trace.to_string();
}
CPPTRACE_FORCE_NO_INLINE
raw_trace generate_raw_trace(std::size_t skip) {
try {
return raw_trace{detail::capture_frames(skip + 1, SIZE_MAX)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth) {
try {
return raw_trace{detail::capture_frames(skip + 1, max_depth)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip) {
return detail::safe_capture_frames(buffer, size, skip + 1, SIZE_MAX);
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_generate_raw_trace(
frame_ptr* buffer,
std::size_t size,
std::size_t skip,
std::size_t max_depth
) {
return detail::safe_capture_frames(buffer, size, skip + 1, max_depth);
}
CPPTRACE_FORCE_NO_INLINE
object_trace generate_object_trace(std::size_t skip) {
try {
return object_trace{detail::get_frames_object_info(detail::capture_frames(skip + 1, SIZE_MAX))};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
object_trace generate_object_trace(std::size_t skip, std::size_t max_depth) {
try {
return object_trace{detail::get_frames_object_info(detail::capture_frames(skip + 1, max_depth))};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
stacktrace generate_trace(std::size_t skip) {
return generate_trace(skip + 1, SIZE_MAX);
}
CPPTRACE_FORCE_NO_INLINE
stacktrace generate_trace(std::size_t skip, std::size_t max_depth) {
try {
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
}
return stacktrace{std::move(trace)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace();
}
}
std::string demangle(const std::string& name) {
return detail::demangle(name);
}
bool isatty(int fd) {
return detail::isatty(fd);
}
extern const int stdin_fileno = detail::fileno(stdin);
extern const int stdout_fileno = detail::fileno(stdout);
extern const int stderr_fileno = detail::fileno(stderr);
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
generate_trace(1).print(
std::cerr,
isatty(stderr_fileno),
true,
"Stack trace to reach terminate handler (most recent call first):"
);
}
[[noreturn]] void terminate_handler() {
// TODO: Support std::nested_exception?
try {
auto ptr = std::current_exception();
if(ptr == nullptr) {
std::cerr << "terminate called without an active exception\n";
print_terminate_trace();
} else {
std::rethrow_exception(ptr);
}
} catch(cpptrace::exception& e) {
std::cerr << "Terminate called after throwing an instance of "
<< demangle(typeid(e).name())
<< ": "
<< e.get_raw_what()
<< '\n';
e.get_trace().print(std::cerr, isatty(stderr_fileno));
} catch(std::exception& e) {
std::cerr << "Terminate called after throwing an instance of "
<< demangle(typeid(e).name())
<< ": "
<< e.what()
<< '\n';
print_terminate_trace();
} catch(...) {
std::cerr << "Terminate called after throwing an instance of "
<< detail::exception_type_name()
<< "\n";
print_terminate_trace();
}
std::flush(std::cerr);
abort();
}
void register_terminate_handler() {
std::set_terminate(terminate_handler);
}
namespace detail {
std::atomic_bool absorb_trace_exceptions(true); // NOSONAR
std::atomic<enum cache_mode> cache_mode(cache_mode::prioritize_speed); // NOSONAR
}
void absorb_trace_exceptions(bool absorb) {
detail::absorb_trace_exceptions = absorb;
}
namespace experimental {
void set_cache_mode(cache_mode mode) {
detail::cache_mode = mode;
}
}
namespace detail {
bool should_absorb_trace_exceptions() {
return absorb_trace_exceptions;
}
enum cache_mode get_cache_mode() {
return cache_mode;
}
CPPTRACE_FORCE_NO_INLINE
raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth) noexcept {
try {
return generate_raw_trace(skip + 1, max_depth);
} catch(const std::exception& e) {
if(!detail::should_absorb_trace_exceptions()) {
// TODO: Append to message somehow
std::fprintf(
stderr,
"Cpptrace: Exception ocurred while resolving trace in cpptrace::exception object:\n%s\n",
e.what()
);
}
return raw_trace{};
}
}
}
exception::exception(std::size_t skip, std::size_t max_depth) noexcept
: trace(detail::get_raw_trace_and_absorb(skip + 1, max_depth)) {}
const char* exception::what() const noexcept {
return get_what().c_str();
}
const std::string& exception::get_what() const noexcept {
if(what_string.empty()) {
what_string = get_raw_what() + std::string(":\n") + get_trace().to_string();
}
return what_string;
}
const char* exception::get_raw_what() const noexcept {
return "cpptrace::exception";
}
const raw_trace& exception::get_raw_trace() const noexcept {
return trace;
}
const stacktrace& exception::get_trace() const noexcept {
// I think a non-empty raw trace can never resolve as empty, so this will accurately prevent resolving more
// than once. Either way the raw trace is cleared.
try {
if(resolved_trace.empty() && !trace.empty()) {
resolved_trace = trace.resolve();
trace.clear();
}
} catch(const std::exception& e) {
if(!detail::should_absorb_trace_exceptions()) {
// TODO: Append to message somehow
std::fprintf(
stderr,
"Exception ocurred while resolving trace in cpptrace::exception object:\n%s\n",
e.what()
);
}
}
return resolved_trace;
}
const char* exception_with_message::get_raw_what() const noexcept {
return message.c_str();
}
const char* nested_exception::get_raw_what() const noexcept {
if(what_value.empty()) {
try {
std::rethrow_exception(ptr);
} catch(std::exception& e) {
// what_value = std::string("cpptrace::nested_exception: ") + e.what();
what_value = e.what();
} catch(...) {
what_value = "<cpptrace::nested_exception holding instance of " + detail::exception_type_name() + ">";
}
}
return what_value.c_str();
}
std::exception_ptr nested_exception::nested_ptr() const noexcept {
return ptr;
}
void rethrow_and_wrap_if_needed(std::size_t skip) {
try {
std::rethrow_exception(std::current_exception());
} catch(cpptrace::exception&) {
throw; // already a cpptrace::exception
} catch(...) {
throw nested_exception(std::current_exception(), skip + 1);
}
}
}