diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 86013f2..9af6485 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -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; }; diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index fc39c72..b547cb6 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #include #include @@ -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,72 +441,127 @@ 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(); - } - - 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(); + 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); } - } 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() + } + 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)); + } + } + 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); + } + 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? + std::fprintf( + stderr, + "Exception ocurred while resolving trace in cpptrace::exception object:\n%s\n", + e.what() + ); + } + } + trace.~raw_trace(); + new (&resolved_trace) stacktrace(std::move(new_trace)); + resolved = true; + } + return resolved_trace; + } + 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(); + } } - return resolved_trace; } - const char* exception_with_message::get_raw_what() const noexcept { - return message.c_str(); + 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* nested_exception::get_raw_what() const noexcept { - if(what_value.empty()) { + 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());