#ifndef ARBUTILS_EXCEPTION_HPP #define ARBUTILS_EXCEPTION_HPP #include #include #include #include "ErrorHelpers.hpp" #if defined(__clang__) #include #else #include #endif #if PRETTYTRACES && !WINDOWS #define BACKWARD_HAS_DW 1 #endif #include #if defined(__clang__) namespace std { using source_location = std::experimental::source_location; } #endif static constexpr const char* non_null file_name(const char* non_null path) { const char* file = path; while (*path != 0) { if (*path++ == '/') { file = path; } } return file; } namespace ArbUt { /// @brief Implementation of std::logic_error that gives a stack trace when thrown. class Exception : std::logic_error { backward::StackTrace _stack; public: /// @brief Throw an exception with specified message. explicit Exception(const std::string& msg) : std::logic_error(msg), _stack({}) { _stack.load_here(32); #if DEBUG _stack.skip_n_firsts(2); #else _stack.skip_n_firsts(0); #endif } /// @brief Copy operator. Exception(const Exception& e) noexcept : std::logic_error(e.what()), _stack(e._stack) {} template /// @brief Throw a nicely formatted exception. The exception message will have the file name, line number, /// message, along with any optional parameters passed. [[noreturn]] static void Throw(const std::string& expression, const std::source_location& location, const Args... args) { std::stringstream error; error << "[" << file_name(location.file_name()) << ":" << location.line() << "] \""; error << expression; using List = int[]; (void)List{0, ((void)(error << args), 0)...}; error << "\""; throw Exception(error.str()); } /// @brief Returns the error message of the exception. [[nodiscard]] const char* non_null what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW final { return logic_error::what(); } /// @brief Returns the stacktrace of the exception. /// @param depth The max number of stacks it should retrieve. /// @param include_addr Whether or not the address should be shown if the line information could not be /// retrieved. [[nodiscard]] std::string GetStacktrace([[maybe_unused]] size_t depth = 6, [[maybe_unused]] bool include_addr = true) const { return BuildStacktraceFromStack(_stack, depth, include_addr); } /// @brief Builds a string from a given stack. /// @param stack The stack to build the string from. /// @param depth The max number of stacks it should retrieve. /// @param include_addr Whether or not the address should be shown if the line information could not be /// retrieved. static std::string BuildStacktraceFromStack(const backward::StackTrace& stack, size_t depth = 6, bool include_addr = true) { if (stack.size() == 0) { return "No stack trace could be retrieved."; } backward::TraceResolver tr; backward::SnippetFactory snippetFactory; tr.load_stacktrace(stack); std::stringstream ss; ss << "Stacktrace with depth " << depth << ": " << std::endl; size_t framesAppended = 0; for (size_t i = 0; i < stack.size() && framesAppended < depth; ++i) { backward::ResolvedTrace trace = tr.resolve(stack[i]); if (trace.source.filename.empty()) { AppendNoSourceStack(ss, trace, include_addr); framesAppended++; } else if (i != 0) { AppendSourceStack(ss, trace.source, snippetFactory); framesAppended++; } for (auto& t : trace.inliners) { AppendSourceStack(ss, t, snippetFactory); framesAppended++; if (framesAppended >= depth) break; } } ss << "--- End of Stack ---"; return ss.str(); } private: static void AppendNoSourceStack(std::stringstream& ss, const backward::ResolvedTrace& trace, bool include_addr) { auto objectName = (strrchr(trace.object_filename.c_str(), '/') ? strrchr(trace.object_filename.c_str(), '/') + 1 : trace.object_filename.c_str()); auto function = trace.object_function; if (function.length() == 0) { function = "[No function name]"; } if (strcmp(objectName, "") == 0) { objectName = "[No object name]"; } ss << objectName; if (include_addr) { ss << "[" << trace.addr << "]"; } ss << " " << function << std::endl; } static void AppendSourceStack(std::stringstream& ss, const backward::ResolvedTrace::SourceLoc& source, backward::SnippetFactory& snippetFactory) { auto fileName = (strrchr(source.filename.c_str(), '/') ? strrchr(source.filename.c_str(), '/') + 1 : source.filename.c_str()); if (strlen(fileName) == 0){ return; } auto snippetSearch = snippetFactory.get_snippet(source.filename, source.line, 1); if (snippetSearch.empty()) { ss << fileName << "[" << source.line << "]" << std::endl; return; } auto snippet = snippetSearch[0].second; size_t p = snippet.find_first_not_of(" \t"); snippet.erase(0, p); p = snippet.find_last_not_of(" \t"); if (std::string::npos != p) snippet.erase(p + 1); ss << fileName << "[" << source.line << "] " << snippet << std::endl; } }; } #define THROW(message, ...) \ { ArbUt::Exception::Throw(message, std::source_location::current() __VA_OPT__(, ) __VA_ARGS__); } #define NOT_REACHABLE THROW("Not reachable"); #define NOT_IMPLEMENTED THROW("Not implemented"); #endif // ARBUTILS_EXCEPTION_HPP