Rework the exception interface
This commit is contained in:
parent
0de366f7d6
commit
77a2e3a22b
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
121
src/cpptrace.cpp
121
src/cpptrace.cpp
@ -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());
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user