Arbutils/src/Exception.hpp

168 lines
6.6 KiB
C++

#ifndef ARBUTILS_EXCEPTION_HPP
#define ARBUTILS_EXCEPTION_HPP
#include <cstring>
#include <stdexcept>
#include <string>
#include "ErrorHelpers.hpp"
#if defined(__clang__)
#include <experimental/source_location>
#else
#include <source_location>
#endif
#if PRETTYTRACES && !WINDOWS
#define BACKWARD_HAS_DW 1
#endif
#include <backward.hpp>
#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 <typename... Args>
/// @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