From 4c59e73a010fd0bd4c0fb548a9a6ad5f6af25e02 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 5 Oct 2024 18:01:12 -0500 Subject: [PATCH] Split up cpptrace.hpp --- CMakeLists.txt | 3 + include/cpptrace/basic.hpp | 234 ++++++++++ include/cpptrace/cpptrace.hpp | 497 +--------------------- include/cpptrace/exceptions.hpp | 218 ++++++++++ include/cpptrace/forward.hpp | 18 + include/cpptrace/from_current.hpp | 2 +- include/cpptrace/io.hpp | 52 +++ include/cpptrace/utils.hpp | 48 +++ src/cpptrace.cpp | 276 ------------ src/exceptions.cpp | 186 ++++++++ src/options.cpp | 41 ++ src/options.hpp | 14 + src/symbols/dwarf/debug_map_resolver.cpp | 2 +- src/symbols/dwarf/dwarf.hpp | 2 +- src/symbols/dwarf/dwarf_resolver.cpp | 2 +- src/symbols/dwarf/resolver.hpp | 2 +- src/symbols/symbols.hpp | 2 +- src/symbols/symbols_core.cpp | 2 +- src/symbols/symbols_with_addr2line.cpp | 2 +- src/symbols/symbols_with_dbghelp.cpp | 2 +- src/symbols/symbols_with_dl.cpp | 2 +- src/symbols/symbols_with_libbacktrace.cpp | 2 +- src/symbols/symbols_with_libdwarf.cpp | 2 +- src/symbols/symbols_with_nothing.cpp | 2 +- src/unwind/unwind.hpp | 2 +- src/unwind/unwind_with_dbghelp.cpp | 2 +- src/unwind/unwind_with_winapi.cpp | 2 +- src/utils.cpp | 79 ++++ src/utils/common.hpp | 7 +- src/utils/utils.hpp | 2 - 30 files changed, 915 insertions(+), 792 deletions(-) create mode 100644 include/cpptrace/basic.hpp create mode 100644 include/cpptrace/exceptions.hpp create mode 100644 include/cpptrace/forward.hpp create mode 100644 include/cpptrace/io.hpp create mode 100644 include/cpptrace/utils.hpp create mode 100644 src/exceptions.cpp create mode 100644 src/options.cpp create mode 100644 src/options.hpp create mode 100644 src/utils.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 322317c0..aa21b3ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,7 +226,10 @@ target_sources( src/binary/safe_dl.cpp src/cpptrace.cpp src/ctrace.cpp + src/exceptions.cpp src/from_current.cpp + src/options.cpp + src/utils.cpp src/demangle/demangle_with_cxxabi.cpp src/demangle/demangle_with_nothing.cpp src/demangle/demangle_with_winapi.cpp diff --git a/include/cpptrace/basic.hpp b/include/cpptrace/basic.hpp new file mode 100644 index 00000000..03100629 --- /dev/null +++ b/include/cpptrace/basic.hpp @@ -0,0 +1,234 @@ +#ifndef CPPTRACE_BASIC_HPP +#define CPPTRACE_BASIC_HPP + +#include + +#include +#include +#include +#include + +#ifdef _WIN32 +#define CPPTRACE_EXPORT_ATTR __declspec(dllexport) +#define CPPTRACE_IMPORT_ATTR __declspec(dllimport) +#else +#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default"))) +#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default"))) +#endif + +#ifdef CPPTRACE_STATIC_DEFINE +# define CPPTRACE_EXPORT +# define CPPTRACE_NO_EXPORT +#else +# ifndef CPPTRACE_EXPORT +# ifdef cpptrace_lib_EXPORTS + /* We are building this library */ +# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR +# else + /* We are using this library */ +# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR +# endif +# endif +#endif + +#ifdef _MSC_VER + #define CPPTRACE_FORCE_NO_INLINE __declspec(noinline) +#else + #define CPPTRACE_FORCE_NO_INLINE __attribute__((noinline)) +#endif + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector and others for some +// reason +// 4275 is the same thing but for base classes +#pragma warning(disable: 4251; disable: 4275) +#endif + +namespace cpptrace { + struct CPPTRACE_EXPORT raw_trace { + std::vector frames; + static raw_trace current(std::size_t skip = 0); + static raw_trace current(std::size_t skip, std::size_t max_depth); + object_trace resolve_object_trace() const; + stacktrace resolve() const; + void clear(); + bool empty() const noexcept; + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + inline iterator begin() noexcept { return frames.begin(); } + inline iterator end() noexcept { return frames.end(); } + inline const_iterator begin() const noexcept { return frames.begin(); } + inline const_iterator end() const noexcept { return frames.end(); } + inline const_iterator cbegin() const noexcept { return frames.cbegin(); } + inline const_iterator cend() const noexcept { return frames.cend(); } + }; + + struct CPPTRACE_EXPORT object_frame { + frame_ptr raw_address; + frame_ptr object_address; + std::string object_path; + }; + + struct CPPTRACE_EXPORT object_trace { + std::vector frames; + static object_trace current(std::size_t skip = 0); + static object_trace current(std::size_t skip, std::size_t max_depth); + stacktrace resolve() const; + void clear(); + bool empty() const noexcept; + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + inline iterator begin() noexcept { return frames.begin(); } + inline iterator end() noexcept { return frames.end(); } + inline const_iterator begin() const noexcept { return frames.begin(); } + inline const_iterator end() const noexcept { return frames.end(); } + inline const_iterator cbegin() const noexcept { return frames.cbegin(); } + inline const_iterator cend() const noexcept { return frames.cend(); } + }; + + // This represents a nullable integer type + // The max value of the type is used as a sentinel + // This is used over std::optional because the library is C++11 and also std::optional is a bit heavy-duty for this + // use. + template::value, int>::type = 0> + struct nullable { + T raw_value; + nullable& operator=(T value) { + raw_value = value; + return *this; + } + bool has_value() const noexcept { + return raw_value != (std::numeric_limits::max)(); + } + T& value() noexcept { + return raw_value; + } + const T& value() const noexcept { + return raw_value; + } + T value_or(T alternative) const noexcept { + return has_value() ? raw_value : alternative; + } + void swap(nullable& other) noexcept { + std::swap(raw_value, other.raw_value); + } + void reset() noexcept { + raw_value = (std::numeric_limits::max)(); + } + bool operator==(const nullable& other) const noexcept { + return raw_value == other.raw_value; + } + bool operator!=(const nullable& other) const noexcept { + return raw_value != other.raw_value; + } + constexpr static nullable null() noexcept { + return { (std::numeric_limits::max)() }; + } + }; + + struct CPPTRACE_EXPORT stacktrace_frame { + frame_ptr raw_address; + frame_ptr object_address; + nullable line; + nullable column; + std::string filename; + std::string symbol; + bool is_inline; + + bool operator==(const stacktrace_frame& other) const { + return raw_address == other.raw_address + && object_address == other.object_address + && line == other.line + && column == other.column + && filename == other.filename + && symbol == other.symbol; + } + + bool operator!=(const stacktrace_frame& other) const { + return !operator==(other); + } + + object_frame get_object_info() const; + + std::string to_string() const; + friend std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame); + }; + + struct CPPTRACE_EXPORT stacktrace { + std::vector frames; + static stacktrace current(std::size_t skip = 0); + static stacktrace current(std::size_t skip, std::size_t max_depth); + void print() const; + void print(std::ostream& stream) const; + void print(std::ostream& stream, bool color) const; + void print_with_snippets() const; + void print_with_snippets(std::ostream& stream) const; + void print_with_snippets(std::ostream& stream, bool color) const; + void clear(); + bool empty() const noexcept; + std::string to_string(bool color = false) const; + friend std::ostream& operator<<(std::ostream& stream, const stacktrace& trace); + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + inline iterator begin() noexcept { return frames.begin(); } + inline iterator end() noexcept { return frames.end(); } + inline const_iterator begin() const noexcept { return frames.begin(); } + inline const_iterator end() const noexcept { return frames.end(); } + inline const_iterator cbegin() const noexcept { return frames.cbegin(); } + inline const_iterator cend() const noexcept { return frames.cend(); } + private: + void print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const; + void print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const; + friend void print_terminate_trace(); + }; + + CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip = 0); + CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth); + CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip = 0); + CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip, std::size_t max_depth); + CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip = 0); + CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip, std::size_t max_depth); + + // Path max isn't so simple, so I'm choosing 4096 which seems to encompass what all major OS's expect and should be + // fine in all reasonable cases. + // https://eklitzke.org/path-max-is-tricky + // https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html + #define CPPTRACE_PATH_MAX 4096 + + // safe tracing interface + // signal-safe + CPPTRACE_EXPORT std::size_t safe_generate_raw_trace( + frame_ptr* buffer, + std::size_t size, + std::size_t skip = 0 + ); + // signal-safe + CPPTRACE_EXPORT std::size_t safe_generate_raw_trace( + frame_ptr* buffer, + std::size_t size, + std::size_t skip, + std::size_t max_depth + ); + struct CPPTRACE_EXPORT safe_object_frame { + frame_ptr raw_address; + // This ends up being the real object address. It was named at a time when I thought the object base address + // still needed to be added in + frame_ptr address_relative_to_object_start; + char object_path[CPPTRACE_PATH_MAX + 1]; + // To be called outside a signal handler. Not signal safe. + object_frame resolve() const; + }; + // signal-safe + CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out); + CPPTRACE_EXPORT bool can_signal_safe_unwind(); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 667510a0..998f509e 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -1,498 +1,9 @@ #ifndef CPPTRACE_HPP #define CPPTRACE_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#define CPPTRACE_EXPORT_ATTR __declspec(dllexport) -#define CPPTRACE_IMPORT_ATTR __declspec(dllimport) -#else -#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default"))) -#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default"))) -#endif - -#ifdef CPPTRACE_STATIC_DEFINE -# define CPPTRACE_EXPORT -# define CPPTRACE_NO_EXPORT -#else -# ifndef CPPTRACE_EXPORT -# ifdef cpptrace_lib_EXPORTS - /* We are building this library */ -# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR -# else - /* We are using this library */ -# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR -# endif -# endif -#endif - -#ifndef CPPTRACE_NO_STD_FORMAT - #if __cplusplus >= 202002L - #ifdef __has_include - #if __has_include() - #define CPPTRACE_STD_FORMAT - #include - #endif - #endif - #endif -#endif - -#ifdef _MSC_VER - #define CPPTRACE_FORCE_NO_INLINE __declspec(noinline) -#else - #define CPPTRACE_FORCE_NO_INLINE __attribute__((noinline)) -#endif - -#ifdef _MSC_VER -#pragma warning(push) -// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector and others for some -// reason -// 4275 is the same thing but for base classes -#pragma warning(disable: 4251; disable: 4275) -#endif - -namespace cpptrace { - struct object_trace; - struct stacktrace; - - // Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t - using frame_ptr = std::uintptr_t; - - struct CPPTRACE_EXPORT raw_trace { - std::vector frames; - static raw_trace current(std::size_t skip = 0); - static raw_trace current(std::size_t skip, std::size_t max_depth); - object_trace resolve_object_trace() const; - stacktrace resolve() const; - void clear(); - bool empty() const noexcept; - - using iterator = std::vector::iterator; - using const_iterator = std::vector::const_iterator; - inline iterator begin() noexcept { return frames.begin(); } - inline iterator end() noexcept { return frames.end(); } - inline const_iterator begin() const noexcept { return frames.begin(); } - inline const_iterator end() const noexcept { return frames.end(); } - inline const_iterator cbegin() const noexcept { return frames.cbegin(); } - inline const_iterator cend() const noexcept { return frames.cend(); } - }; - - struct CPPTRACE_EXPORT object_frame { - frame_ptr raw_address; - frame_ptr object_address; - std::string object_path; - }; - - struct CPPTRACE_EXPORT object_trace { - std::vector frames; - static object_trace current(std::size_t skip = 0); - static object_trace current(std::size_t skip, std::size_t max_depth); - stacktrace resolve() const; - void clear(); - bool empty() const noexcept; - - using iterator = std::vector::iterator; - using const_iterator = std::vector::const_iterator; - inline iterator begin() noexcept { return frames.begin(); } - inline iterator end() noexcept { return frames.end(); } - inline const_iterator begin() const noexcept { return frames.begin(); } - inline const_iterator end() const noexcept { return frames.end(); } - inline const_iterator cbegin() const noexcept { return frames.cbegin(); } - inline const_iterator cend() const noexcept { return frames.cend(); } - }; - - // This represents a nullable integer type - // The max value of the type is used as a sentinel - // This is used over std::optional because the library is C++11 and also std::optional is a bit heavy-duty for this - // use. - template::value, int>::type = 0> - struct nullable { - T raw_value; - nullable& operator=(T value) { - raw_value = value; - return *this; - } - bool has_value() const noexcept { - return raw_value != (std::numeric_limits::max)(); - } - T& value() noexcept { - return raw_value; - } - const T& value() const noexcept { - return raw_value; - } - T value_or(T alternative) const noexcept { - return has_value() ? raw_value : alternative; - } - void swap(nullable& other) noexcept { - std::swap(raw_value, other.raw_value); - } - void reset() noexcept { - raw_value = (std::numeric_limits::max)(); - } - bool operator==(const nullable& other) const noexcept { - return raw_value == other.raw_value; - } - bool operator!=(const nullable& other) const noexcept { - return raw_value != other.raw_value; - } - constexpr static nullable null() noexcept { - return { (std::numeric_limits::max)() }; - } - }; - - struct CPPTRACE_EXPORT stacktrace_frame { - frame_ptr raw_address; - frame_ptr object_address; - nullable line; - nullable column; - std::string filename; - std::string symbol; - bool is_inline; - - bool operator==(const stacktrace_frame& other) const { - return raw_address == other.raw_address - && object_address == other.object_address - && line == other.line - && column == other.column - && filename == other.filename - && symbol == other.symbol; - } - - bool operator!=(const stacktrace_frame& other) const { - return !operator==(other); - } - - object_frame get_object_info() const; - - std::string to_string() const; - friend std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame); - }; - - struct CPPTRACE_EXPORT stacktrace { - std::vector frames; - static stacktrace current(std::size_t skip = 0); - static stacktrace current(std::size_t skip, std::size_t max_depth); - void print() const; - void print(std::ostream& stream) const; - void print(std::ostream& stream, bool color) const; - void print_with_snippets() const; - void print_with_snippets(std::ostream& stream) const; - void print_with_snippets(std::ostream& stream, bool color) const; - void clear(); - bool empty() const noexcept; - std::string to_string(bool color = false) const; - friend std::ostream& operator<<(std::ostream& stream, const stacktrace& trace); - - using iterator = std::vector::iterator; - using const_iterator = std::vector::const_iterator; - inline iterator begin() noexcept { return frames.begin(); } - inline iterator end() noexcept { return frames.end(); } - inline const_iterator begin() const noexcept { return frames.begin(); } - inline const_iterator end() const noexcept { return frames.end(); } - inline const_iterator cbegin() const noexcept { return frames.cbegin(); } - inline const_iterator cend() const noexcept { return frames.cend(); } - private: - void print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const; - void print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const; - friend void print_terminate_trace(); - }; - - CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip = 0); - CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth); - CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip = 0); - CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip, std::size_t max_depth); - CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip = 0); - CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip, std::size_t max_depth); - - // Path max isn't so simple, so I'm choosing 4096 which seems to encompass what all major OS's expect and should be - // fine in all reasonable cases. - // https://eklitzke.org/path-max-is-tricky - // https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html - #define CPPTRACE_PATH_MAX 4096 - - // safe tracing interface - // signal-safe - CPPTRACE_EXPORT std::size_t safe_generate_raw_trace( - frame_ptr* buffer, - std::size_t size, - std::size_t skip = 0 - ); - // signal-safe - CPPTRACE_EXPORT std::size_t safe_generate_raw_trace( - frame_ptr* buffer, - std::size_t size, - std::size_t skip, - std::size_t max_depth - ); - struct CPPTRACE_EXPORT safe_object_frame { - frame_ptr raw_address; - // This ends up being the real object address. It was named at a time when I thought the object base address - // still needed to be added in - frame_ptr address_relative_to_object_start; - char object_path[CPPTRACE_PATH_MAX + 1]; - // To be called outside a signal handler. Not signal safe. - object_frame resolve() const; - }; - // signal-safe - CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out); - CPPTRACE_EXPORT bool can_signal_safe_unwind(); - - // utilities: - CPPTRACE_EXPORT std::string demangle(const std::string& name); - CPPTRACE_EXPORT std::string get_snippet( - const std::string& path, - std::size_t line, - std::size_t context_size, - bool color = false - ); - CPPTRACE_EXPORT bool isatty(int fd); - - CPPTRACE_EXPORT extern const int stdin_fileno; - CPPTRACE_EXPORT extern const int stderr_fileno; - CPPTRACE_EXPORT extern const int stdout_fileno; - - CPPTRACE_EXPORT void register_terminate_handler(); - - // configuration: - CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb); - CPPTRACE_EXPORT void enable_inlined_call_resolution(bool enable); - - enum class cache_mode { - // Only minimal lookup tables - prioritize_memory = 0, - // Build lookup tables but don't keep them around between trace calls - hybrid = 1, - // Build lookup tables as needed - prioritize_speed = 2 - }; - - namespace experimental { - CPPTRACE_EXPORT void set_cache_mode(cache_mode mode); - } - - // tracing exceptions: - namespace detail { - // 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() : resolved(false), 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 - const raw_trace& get_raw_trace() const; - stacktrace& get_resolved_trace(); - const stacktrace& get_resolved_trace() const; - private: - void clear(); - }; - - CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth); - CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip = 0); - } - - // Interface for a traced exception object - class CPPTRACE_EXPORT exception : public std::exception { - public: - const char* what() const noexcept override = 0; - 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 - class CPPTRACE_EXPORT lazy_exception : public exception { - mutable detail::lazy_trace_holder trace_holder; - mutable std::string what_string; - - public: - explicit lazy_exception( - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) : trace_holder(std::move(trace)) {} - // std::exception - const char* what() const noexcept override; - // cpptrace::exception - const char* message() const noexcept override; - const stacktrace& trace() const noexcept override; - }; - - class CPPTRACE_EXPORT exception_with_message : public lazy_exception { - mutable std::string user_message; - - public: - explicit exception_with_message( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept : lazy_exception(std::move(trace)), user_message(std::move(message_arg)) {} - - const char* message() const noexcept override; - }; - - class CPPTRACE_EXPORT logic_error : public exception_with_message { - public: - explicit logic_error( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : exception_with_message(std::move(message_arg), std::move(trace)) {} - }; - - class CPPTRACE_EXPORT domain_error : public exception_with_message { - public: - explicit domain_error( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : exception_with_message(std::move(message_arg), std::move(trace)) {} - }; - - class CPPTRACE_EXPORT invalid_argument : public exception_with_message { - public: - explicit invalid_argument( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : exception_with_message(std::move(message_arg), std::move(trace)) {} - }; - - class CPPTRACE_EXPORT length_error : public exception_with_message { - public: - explicit length_error( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : exception_with_message(std::move(message_arg), std::move(trace)) {} - }; - - class CPPTRACE_EXPORT out_of_range : public exception_with_message { - public: - explicit out_of_range( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : exception_with_message(std::move(message_arg), std::move(trace)) {} - }; - - class CPPTRACE_EXPORT runtime_error : public exception_with_message { - public: - explicit runtime_error( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : exception_with_message(std::move(message_arg), std::move(trace)) {} - }; - - class CPPTRACE_EXPORT range_error : public exception_with_message { - public: - explicit range_error( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : exception_with_message(std::move(message_arg), std::move(trace)) {} - }; - - class CPPTRACE_EXPORT overflow_error : public exception_with_message { - public: - explicit overflow_error( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : exception_with_message(std::move(message_arg), std::move(trace)) {} - }; - - class CPPTRACE_EXPORT underflow_error : public exception_with_message { - public: - explicit underflow_error( - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : exception_with_message(std::move(message_arg), std::move(trace)) {} - }; - - class CPPTRACE_EXPORT nested_exception : public lazy_exception { - std::exception_ptr ptr; - mutable std::string message_value; - public: - explicit nested_exception( - const std::exception_ptr& exception_ptr, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept - : lazy_exception(std::move(trace)), ptr(exception_ptr) {} - - const char* message() const noexcept override; - std::exception_ptr nested_ptr() const noexcept; - }; - - class CPPTRACE_EXPORT system_error : public runtime_error { - std::error_code ec; - public: - explicit system_error( - int error_code, - std::string&& message_arg, - raw_trace&& trace = detail::get_raw_trace_and_absorb() - ) noexcept; - const std::error_code& code() const noexcept; - }; - - // [[noreturn]] must come first due to old clang - [[noreturn]] CPPTRACE_EXPORT void rethrow_and_wrap_if_needed(std::size_t skip = 0); -} - -#if defined(CPPTRACE_STD_FORMAT) && defined(__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 - -// Exception wrapper utilities -#define CPPTRACE_WRAP_BLOCK(statements) do { \ - try { \ - statements \ - } catch(...) { \ - ::cpptrace::rethrow_and_wrap_if_needed(); \ - } \ - } while(0) - -#define CPPTRACE_WRAP(expression) [&] () -> decltype((expression)) { \ - try { \ - return expression; \ - } catch(...) { \ - ::cpptrace::rethrow_and_wrap_if_needed(1); \ - } \ - } () - -#ifdef _MSC_VER -#pragma warning(pop) -#endif +#include +#include +#include +#include #endif diff --git a/include/cpptrace/exceptions.hpp b/include/cpptrace/exceptions.hpp new file mode 100644 index 00000000..2d41bd61 --- /dev/null +++ b/include/cpptrace/exceptions.hpp @@ -0,0 +1,218 @@ +#ifndef CPPTRACE_EXCEPTIONS_HPP +#define CPPTRACE_EXCEPTIONS_HPP + +#include + +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector and others for some +// reason +// 4275 is the same thing but for base classes +#pragma warning(disable: 4251; disable: 4275) +#endif + +namespace cpptrace { + // tracing exceptions: + namespace detail { + // 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() : resolved(false), 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 + const raw_trace& get_raw_trace() const; + stacktrace& get_resolved_trace(); + const stacktrace& get_resolved_trace() const; + private: + void clear(); + }; + + CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth); + CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip = 0); + } + + // Interface for a traced exception object + class CPPTRACE_EXPORT exception : public std::exception { + public: + const char* what() const noexcept override = 0; + 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 + class CPPTRACE_EXPORT lazy_exception : public exception { + mutable detail::lazy_trace_holder trace_holder; + mutable std::string what_string; + + public: + explicit lazy_exception( + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) : trace_holder(std::move(trace)) {} + // std::exception + const char* what() const noexcept override; + // cpptrace::exception + const char* message() const noexcept override; + const stacktrace& trace() const noexcept override; + }; + + class CPPTRACE_EXPORT exception_with_message : public lazy_exception { + mutable std::string user_message; + + public: + explicit exception_with_message( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept : lazy_exception(std::move(trace)), user_message(std::move(message_arg)) {} + + const char* message() const noexcept override; + }; + + class CPPTRACE_EXPORT logic_error : public exception_with_message { + public: + explicit logic_error( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : exception_with_message(std::move(message_arg), std::move(trace)) {} + }; + + class CPPTRACE_EXPORT domain_error : public exception_with_message { + public: + explicit domain_error( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : exception_with_message(std::move(message_arg), std::move(trace)) {} + }; + + class CPPTRACE_EXPORT invalid_argument : public exception_with_message { + public: + explicit invalid_argument( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : exception_with_message(std::move(message_arg), std::move(trace)) {} + }; + + class CPPTRACE_EXPORT length_error : public exception_with_message { + public: + explicit length_error( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : exception_with_message(std::move(message_arg), std::move(trace)) {} + }; + + class CPPTRACE_EXPORT out_of_range : public exception_with_message { + public: + explicit out_of_range( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : exception_with_message(std::move(message_arg), std::move(trace)) {} + }; + + class CPPTRACE_EXPORT runtime_error : public exception_with_message { + public: + explicit runtime_error( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : exception_with_message(std::move(message_arg), std::move(trace)) {} + }; + + class CPPTRACE_EXPORT range_error : public exception_with_message { + public: + explicit range_error( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : exception_with_message(std::move(message_arg), std::move(trace)) {} + }; + + class CPPTRACE_EXPORT overflow_error : public exception_with_message { + public: + explicit overflow_error( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : exception_with_message(std::move(message_arg), std::move(trace)) {} + }; + + class CPPTRACE_EXPORT underflow_error : public exception_with_message { + public: + explicit underflow_error( + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : exception_with_message(std::move(message_arg), std::move(trace)) {} + }; + + class CPPTRACE_EXPORT nested_exception : public lazy_exception { + std::exception_ptr ptr; + mutable std::string message_value; + public: + explicit nested_exception( + const std::exception_ptr& exception_ptr, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept + : lazy_exception(std::move(trace)), ptr(exception_ptr) {} + + const char* message() const noexcept override; + std::exception_ptr nested_ptr() const noexcept; + }; + + class CPPTRACE_EXPORT system_error : public runtime_error { + std::error_code ec; + public: + explicit system_error( + int error_code, + std::string&& message_arg, + raw_trace&& trace = detail::get_raw_trace_and_absorb() + ) noexcept; + const std::error_code& code() const noexcept; + }; + + // [[noreturn]] must come first due to old clang + [[noreturn]] CPPTRACE_EXPORT void rethrow_and_wrap_if_needed(std::size_t skip = 0); +} + +// Exception wrapper utilities +#define CPPTRACE_WRAP_BLOCK(statements) do { \ + try { \ + statements \ + } catch(...) { \ + ::cpptrace::rethrow_and_wrap_if_needed(); \ + } \ + } while(0) + +#define CPPTRACE_WRAP(expression) [&] () -> decltype((expression)) { \ + try { \ + return expression; \ + } catch(...) { \ + ::cpptrace::rethrow_and_wrap_if_needed(1); \ + } \ + } () + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/include/cpptrace/forward.hpp b/include/cpptrace/forward.hpp new file mode 100644 index 00000000..84fb2e82 --- /dev/null +++ b/include/cpptrace/forward.hpp @@ -0,0 +1,18 @@ +#ifndef CPPTRACE_FORWARD_HPP +#define CPPTRACE_FORWARD_HPP + +#include + +namespace cpptrace { + // Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t + using frame_ptr = std::uintptr_t; + + struct raw_trace; + struct object_trace; + struct stacktrace; + + struct object_frame; + struct stacktrace_frame; +} + +#endif diff --git a/include/cpptrace/from_current.hpp b/include/cpptrace/from_current.hpp index 91579992..16ff60b9 100644 --- a/include/cpptrace/from_current.hpp +++ b/include/cpptrace/from_current.hpp @@ -1,7 +1,7 @@ #ifndef CPPTRACE_FROM_CURRENT_HPP #define CPPTRACE_FROM_CURRENT_HPP -#include +#include namespace cpptrace { CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception(); diff --git a/include/cpptrace/io.hpp b/include/cpptrace/io.hpp new file mode 100644 index 00000000..d65bcf98 --- /dev/null +++ b/include/cpptrace/io.hpp @@ -0,0 +1,52 @@ +#ifndef CPPTRACE_IO_HPP +#define CPPTRACE_IO_HPP + +#include + +#include + +#ifndef CPPTRACE_NO_STD_FORMAT + #if __cplusplus >= 202002L + #ifdef __has_include + #if __has_include() + #define CPPTRACE_STD_FORMAT + #include + #endif + #endif + #endif +#endif + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector and others for some +// reason +// 4275 is the same thing but for base classes +#pragma warning(disable: 4251; disable: 4275) +#endif + +namespace cpptrace { + std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame); + std::ostream& operator<<(std::ostream& stream, const stacktrace& trace); +} + +#if defined(CPPTRACE_STD_FORMAT) && defined(__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 + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/include/cpptrace/utils.hpp b/include/cpptrace/utils.hpp new file mode 100644 index 00000000..5ecf736b --- /dev/null +++ b/include/cpptrace/utils.hpp @@ -0,0 +1,48 @@ +#ifndef CPPTRACE_UTILS_HPP +#define CPPTRACE_UTILS_HPP + +#include + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector and others for some +// reason +// 4275 is the same thing but for base classes +#pragma warning(disable: 4251; disable: 4275) +#endif + +namespace cpptrace { + CPPTRACE_EXPORT std::string demangle(const std::string& name); + CPPTRACE_EXPORT std::string get_snippet( + const std::string& path, + std::size_t line, + std::size_t context_size, + bool color = false + ); + CPPTRACE_EXPORT bool isatty(int fd); + + CPPTRACE_EXPORT extern const int stdin_fileno; + CPPTRACE_EXPORT extern const int stderr_fileno; + CPPTRACE_EXPORT extern const int stdout_fileno; + + CPPTRACE_EXPORT void register_terminate_handler(); + + // options: + CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb); + CPPTRACE_EXPORT void enable_inlined_call_resolution(bool enable); + + enum class cache_mode { + // Only minimal lookup tables + prioritize_memory = 0, + // Build lookup tables but don't keep them around between trace calls + hybrid = 1, + // Build lookup tables as needed + prioritize_speed = 2 + }; + + namespace experimental { + CPPTRACE_EXPORT void set_cache_mode(cache_mode mode); + } +} + +#endif diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index ff4ce56c..2315c621 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -1,21 +1,17 @@ #include -#include #include #include #include #include #include -#include #include -#include #include #include #include "symbols/symbols.hpp" #include "unwind/unwind.hpp" #include "demangle/demangle.hpp" -#include "platform/exception_type.hpp" #include "utils/common.hpp" #include "utils/microfmt.hpp" #include "utils/utils.hpp" @@ -425,276 +421,4 @@ namespace cpptrace { bool can_signal_safe_unwind() { return detail::has_safe_unwind(); } - - std::string demangle(const std::string& name) { - return detail::demangle(name); - } - - std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) { - return detail::get_snippet(path, line, context_size, color); - } - - bool isatty(int fd) { - return detail::isatty(fd); - } - - extern const int stdin_fileno = detail::fileno(stdin); - extern const int stdout_fileno = detail::fileno(stdout); - extern const int stderr_fileno = detail::fileno(stderr); - - CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() { - try { // try/catch can never be hit but it's needed to prevent TCO - generate_trace(1).print( - std::cerr, - isatty(stderr_fileno), - true, - "Stack trace to reach terminate handler (most recent call first):" - ); - } catch(...) { - if(!detail::should_absorb_trace_exceptions()) { - throw; - } - } - } - - [[noreturn]] void terminate_handler() { - // TODO: Support std::nested_exception? - try { - auto ptr = std::current_exception(); - if(ptr == nullptr) { - fputs("terminate called without an active exception", stderr); - print_terminate_trace(); - } else { - std::rethrow_exception(ptr); - } - } catch(cpptrace::exception& e) { - microfmt::print( - stderr, - "Terminate called after throwing an instance of {}: {}\n", - demangle(typeid(e).name()), - e.message() - ); - e.trace().print(std::cerr, isatty(stderr_fileno)); - } catch(std::exception& e) { - microfmt::print( - stderr, "Terminate called after throwing an instance of {}: {}\n", demangle(typeid(e).name()), e.what() - ); - print_terminate_trace(); - } catch(...) { - microfmt::print( - stderr, "Terminate called after throwing an instance of {}\n", detail::exception_type_name() - ); - print_terminate_trace(); - } - std::flush(std::cerr); - abort(); - } - - void register_terminate_handler() { - std::set_terminate(terminate_handler); - } - - namespace detail { - std::atomic_bool absorb_trace_exceptions(true); // NOSONAR - std::atomic_bool resolve_inlined_calls(true); // NOSONAR - std::atomic current_cache_mode(cache_mode::prioritize_speed); // NOSONAR - } - - void absorb_trace_exceptions(bool absorb) { - detail::absorb_trace_exceptions = absorb; - } - - void enable_inlined_call_resolution(bool enable) { - detail::resolve_inlined_calls = enable; - } - - namespace experimental { - void set_cache_mode(cache_mode mode) { - detail::current_cache_mode = mode; - } - } - - namespace detail { - bool should_absorb_trace_exceptions() { - return absorb_trace_exceptions; - } - - bool should_resolve_inlined_calls() { - return resolve_inlined_calls; - } - - cache_mode get_cache_mode() { - return current_cache_mode; - } - - CPPTRACE_FORCE_NO_INLINE - raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth) { - try { - return generate_raw_trace(skip + 1, max_depth); - } catch(const std::exception& e) { - if(!detail::should_absorb_trace_exceptions()) { - // TODO: Append to message somehow - std::fprintf( - stderr, - "Cpptrace: Exception occurred while resolving trace in cpptrace::exception object:\n%s\n", - e.what() - ); - } - return raw_trace{}; - } - } - - CPPTRACE_FORCE_NO_INLINE - raw_trace get_raw_trace_and_absorb(std::size_t skip) { - try { // try/catch can never be hit but it's needed to prevent TCO - return get_raw_trace_and_absorb(skip + 1, SIZE_MAX); - } catch(...) { - if(!detail::should_absorb_trace_exceptions()) { - throw; - } - return raw_trace{}; - } - } - - 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); - } - } - 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 - const raw_trace& lazy_trace_holder::get_raw_trace() const { - if(resolved) { - throw std::logic_error( - "cpptrace::detail::lazy_trace_holder::get_resolved_trace called on resolved holder" - ); - } - return trace; - } - stacktrace& lazy_trace_holder::get_resolved_trace() { - if(!resolved) { - raw_trace old_trace = std::move(trace); - *this = lazy_trace_holder(stacktrace{}); - try { - if(!old_trace.empty()) { - resolved_trace = old_trace.resolve(); - } - } catch(const std::exception& e) { - if(!detail::should_absorb_trace_exceptions()) { - // TODO: Append to message somehow? - std::fprintf( - stderr, - "Exception occurred while resolving trace in cpptrace::detail::lazy_trace_holder:\n%s\n", - e.what() - ); - } - } - } - return resolved_trace; - } - const stacktrace& lazy_trace_holder::get_resolved_trace() const { - if(!resolved) { - throw std::logic_error( - "cpptrace::detail::lazy_trace_holder::get_resolved_trace called on unresolved const holder" - ); - } - return resolved_trace; - } - void lazy_trace_holder::clear() { - if(resolved) { - resolved_trace.~stacktrace(); - } else { - trace.~raw_trace(); - } - } - } - - 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(); - } - - system_error::system_error(int error_code, std::string&& message_arg, raw_trace&& trace) noexcept - : runtime_error( - message_arg + ": " + std::error_code(error_code, std::generic_category()).message(), - std::move(trace) - ), - ec(std::error_code(error_code, std::generic_category())) {} - - const std::error_code& system_error::code() const noexcept { - return ec; - } - - const char* nested_exception::message() const noexcept { - if(message_value.empty()) { - try { - std::rethrow_exception(ptr); - } catch(std::exception& e) { - message_value = std::string("Nested exception: ") + e.what(); - } catch(...) { - message_value = "Nested exception holding instance of " + detail::exception_type_name(); - } - } - 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()); - } catch(cpptrace::exception&) { - throw; // already a cpptrace::exception - } catch(...) { - throw nested_exception(std::current_exception(), detail::get_raw_trace_and_absorb(skip + 1)); - } - } } diff --git a/src/exceptions.cpp b/src/exceptions.cpp new file mode 100644 index 00000000..e873d303 --- /dev/null +++ b/src/exceptions.cpp @@ -0,0 +1,186 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "platform/exception_type.hpp" +#include "utils/common.hpp" + +namespace cpptrace { + namespace detail { + 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); + } + } + 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 + const raw_trace& lazy_trace_holder::get_raw_trace() const { + if(resolved) { + throw std::logic_error( + "cpptrace::detail::lazy_trace_holder::get_resolved_trace called on resolved holder" + ); + } + return trace; + } + stacktrace& lazy_trace_holder::get_resolved_trace() { + if(!resolved) { + raw_trace old_trace = std::move(trace); + *this = lazy_trace_holder(stacktrace{}); + try { + if(!old_trace.empty()) { + resolved_trace = old_trace.resolve(); + } + } catch(const std::exception& e) { + if(!detail::should_absorb_trace_exceptions()) { + // TODO: Append to message somehow? + std::fprintf( + stderr, + "Exception occurred while resolving trace in cpptrace::detail::lazy_trace_holder:\n%s\n", + e.what() + ); + } + } + } + return resolved_trace; + } + const stacktrace& lazy_trace_holder::get_resolved_trace() const { + if(!resolved) { + throw std::logic_error( + "cpptrace::detail::lazy_trace_holder::get_resolved_trace called on unresolved const holder" + ); + } + return resolved_trace; + } + void lazy_trace_holder::clear() { + if(resolved) { + resolved_trace.~stacktrace(); + } else { + trace.~raw_trace(); + } + } + + CPPTRACE_FORCE_NO_INLINE + raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth) { + try { + return generate_raw_trace(skip + 1, max_depth); + } catch(const std::exception& e) { + if(!detail::should_absorb_trace_exceptions()) { + // TODO: Append to message somehow + std::fprintf( + stderr, + "Cpptrace: Exception occurred while resolving trace in cpptrace::exception object:\n%s\n", + e.what() + ); + } + return raw_trace{}; + } + } + + CPPTRACE_FORCE_NO_INLINE + raw_trace get_raw_trace_and_absorb(std::size_t skip) { + try { // try/catch can never be hit but it's needed to prevent TCO + return get_raw_trace_and_absorb(skip + 1, SIZE_MAX); + } catch(...) { + if(!detail::should_absorb_trace_exceptions()) { + throw; + } + return raw_trace{}; + } + } + } + + 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(); + } + + system_error::system_error(int error_code, std::string&& message_arg, raw_trace&& trace) noexcept + : runtime_error( + message_arg + ": " + std::error_code(error_code, std::generic_category()).message(), + std::move(trace) + ), + ec(std::error_code(error_code, std::generic_category())) {} + + const std::error_code& system_error::code() const noexcept { + return ec; + } + + const char* nested_exception::message() const noexcept { + if(message_value.empty()) { + try { + std::rethrow_exception(ptr); + } catch(std::exception& e) { + message_value = std::string("Nested exception: ") + e.what(); + } catch(...) { + message_value = "Nested exception holding instance of " + detail::exception_type_name(); + } + } + 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()); + } catch(cpptrace::exception&) { + throw; // already a cpptrace::exception + } catch(...) { + throw nested_exception(std::current_exception(), detail::get_raw_trace_and_absorb(skip + 1)); + } + } +} diff --git a/src/options.cpp b/src/options.cpp new file mode 100644 index 00000000..43facc25 --- /dev/null +++ b/src/options.cpp @@ -0,0 +1,41 @@ +#include + +#include "options.hpp" + +#include + +namespace cpptrace { + namespace detail { + std::atomic_bool absorb_trace_exceptions(true); // NOSONAR + std::atomic_bool resolve_inlined_calls(true); // NOSONAR + std::atomic current_cache_mode(cache_mode::prioritize_speed); // NOSONAR + } + + void absorb_trace_exceptions(bool absorb) { + detail::absorb_trace_exceptions = absorb; + } + + void enable_inlined_call_resolution(bool enable) { + detail::resolve_inlined_calls = enable; + } + + namespace experimental { + void set_cache_mode(cache_mode mode) { + detail::current_cache_mode = mode; + } + } + + namespace detail { + bool should_absorb_trace_exceptions() { + return absorb_trace_exceptions; + } + + bool should_resolve_inlined_calls() { + return resolve_inlined_calls; + } + + cache_mode get_cache_mode() { + return current_cache_mode; + } + } +} diff --git a/src/options.hpp b/src/options.hpp new file mode 100644 index 00000000..ddd6b875 --- /dev/null +++ b/src/options.hpp @@ -0,0 +1,14 @@ +#ifndef OPTIONS_HPP +#define OPTIONS_HPP + +#include + +namespace cpptrace { +namespace detail { + bool should_absorb_trace_exceptions(); + bool should_resolve_inlined_calls(); + cache_mode get_cache_mode(); +} +} + +#endif diff --git a/src/symbols/dwarf/debug_map_resolver.cpp b/src/symbols/dwarf/debug_map_resolver.cpp index 1bc6a6ea..8cd8650c 100644 --- a/src/symbols/dwarf/debug_map_resolver.cpp +++ b/src/symbols/dwarf/debug_map_resolver.cpp @@ -2,7 +2,7 @@ #include "symbols/dwarf/resolver.hpp" -#include +#include #include "symbols/symbols.hpp" #include "utils/common.hpp" #include "utils/error.hpp" diff --git a/src/symbols/dwarf/dwarf.hpp b/src/symbols/dwarf/dwarf.hpp index 72be3564..18ac4e30 100644 --- a/src/symbols/dwarf/dwarf.hpp +++ b/src/symbols/dwarf/dwarf.hpp @@ -1,7 +1,7 @@ #ifndef DWARF_HPP #define DWARF_HPP -#include +#include #include "utils/error.hpp" #include "utils/microfmt.hpp" #include "utils/utils.hpp" diff --git a/src/symbols/dwarf/dwarf_resolver.cpp b/src/symbols/dwarf/dwarf_resolver.cpp index a262812e..277d4f0d 100644 --- a/src/symbols/dwarf/dwarf_resolver.cpp +++ b/src/symbols/dwarf/dwarf_resolver.cpp @@ -2,7 +2,7 @@ #include "symbols/dwarf/resolver.hpp" -#include +#include #include "symbols/dwarf/dwarf.hpp" // has dwarf #includes #include "symbols/symbols.hpp" #include "utils/common.hpp" diff --git a/src/symbols/dwarf/resolver.hpp b/src/symbols/dwarf/resolver.hpp index 0d7c28c1..d366ac5b 100644 --- a/src/symbols/dwarf/resolver.hpp +++ b/src/symbols/dwarf/resolver.hpp @@ -1,7 +1,7 @@ #ifndef SYMBOL_RESOLVER_HPP #define SYMBOL_RESOLVER_HPP -#include +#include #include "symbols/symbols.hpp" #include "platform/platform.hpp" diff --git a/src/symbols/symbols.hpp b/src/symbols/symbols.hpp index 9e25fc04..e9688d8b 100644 --- a/src/symbols/symbols.hpp +++ b/src/symbols/symbols.hpp @@ -1,7 +1,7 @@ #ifndef SYMBOLS_HPP #define SYMBOLS_HPP -#include +#include #include #include diff --git a/src/symbols/symbols_core.cpp b/src/symbols/symbols_core.cpp index 984ef54a..ab7574dc 100644 --- a/src/symbols/symbols_core.cpp +++ b/src/symbols/symbols_core.cpp @@ -1,4 +1,4 @@ -#include +#include #include "symbols/symbols.hpp" diff --git a/src/symbols/symbols_with_addr2line.cpp b/src/symbols/symbols_with_addr2line.cpp index d943ded3..736417f2 100644 --- a/src/symbols/symbols_with_addr2line.cpp +++ b/src/symbols/symbols_with_addr2line.cpp @@ -1,6 +1,6 @@ #ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE -#include +#include #include "symbols/symbols.hpp" #include "utils/common.hpp" #include "utils/microfmt.hpp" diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp index 6e040252..7bb05e2b 100644 --- a/src/symbols/symbols_with_dbghelp.cpp +++ b/src/symbols/symbols_with_dbghelp.cpp @@ -1,6 +1,6 @@ #ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP -#include +#include #include "symbols/symbols.hpp" #include "platform/dbghelp_syminit_manager.hpp" #include "binary/object.hpp" diff --git a/src/symbols/symbols_with_dl.cpp b/src/symbols/symbols_with_dl.cpp index c3049b57..59e61347 100644 --- a/src/symbols/symbols_with_dl.cpp +++ b/src/symbols/symbols_with_dl.cpp @@ -1,6 +1,6 @@ #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL -#include +#include #include "symbols/symbols.hpp" #include "binary/module_base.hpp" diff --git a/src/symbols/symbols_with_libbacktrace.cpp b/src/symbols/symbols_with_libbacktrace.cpp index bdab1691..8ead5921 100644 --- a/src/symbols/symbols_with_libbacktrace.cpp +++ b/src/symbols/symbols_with_libbacktrace.cpp @@ -1,6 +1,6 @@ #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE -#include +#include #include "symbols/symbols.hpp" #include "platform/program_name.hpp" #include "utils/error.hpp" diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index 3a492656..105c58ce 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -2,7 +2,7 @@ #include "symbols/symbols.hpp" -#include +#include #include "dwarf/resolver.hpp" #include "utils/common.hpp" #include "utils/utils.hpp" diff --git a/src/symbols/symbols_with_nothing.cpp b/src/symbols/symbols_with_nothing.cpp index f2afd82c..718fac17 100644 --- a/src/symbols/symbols_with_nothing.cpp +++ b/src/symbols/symbols_with_nothing.cpp @@ -1,6 +1,6 @@ #ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING -#include +#include #include "symbols/symbols.hpp" #include "utils/common.hpp" diff --git a/src/unwind/unwind.hpp b/src/unwind/unwind.hpp index d37e8361..4d8b3dc0 100644 --- a/src/unwind/unwind.hpp +++ b/src/unwind/unwind.hpp @@ -1,7 +1,7 @@ #ifndef UNWIND_HPP #define UNWIND_HPP -#include +#include #include #include diff --git a/src/unwind/unwind_with_dbghelp.cpp b/src/unwind/unwind_with_dbghelp.cpp index eb90ea52..e7ec4679 100644 --- a/src/unwind/unwind_with_dbghelp.cpp +++ b/src/unwind/unwind_with_dbghelp.cpp @@ -1,6 +1,6 @@ #ifdef CPPTRACE_UNWIND_WITH_DBGHELP -#include +#include #include "unwind/unwind.hpp" #include "utils/common.hpp" #include "utils/utils.hpp" diff --git a/src/unwind/unwind_with_winapi.cpp b/src/unwind/unwind_with_winapi.cpp index 445519d8..75ec5a1d 100644 --- a/src/unwind/unwind_with_winapi.cpp +++ b/src/unwind/unwind_with_winapi.cpp @@ -1,6 +1,6 @@ #ifdef CPPTRACE_UNWIND_WITH_WINAPI -#include +#include #include "unwind/unwind.hpp" #include "utils/common.hpp" #include "utils/utils.hpp" diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 00000000..ce2917f5 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,79 @@ +#include +#include + +#include + +#include "demangle/demangle.hpp" +#include "snippets/snippet.hpp" +#include "utils/utils.hpp" +#include "platform/exception_type.hpp" + +namespace cpptrace { + std::string demangle(const std::string& name) { + return detail::demangle(name); + } + + std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) { + return detail::get_snippet(path, line, context_size, color); + } + + bool isatty(int fd) { + return detail::isatty(fd); + } + + extern const int stdin_fileno = detail::fileno(stdin); + extern const int stdout_fileno = detail::fileno(stdout); + extern const int stderr_fileno = detail::fileno(stderr); + + CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() { + try { // try/catch can never be hit but it's needed to prevent TCO + generate_trace(1).print( + std::cerr, + isatty(stderr_fileno), + true, + "Stack trace to reach terminate handler (most recent call first):" + ); + } catch(...) { + if(!detail::should_absorb_trace_exceptions()) { + throw; + } + } + } + + [[noreturn]] void terminate_handler() { + // TODO: Support std::nested_exception? + try { + auto ptr = std::current_exception(); + if(ptr == nullptr) { + fputs("terminate called without an active exception", stderr); + print_terminate_trace(); + } else { + std::rethrow_exception(ptr); + } + } catch(cpptrace::exception& e) { + microfmt::print( + stderr, + "Terminate called after throwing an instance of {}: {}\n", + demangle(typeid(e).name()), + e.message() + ); + e.trace().print(std::cerr, isatty(stderr_fileno)); + } catch(std::exception& e) { + microfmt::print( + stderr, "Terminate called after throwing an instance of {}: {}\n", demangle(typeid(e).name()), e.what() + ); + print_terminate_trace(); + } catch(...) { + microfmt::print( + stderr, "Terminate called after throwing an instance of {}\n", detail::exception_type_name() + ); + print_terminate_trace(); + } + std::flush(std::cerr); + abort(); + } + + void register_terminate_handler() { + std::set_terminate(terminate_handler); + } +} diff --git a/src/utils/common.hpp b/src/utils/common.hpp index db2518cd..faf6779b 100644 --- a/src/utils/common.hpp +++ b/src/utils/common.hpp @@ -1,9 +1,10 @@ #ifndef COMMON_HPP #define COMMON_HPP -#include +#include #include "platform/platform.hpp" +#include "options.hpp" #include @@ -35,10 +36,6 @@ namespace detail { "", false }; - - bool should_absorb_trace_exceptions(); - bool should_resolve_inlined_calls(); - cache_mode get_cache_mode(); } } diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index ea2637d0..e4bf228c 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -17,8 +17,6 @@ #include "utils/common.hpp" #include "utils/error.hpp" - - namespace cpptrace { namespace detail { bool isatty(int fd);