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 { namespace detail {
CPPTRACE_EXPORT bool should_absorb_trace_exceptions(); CPPTRACE_EXPORT bool should_absorb_trace_exceptions();
CPPTRACE_EXPORT enum cache_mode get_cache_mode(); 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 { class CPPTRACE_EXPORT exception : public std::exception {
mutable raw_trace trace; public:
mutable stacktrace resolved_trace; 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; mutable std::string what_string;
protected: protected:
explicit exception(std::size_t skip, std::size_t max_depth) noexcept; explicit lazy_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) noexcept : lazy_exception(skip + 1, SIZE_MAX) {}
public: public:
explicit exception() noexcept : exception(1) {} explicit lazy_exception() noexcept : lazy_exception(1) {}
// std::exception
const char* what() const noexcept override; const char* what() const noexcept override;
// what(), but not a C-string. Performs lazy evaluation of the full what string. // cpptrace::exception
virtual const std::string& get_what() const noexcept; const char* message() const noexcept override;
// Just the plain what() value without the stacktrace. This value is called by get_what() during lazy const stacktrace& trace() const noexcept override;
// 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;
}; };
class CPPTRACE_EXPORT exception_with_message : public exception { class CPPTRACE_EXPORT exception_with_message : public lazy_exception {
mutable std::string message; mutable std::string user_message;
protected: protected:
explicit exception_with_message( explicit exception_with_message(
std::string&& message_arg, std::string&& message_arg,
std::size_t skip 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( explicit exception_with_message(
std::string&& message_arg, std::string&& message_arg,
std::size_t skip, std::size_t skip,
std::size_t max_depth 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: public:
explicit exception_with_message(std::string&& message_arg) noexcept explicit exception_with_message(std::string&& message_arg) noexcept
: exception_with_message(std::move(message_arg), 1) {} : 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 { class logic_error : public exception_with_message {
@ -314,16 +343,16 @@ namespace cpptrace {
: exception_with_message(std::move(message_arg), 1) {} : 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; std::exception_ptr ptr;
mutable std::string what_value; mutable std::string message_value;
public: public:
explicit nested_exception(std::exception_ptr exception_ptr) noexcept 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 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; std::exception_ptr nested_ptr() const noexcept;
}; };

View File

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