Rework the exception interface

This commit is contained in:
Jeremy 2023-11-18 09:00:57 -06:00
parent 0de366f7d6
commit 77a2e3a22b
No known key found for this signature in database
GPG Key ID: B4C8300FEC395042
2 changed files with 157 additions and 71 deletions

View File

@ -213,51 +213,80 @@ namespace cpptrace {
namespace detail {
CPPTRACE_EXPORT bool should_absorb_trace_exceptions();
CPPTRACE_EXPORT enum cache_mode get_cache_mode();
// This is a helper utility, if the library weren't C++11 an std::variant would be used
class CPPTRACE_EXPORT lazy_trace_holder {
bool resolved;
union {
raw_trace trace;
stacktrace resolved_trace;
};
public:
// constructors
lazy_trace_holder() : trace() {}
explicit lazy_trace_holder(raw_trace&& _trace) : resolved(false), trace(std::move(_trace)) {}
explicit lazy_trace_holder(stacktrace&& _resolved_trace) : resolved(true), resolved_trace(std::move(_resolved_trace)) {}
// logistics
lazy_trace_holder(const lazy_trace_holder& other);
lazy_trace_holder(lazy_trace_holder&& other) noexcept;
lazy_trace_holder& operator=(const lazy_trace_holder& other);
lazy_trace_holder& operator=(lazy_trace_holder&& other) noexcept;
~lazy_trace_holder();
// access
stacktrace& get_resolved_trace();
const stacktrace& get_resolved_trace() const;
private:
void clear();
};
}
// Interface for a traced exception object
class CPPTRACE_EXPORT exception : public std::exception {
mutable raw_trace trace;
mutable stacktrace resolved_trace;
public:
virtual const char* message() const noexcept = 0;
virtual const stacktrace& trace() const noexcept = 0;
};
// Cpptrace traced exception object
// I hate to have to expose anything about implementation detail but the idea here is that
// TODO: CPPTRACE_FORCE_NO_INLINE annotations
class CPPTRACE_EXPORT lazy_exception : public exception {
mutable detail::lazy_trace_holder trace_holder;
mutable std::string what_string;
protected:
explicit exception(std::size_t skip, std::size_t max_depth) noexcept;
explicit exception(std::size_t skip) noexcept : exception(skip + 1, SIZE_MAX) {}
explicit lazy_exception(std::size_t skip, std::size_t max_depth) noexcept;
explicit lazy_exception(std::size_t skip) noexcept : lazy_exception(skip + 1, SIZE_MAX) {}
public:
explicit exception() noexcept : exception(1) {}
explicit lazy_exception() noexcept : lazy_exception(1) {}
// std::exception
const char* what() const noexcept override;
// what(), but not a C-string. Performs lazy evaluation of the full what string.
virtual const std::string& get_what() const noexcept;
// Just the plain what() value without the stacktrace. This value is called by get_what() during lazy
// evaluation.
virtual const char* get_raw_what() const noexcept;
// Returns internal raw_trace
const raw_trace& get_raw_trace() const noexcept;
// Returns a resolved trace from the raw_trace. Handles lazy evaluation of the resolved trace.
const stacktrace& get_trace() const noexcept;
// cpptrace::exception
const char* message() const noexcept override;
const stacktrace& trace() const noexcept override;
};
class CPPTRACE_EXPORT exception_with_message : public exception {
mutable std::string message;
class CPPTRACE_EXPORT exception_with_message : public lazy_exception {
mutable std::string user_message;
protected:
explicit exception_with_message(
std::string&& message_arg,
std::size_t skip
) noexcept : exception(skip + 1), message(std::move(message_arg)) {}
) noexcept : lazy_exception(skip + 1), user_message(std::move(message_arg)) {}
explicit exception_with_message(
std::string&& message_arg,
std::size_t skip,
std::size_t max_depth
) noexcept : exception(skip + 1, max_depth), message(std::move(message_arg)) {}
) noexcept : lazy_exception(skip + 1, max_depth), user_message(std::move(message_arg)) {}
public:
explicit exception_with_message(std::string&& message_arg) noexcept
: exception_with_message(std::move(message_arg), 1) {}
const char* get_raw_what() const noexcept override;
const char* message() const noexcept override;
};
class logic_error : public exception_with_message {
@ -314,16 +343,16 @@ namespace cpptrace {
: exception_with_message(std::move(message_arg), 1) {}
};
class CPPTRACE_EXPORT nested_exception : public exception {
class CPPTRACE_EXPORT nested_exception : public lazy_exception {
std::exception_ptr ptr;
mutable std::string what_value;
mutable std::string message_value;
public:
explicit nested_exception(std::exception_ptr exception_ptr) noexcept
: exception(1), ptr(exception_ptr) {}
: lazy_exception(1), ptr(exception_ptr) {}
explicit nested_exception(std::exception_ptr exception_ptr, std::size_t skip) noexcept
: exception(skip + 1), ptr(exception_ptr) {}
: lazy_exception(skip + 1), ptr(exception_ptr) {}
const char* get_raw_what() const noexcept override;
const char* message() const noexcept override;
std::exception_ptr nested_ptr() const noexcept;
};

View File

@ -6,7 +6,9 @@
#include <cstdio>
#include <iomanip>
#include <iostream>
#include <new>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
@ -375,9 +377,9 @@ namespace cpptrace {
std::cerr << "Terminate called after throwing an instance of "
<< demangle(typeid(e).name())
<< ": "
<< e.get_raw_what()
<< e.message()
<< '\n';
e.get_trace().print(std::cerr, isatty(stderr_fileno));
e.trace().print(std::cerr, isatty(stderr_fileno));
} catch(std::exception& e) {
std::cerr << "Terminate called after throwing an instance of "
<< demangle(typeid(e).name())
@ -439,41 +441,57 @@ namespace cpptrace {
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();
lazy_trace_holder::lazy_trace_holder(const lazy_trace_holder& other) : resolved(other.resolved) {
if(other.resolved) {
new (&resolved_trace) stacktrace(other.resolved_trace);
} else {
new (&trace) raw_trace(other.trace);
}
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;
lazy_trace_holder::lazy_trace_holder(lazy_trace_holder&& other) noexcept : resolved(other.resolved) {
if(other.resolved) {
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
} else {
new (&trace) raw_trace(std::move(other.trace));
}
const char* exception::get_raw_what() const noexcept {
return "cpptrace::exception";
}
const raw_trace& exception::get_raw_trace() const noexcept {
return trace;
lazy_trace_holder& lazy_trace_holder::operator=(const lazy_trace_holder& other) {
clear();
resolved = other.resolved;
if(other.resolved) {
new (&resolved_trace) stacktrace(other.resolved_trace);
} else {
new (&trace) raw_trace(other.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.
return *this;
}
lazy_trace_holder& lazy_trace_holder::operator=(lazy_trace_holder&& other) noexcept {
clear();
resolved = other.resolved;
if(other.resolved) {
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
} else {
new (&trace) raw_trace(std::move(other.trace));
}
return *this;
}
lazy_trace_holder::~lazy_trace_holder() {
clear();
}
// access
stacktrace& lazy_trace_holder::get_resolved_trace() {
if(!resolved) {
stacktrace new_trace;
try {
if(resolved_trace.empty() && !trace.empty()) {
resolved_trace = trace.resolve();
trace.clear();
}
new_trace = trace.resolve();
} catch(const std::exception& e) {
if(!detail::should_absorb_trace_exceptions()) {
// TODO: Append to message somehow
// TODO: Append to message somehow?
std::fprintf(
stderr,
"Exception ocurred while resolving trace in cpptrace::exception object:\n%s\n",
@ -481,30 +499,69 @@ namespace cpptrace {
);
}
}
trace.~raw_trace();
new (&resolved_trace) stacktrace(std::move(new_trace));
resolved = true;
}
return resolved_trace;
}
const char* exception_with_message::get_raw_what() const noexcept {
return message.c_str();
const stacktrace& lazy_trace_holder::get_resolved_trace() const {
if(!resolved) {
throw std::logic_error(
"cpptrace::detaillazy_trace_holder::get_resolved_trace called on unresolved const object"
);
}
return resolved_trace;
}
void lazy_trace_holder::clear() {
if(resolved) {
resolved_trace.~stacktrace();
} else {
trace.~raw_trace();
}
}
}
const char* nested_exception::get_raw_what() const noexcept {
if(what_value.empty()) {
lazy_exception::lazy_exception(std::size_t skip, std::size_t max_depth) noexcept
: trace_holder(detail::get_raw_trace_and_absorb(skip + 1, max_depth)) {}
const char* lazy_exception::what() const noexcept {
if(what_string.empty()) {
what_string = message() + std::string(":\n") + trace_holder.get_resolved_trace().to_string();
}
return what_string.c_str();
}
const char* lazy_exception::message() const noexcept {
return "cpptrace::lazy_exception";
}
const stacktrace& lazy_exception::trace() const noexcept {
return trace_holder.get_resolved_trace();
}
const char* exception_with_message::message() const noexcept {
return user_message.c_str();
}
const char* nested_exception::message() const noexcept {
if(message_value.empty()) {
try {
std::rethrow_exception(ptr);
} catch(std::exception& e) {
what_value = std::string("Nested exception: ") + e.what();
message_value = std::string("Nested exception: ") + e.what();
} catch(...) {
what_value = "Nested exception holding instance of " + detail::exception_type_name();
message_value = "Nested exception holding instance of " + detail::exception_type_name();
}
}
return what_value.c_str();
return message_value.c_str();
}
std::exception_ptr nested_exception::nested_ptr() const noexcept {
return ptr;
}
CPPTRACE_FORCE_NO_INLINE
void rethrow_and_wrap_if_needed(std::size_t skip) {
try {
std::rethrow_exception(std::current_exception());