diff --git a/README.md b/README.md index 6be0ccd..f039bb2 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ namespace cpptrace { object_trace resolve_object_trace() const; stacktrace resolve() const; void clear(); + bool empty() const noexcept; /* iterators exist for this object */ }; @@ -141,6 +142,7 @@ namespace cpptrace { explicit object_trace(std::vector&& frames); stacktrace resolve() const; void clear(); + bool empty() const noexcept; /* iterators exist for this object */ }; @@ -155,17 +157,21 @@ namespace cpptrace { std::string symbol; bool operator==(const stacktrace_frame& other) const; bool operator!=(const stacktrace_frame& other) const; + std::string to_string() const; + /* operator<<(ostream, ..) and std::format support exist for this object */ }; struct stacktrace { std::vector frames; + explicit stacktrace(); explicit stacktrace(std::vector&& frames); void print() const; void print(std::ostream& stream) const; void print(std::ostream& stream, bool color) const; std::string to_string() const; void clear(); - /* iterators exist for this object */ + bool empty() const noexcept; + /* operator<<(ostream, ..), std::format support, and iterators exist for this object */ }; /* @@ -183,17 +189,29 @@ namespace cpptrace { // Traced exception class // Extending classes should call the exception constructor with a skip value of 1. class exception : public std::exception { - explicit exception(uint32_t skip) + protected: + mutable raw_trace trace; + mutable stacktrace resolved_trace; + mutable std::string resolved_what; + explicit exception(uint32_t skip) noexcept; + const stacktrace& get_resolved_trace() const noexcept; + virtual const std::string& get_resolved_what() const noexcept; public: - explicit exception(); + explicit exception() noexcept; const char* what() const noexcept override; + const std::string& get_what() const noexcept; // what(), but not a C-string + const raw_trace& get_raw_trace() const noexcept; + const stacktrace& get_trace() const noexcept; }; class exception_with_message : public exception { - explicit exception_with_message(std::string&& message_arg, uint32_t skip) + mutable std::string message; + explicit exception_with_message(std::string&& message_arg, uint32_t skip) noexcept; + const std::string& get_resolved_what() const noexcept override; public: explicit exception_with_message(std::string&& message_arg); const char* what() const noexcept override; + const std::string& get_message() const noexcept; }; // All stdexcept errors have analogs here. Same constructor as exception_with_message. diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 5b522e8..e1f12a3 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -7,6 +7,10 @@ #include #include +#ifdef __cpp_lib_format +#include +#endif + #if defined(_WIN32) || defined(__CYGWIN__) #define CPPTRACE_API __declspec(dllexport) #else @@ -23,6 +27,7 @@ namespace cpptrace { CPPTRACE_API object_trace resolve_object_trace() const; CPPTRACE_API stacktrace resolve() const; CPPTRACE_API void clear(); + CPPTRACE_API bool empty() const noexcept; using iterator = std::vector::iterator; using const_iterator = std::vector::const_iterator; @@ -44,6 +49,7 @@ namespace cpptrace { explicit object_trace(std::vector&& frames_) : frames(frames_) {} CPPTRACE_API stacktrace resolve() const; CPPTRACE_API void clear(); + CPPTRACE_API bool empty() const noexcept; using iterator = std::vector::iterator; using const_iterator = std::vector::const_iterator; @@ -69,16 +75,21 @@ namespace cpptrace { bool operator!=(const stacktrace_frame& other) const { return !operator==(other); } + CPPTRACE_API std::string to_string() const; + CPPTRACE_API friend std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame); }; struct stacktrace { std::vector frames; + explicit stacktrace() {} explicit stacktrace(std::vector&& frames_) : frames(frames_) {} CPPTRACE_API void print() const; CPPTRACE_API void print(std::ostream& stream) const; CPPTRACE_API void print(std::ostream& stream, bool color) const; - CPPTRACE_API std::string to_string() const; CPPTRACE_API void clear(); + CPPTRACE_API bool empty() const noexcept; + CPPTRACE_API std::string to_string() const; + CPPTRACE_API friend std::ostream& operator<<(std::ostream& stream, const stacktrace& trace); using iterator = std::vector::iterator; using const_iterator = std::vector::const_iterator; @@ -96,91 +107,150 @@ namespace cpptrace { // utilities: CPPTRACE_API std::string demangle(const std::string& name); + CPPTRACE_API void absorb_trace_exceptions(bool absorb); + + namespace detail { + CPPTRACE_API bool should_absorb_trace_exceptions(); + } class exception : public std::exception { protected: mutable raw_trace trace; - mutable std::string resolved_message; - explicit exception(uint32_t skip) : trace(generate_raw_trace(skip + 1)) {} - virtual const std::string& get_resolved_message() const { - if(resolved_message.empty()) { - resolved_message = "cpptrace::exception:\n" + trace.resolve().to_string(); - trace.clear(); + mutable stacktrace resolved_trace; + mutable std::string resolved_what; + explicit exception(uint32_t skip) noexcept + try : trace(generate_raw_trace(skip + 1)) {} + catch(const std::exception& e) { + if(!detail::should_absorb_trace_exceptions()) { + std::rethrow_exception(std::current_exception()); + } } - return resolved_message; + const stacktrace& get_resolved_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()) { + std::rethrow_exception(std::current_exception()); + } + } + return resolved_trace; + } + virtual const std::string& get_resolved_what() const noexcept { + if(resolved_what.empty()) { + resolved_what = "cpptrace::exception:\n" + get_resolved_trace().to_string(); + } + return resolved_what; } public: - explicit exception() : exception(1) {} + explicit exception() noexcept : exception(1) {} const char* what() const noexcept override { - return get_resolved_message().c_str(); + return get_resolved_what().c_str(); + } + // what(), but not a C-string + const std::string& get_what() const noexcept { + return resolved_what; + } + const raw_trace& get_raw_trace() const noexcept { + return trace; + } + const stacktrace& get_trace() const noexcept { + return resolved_trace; } }; class exception_with_message : public exception { protected: mutable std::string message; - explicit exception_with_message(std::string&& message_arg, uint32_t skip) + explicit exception_with_message(std::string&& message_arg, uint32_t skip) noexcept : exception(skip + 1), message(std::move(message_arg)) {} - const std::string& get_resolved_message() const override { - if(resolved_message.empty()) { - resolved_message = message + "\n" + trace.resolve().to_string(); - trace.clear(); - message.clear(); + const std::string& get_resolved_what() const noexcept override { + if(resolved_what.empty()) { + resolved_what = message + "\n" + get_resolved_trace().to_string(); } - return resolved_message; + return resolved_what; } public: - explicit exception_with_message(std::string&& message_arg) + explicit exception_with_message(std::string&& message_arg) noexcept : exception_with_message(std::move(message_arg), 1) {} - const char* what() const noexcept override { - return get_resolved_message().c_str(); + const std::string& get_message() const noexcept { + return message; } }; class logic_error : public exception_with_message { public: - explicit logic_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + explicit logic_error(std::string&& message_arg) noexcept + : exception_with_message(std::move(message_arg), 1) {} }; class domain_error : public exception_with_message { public: - explicit domain_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + explicit domain_error(std::string&& message_arg) noexcept + : exception_with_message(std::move(message_arg), 1) {} }; class invalid_argument : public exception_with_message { public: - explicit invalid_argument(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + explicit invalid_argument(std::string&& message_arg) noexcept + : exception_with_message(std::move(message_arg), 1) {} }; class length_error : public exception_with_message { public: - explicit length_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + explicit length_error(std::string&& message_arg) noexcept + : exception_with_message(std::move(message_arg), 1) {} }; class out_of_range : public exception_with_message { public: - explicit out_of_range(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + explicit out_of_range(std::string&& message_arg) noexcept + : exception_with_message(std::move(message_arg), 1) {} }; class runtime_error : public exception_with_message { public: - explicit runtime_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + explicit runtime_error(std::string&& message_arg) noexcept + : exception_with_message(std::move(message_arg), 1) {} }; class range_error : public exception_with_message { public: - explicit range_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + explicit range_error(std::string&& message_arg) noexcept + : exception_with_message(std::move(message_arg), 1) {} }; class overflow_error : public exception_with_message { public: - explicit overflow_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + explicit overflow_error(std::string&& message_arg) noexcept + : exception_with_message(std::move(message_arg), 1) {} }; class underflow_error : public exception_with_message { public: - explicit underflow_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + explicit underflow_error(std::string&& message_arg) noexcept + : exception_with_message(std::move(message_arg), 1) {} }; } #endif + +#ifdef __cpp_lib_format +template <> +struct std::formatter : std::formatter { + auto format(cpptrace::stacktrace_frame frame, format_context& ctx) const { + return formatter::format(frame.to_string(), ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + auto format(cpptrace::stacktrace trace, format_context& ctx) const { + return formatter::format(trace.to_string(), ctx); + } +}; +#endif diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index 99b2b1f..e2d7a9e 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -25,6 +26,10 @@ #define CYAN ESC "36m" namespace cpptrace { + namespace detail { + std::atomic_bool absorb_trace_exceptions = true; + } + CPPTRACE_API object_trace raw_trace::resolve_object_trace() const { return object_trace(detail::get_frames_object_info(frames)); @@ -44,6 +49,11 @@ namespace cpptrace { frames.clear(); } + CPPTRACE_API + bool raw_trace::empty() const noexcept { + return frames.empty(); + } + CPPTRACE_API stacktrace object_trace::resolve() const { return stacktrace(detail::resolve_frames(frames)); @@ -54,6 +64,41 @@ namespace cpptrace { frames.clear(); } + CPPTRACE_API + bool object_trace::empty() const noexcept { + return frames.empty(); + } + + CPPTRACE_API std::string stacktrace_frame::to_string() const { + std::ostringstream oss; + oss << *this; + return std::move(oss).str(); + } + + CPPTRACE_API std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) { + stream + << std::hex + << "0x" + << std::setw(2 * sizeof(uintptr_t)) + << std::setfill('0') + << frame.address + << std::dec + << std::setfill(' ') + << " in " + << frame.symbol + << " at " + << frame.filename; + if(frame.line != 0) { + stream + << ":" + << frame.line; + if(frame.column != UINT_LEAST32_MAX) { + stream << frame.column; + } + } + return stream; + } + CPPTRACE_API void stacktrace::print() const { print(std::cerr, true); @@ -113,11 +158,9 @@ namespace cpptrace { << frame.line << (color ? RESET : ""); if(frame.column != UINT_LEAST32_MAX) { - stream << ( - frame.column > 0 - ? (color ? ":" BLUE : ":") + std::to_string(frame.column) + (color ? RESET : "") - : "" - ); + stream << (color ? ":" BLUE : ":") + << std::to_string(frame.column) + << (color ? RESET : ""); } } if(newline_at_end || &frame != &frames.back()) { @@ -126,6 +169,16 @@ namespace cpptrace { } } + CPPTRACE_API + void stacktrace::clear() { + frames.clear(); + } + + CPPTRACE_API + bool stacktrace::empty() const noexcept { + return frames.empty(); + } + CPPTRACE_API std::string stacktrace::to_string() const { std::ostringstream oss; @@ -133,9 +186,8 @@ namespace cpptrace { return std::move(oss).str(); } - CPPTRACE_API - void stacktrace::clear() { - frames.clear(); + CPPTRACE_API std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) { + return stream << trace.to_string(); } CPPTRACE_FORCE_NO_INLINE CPPTRACE_API @@ -162,4 +214,14 @@ namespace cpptrace { std::string demangle(const std::string& name) { return detail::demangle(name); } + + CPPTRACE_API void absorb_trace_exceptions(bool absorb) { + detail::absorb_trace_exceptions = absorb; + } + + namespace detail { + CPPTRACE_API bool should_absorb_trace_exceptions() { + return detail::absorb_trace_exceptions; + } + } }