Commit 33489ea5 authored by David Frank's avatar David Frank
Browse files

Refactor Error class

Backtraces have sadly been broken from some time, and it was a major
pain to find the source (still searching). This commit removes all the
self rolled code with the backward-cpp library
(https://github.com/bombela/backward-cpp)

This library features a much more comprehensive set of options, pretty
printing and deals with dependencies for us.
parent 0fa4cc78
Pipeline #747140 failed with stages
in 15 minutes and 17 seconds
...@@ -41,6 +41,7 @@ set(ELSA_SANITIZER ...@@ -41,6 +41,7 @@ set(ELSA_SANITIZER
set(ELSA_CUDA_ARCH_TYPE "auto" CACHE STRING "Set CUDA architectures") set(ELSA_CUDA_ARCH_TYPE "auto" CACHE STRING "Set CUDA architectures")
option(SYSTEM_EIGEN "Build elsa using the system eigen installation" OFF) option(SYSTEM_EIGEN "Build elsa using the system eigen installation" OFF)
option(SYSTEM_BACKWARD "Build elsa using the system backward-cpp installation" OFF)
option(SYSTEM_SPDLOG "Build elsa using the system spdlog installation" OFF) option(SYSTEM_SPDLOG "Build elsa using the system spdlog installation" OFF)
# ------------ general setup ----------- # ------------ general setup -----------
...@@ -78,10 +79,12 @@ set(INSTALL_CONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/elsa) ...@@ -78,10 +79,12 @@ set(INSTALL_CONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/elsa)
# only add the dependencies if elsa is stand-alone # only add the dependencies if elsa is stand-alone
if(ELSA_MASTER_PROJECT) if(ELSA_MASTER_PROJECT)
if(SYSTEM_EIGEN) if(SYSTEM_EIGEN)
message(STATUS "Using system-wide Eigen library...") message(STATUS "Using system-wide Eigen library...")
find_package(Eigen3 REQUIRED) find_package(Eigen3 REQUIRED)
else() else()
# setup custom Eigen Library # setup custom Eigen Library
# once there's a proper release, # once there's a proper release,
# the system-eigen has to be a drop-in replacement for the git-version # the system-eigen has to be a drop-in replacement for the git-version
...@@ -153,6 +156,13 @@ if(ELSA_MASTER_PROJECT) ...@@ -153,6 +156,13 @@ if(ELSA_MASTER_PROJECT)
# SPDLOG_INSTALL will manage install and includedir setup # SPDLOG_INSTALL will manage install and includedir setup
# to $CMAKE_INSTALL_INCLUDEDIR/spdlog # to $CMAKE_INSTALL_INCLUDEDIR/spdlog
endif() endif()
if(SYSTEM_BACKWARD)
message(STATUS "Using system-wide backward-cpp library...")
find_package(Eigen3 REQUIRED)
else()
CPMAddPackage(NAME backward GIT_REPOSITORY https://github.com/bombela/backward-cpp VERSION 1.6)
endif()
# setup Dnnl library # setup Dnnl library
find_package(Dnnl) find_package(Dnnl)
......
...@@ -22,7 +22,7 @@ macro(ADD_ELSA_MODULE elsa_module_name module_headers module_sources) ...@@ -22,7 +22,7 @@ macro(ADD_ELSA_MODULE elsa_module_name module_headers module_sources)
set(ELSA_MODULE_EXPORT_TARGET elsa_${elsa_module_name}Targets) set(ELSA_MODULE_EXPORT_TARGET elsa_${elsa_module_name}Targets)
# build the module library # build the module library
add_library(${ELSA_MODULE_TARGET_NAME} ${module_headers} ${module_sources}) add_library(${ELSA_MODULE_TARGET_NAME} ${module_headers} ${module_sources} ${BACKWARD_ENABLE})
add_library(elsa::${ELSA_MODULE_NAME} ALIAS ${ELSA_MODULE_TARGET_NAME}) add_library(elsa::${ELSA_MODULE_NAME} ALIAS ${ELSA_MODULE_TARGET_NAME})
# Add public dependencies # Add public dependencies
...@@ -31,6 +31,9 @@ macro(ADD_ELSA_MODULE elsa_module_name module_headers module_sources) ...@@ -31,6 +31,9 @@ macro(ADD_ELSA_MODULE elsa_module_name module_headers module_sources)
target_link_libraries(${ELSA_MODULE_TARGET_NAME} PRIVATE "${ADD_MODULE_PRIVATE_DPES}") target_link_libraries(${ELSA_MODULE_TARGET_NAME} PRIVATE "${ADD_MODULE_PRIVATE_DPES}")
# require C++17 # require C++17
target_compile_features(${ELSA_MODULE_TARGET_NAME} PUBLIC cxx_std_17) target_compile_features(${ELSA_MODULE_TARGET_NAME} PUBLIC cxx_std_17)
# Add Backward dependecy
add_backward(${ELSA_MODULE_TARGET_NAME})
# set -fPIC and -fvisibility=hidden # set -fPIC and -fvisibility=hidden
set_target_properties(${ELSA_MODULE_TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON set_target_properties(${ELSA_MODULE_TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON
CXX_VISIBILITY_PRESET hidden) CXX_VISIBILITY_PRESET hidden)
...@@ -43,7 +46,7 @@ macro(ADD_ELSA_MODULE elsa_module_name module_headers module_sources) ...@@ -43,7 +46,7 @@ macro(ADD_ELSA_MODULE elsa_module_name module_headers module_sources)
if(ELSA_MASTER_PROJECT) if(ELSA_MASTER_PROJECT)
set_target_warnings(${ELSA_MODULE_TARGET_NAME} LEVEL ${ELSA_WARNINGS_LEVEL}) set_target_warnings(${ELSA_MODULE_TARGET_NAME} LEVEL ${ELSA_WARNINGS_LEVEL})
endif() endif()
# build the tests (if enabled) # build the tests (if enabled)
if(ELSA_TESTING AND NOT ADD_MODULE_NO_TESTS) if(ELSA_TESTING AND NOT ADD_MODULE_NO_TESTS)
add_subdirectory(tests) add_subdirectory(tests)
...@@ -74,7 +77,7 @@ macro(ELSA_DOCTEST name) ...@@ -74,7 +77,7 @@ macro(ELSA_DOCTEST name)
# set the name for the test once # set the name for the test once
set(_testname test_${name}) set(_testname test_${name})
# create the test executable # create the test executable
add_executable(${_testname} EXCLUDE_FROM_ALL ${_testname}.cpp test_docmain.cpp) add_executable(${_testname} EXCLUDE_FROM_ALL ${_testname}.cpp test_docmain.cpp ${BACKWARD_ENABLE})
# add catch and the corresponding elsa library # add catch and the corresponding elsa library
target_link_libraries( target_link_libraries(
${_testname} PRIVATE doctest::doctest ${ELSA_MODULE_TARGET_NAME} elsa::test_routines ${_testname} PRIVATE doctest::doctest ${ELSA_MODULE_TARGET_NAME} elsa::test_routines
...@@ -86,6 +89,12 @@ macro(ELSA_DOCTEST name) ...@@ -86,6 +89,12 @@ macro(ELSA_DOCTEST name)
target_include_directories(${_testname} PRIVATE ${CMAKE_SOURCE_DIR}/elsa/test_routines/) target_include_directories(${_testname} PRIVATE ${CMAKE_SOURCE_DIR}/elsa/test_routines/)
# Improve compile times for tests # Improve compile times for tests
target_compile_definitions(${_testname} PRIVATE DOCTEST_CONFIG_SUPER_FAST_ASSERTS) target_compile_definitions(${_testname} PRIVATE DOCTEST_CONFIG_SUPER_FAST_ASSERTS)
set_target_properties(${ELSA_MODULE_TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON
CXX_VISIBILITY_PRESET hidden)
# Add Backward dependecy
add_backward(${_testname})
# Set output directory to bin/tests/<module> # Set output directory to bin/tests/<module>
set_target_properties( set_target_properties(
${_testname} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/tests/${ELSA_MODULE_TARGET_NAME}" ${_testname} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/tests/${ELSA_MODULE_TARGET_NAME}"
......
#include "Backtrace.h"
#include <cxxabi.h>
#include <dlfcn.h>
#include <execinfo.h>
#include <sstream>
namespace elsa::detail
{
std::string symbolDemangle(const char* symbol)
{
int status;
char* buf = abi::__cxa_demangle(symbol, nullptr, nullptr, &status);
if (status != 0) {
return symbol;
} else {
std::string result{buf};
::free(buf);
return result;
}
}
std::string addrToString(const void* addr)
{
std::ostringstream out;
out << "[" << addr << "]";
return out.str();
}
std::string symbolName(const void* addr, bool require_exact_addr, bool no_pure_addrs)
{
Dl_info addr_info;
if (::dladdr(addr, &addr_info) == 0) {
// dladdr has... failed.
return no_pure_addrs ? "" : addrToString(addr);
} else {
size_t symbol_offset =
reinterpret_cast<size_t>(addr) - reinterpret_cast<size_t>(addr_info.dli_saddr);
if (addr_info.dli_sname == nullptr or (symbol_offset != 0 and require_exact_addr)) {
return no_pure_addrs ? "" : addrToString(addr);
}
if (symbol_offset == 0) {
// this is our symbol name.
return symbolDemangle(addr_info.dli_sname);
} else {
std::ostringstream out;
out << symbolDemangle(addr_info.dli_sname) << "+0x" << std::hex << symbol_offset
<< std::dec;
return out.str();
}
}
}
} // namespace elsa::detail
namespace elsa
{
void Backtrace::analyze()
{
std::vector<void*> buffer{32};
// increase buffer size until it's enough
while (true) {
int buff_size = static_cast<int>(buffer.size());
auto elements = static_cast<size_t>(::backtrace(buffer.data(), buff_size));
if (elements < buffer.size()) {
buffer.resize(elements);
break;
}
buffer.resize(buffer.size() * 2);
}
for (void* element : buffer) {
this->stack_addrs.push_back(element);
}
}
void Backtrace::getSymbols(const std::function<void(const backtrace_symbol*)>& cb,
bool reversed) const
{
backtrace_symbol symbol;
if (reversed) {
for (size_t idx = this->stack_addrs.size(); idx-- > 0;) {
void* pc = this->stack_addrs[idx];
symbol.functionname = detail::symbolName(pc, false, true);
symbol.pc = pc;
cb(&symbol);
}
} else {
for (void* pc : this->stack_addrs) {
symbol.functionname = detail::symbolName(pc, false, true);
symbol.pc = pc;
cb(&symbol);
}
}
}
void Backtrace::trimToCurrentStackFrame()
{
Backtrace current;
current.analyze();
while (not current.stack_addrs.empty() and not this->stack_addrs.empty()) {
if (this->stack_addrs.back() != current.stack_addrs.back()) {
break;
}
this->stack_addrs.pop_back();
current.stack_addrs.pop_back();
}
}
} // namespace elsa
#pragma once
#include <string>
#include <functional>
#include <vector>
namespace elsa::detail
{
/**
* Demangles a symbol name.
*
* On failure, the mangled symbol name is returned.
*/
std::string symbolDemangle(const char* symbol);
/**
* Convert a pointer address to string.
*/
std::string addrToString(const void* addr);
/**
* Return the demangled symbol name for a given code address.
*/
std::string symbolName(const void* addr, bool require_exact_addr = true,
bool no_pure_addrs = false);
} // namespace elsa::detail
namespace elsa
{
/**
* A single symbol, as determined from a program counter, and returned by
* Backtrace::getSymbols().
*/
struct backtrace_symbol {
std::string functionname; // empty if unknown
void* pc; // nullptr if unknown
};
/**
* Provide execution backtrace information through getSymbols().
*/
class Backtrace
{
public:
Backtrace() = default;
virtual ~Backtrace() = default;
/**
* Analyzes the current stack, and stores the program counter values in
* this->stack_addrs.
*/
void analyze();
/**
* Provide the names for all stack frames via the callback.
*
* The most recent call is returned last (alike Python).
*
* @param cb
* is called for every symbol in the backtrace,
* starting with the top-most frame.
* @param reversed
* if true, the most recent call is given last.
*/
void getSymbols(const std::function<void(const backtrace_symbol*)>& cb,
bool reversed = true) const;
/**
* Removes all the lower frames that are also present
* in the current stack.
*
* Designed to be used in catch clauses,
* to simulate stack trace collection
* from throw to catch, instead of from throw to the process entry point.
*/
void trimToCurrentStackFrame();
protected:
/**
* All program counters of this backtrace.
*/
std::vector<void*> stack_addrs;
};
} // namespace elsa
# list all the headers of the module # list all the headers of the module
set(MODULE_HEADERS set(MODULE_HEADERS
elsaDefines.h elsaDefines.h
Backtrace.h
Cloneable.h Cloneable.h
Utilities/Badge.hpp Utilities/Badge.hpp
Utilities/DataContainerFormatter.hpp Utilities/DataContainerFormatter.hpp
...@@ -32,7 +31,6 @@ set(MODULE_HEADERS ...@@ -32,7 +31,6 @@ set(MODULE_HEADERS
# list all the code files of the module # list all the code files of the module
set(MODULE_SOURCES set(MODULE_SOURCES
Backtrace.cpp
Descriptors/DataDescriptor.cpp Descriptors/DataDescriptor.cpp
Descriptors/VolumeDescriptor.cpp Descriptors/VolumeDescriptor.cpp
Descriptors/PlanarDetectorDescriptor.cpp Descriptors/PlanarDetectorDescriptor.cpp
......
...@@ -2,145 +2,49 @@ ...@@ -2,145 +2,49 @@
#include <sstream> #include <sstream>
#include "Backtrace.h" #include "backward.hpp"
namespace elsa namespace elsa
{ {
constexpr const char* runtime_error_message = "polymorphic elsa error, catch by reference!"; namespace detail
Error::Error(std::string msg, bool generate_backtrace, bool store_cause)
: std::runtime_error{runtime_error_message}, backtrace{nullptr}, msg{std::move(msg)}
{
if (generate_backtrace) {
this->backtrace = std::make_shared<Backtrace>();
this->backtrace->analyze();
}
if (store_cause) {
this->storeCause();
}
}
std::string Error::str() const { return this->msg; }
const char* Error::what() const noexcept
{
this->what_cache = this->str();
return this->what_cache.c_str();
}
void Error::storeCause()
{
if (not std::current_exception()) {
return;
}
try {
throw;
} catch (Error& cause) {
cause.trimBacktrace();
this->cause = std::current_exception();
} catch (...) {
this->cause = std::current_exception();
}
}
void Error::trimBacktrace()
{
if (this->backtrace) {
this->backtrace->trimToCurrentStackFrame();
}
}
void Error::rethrowCause() const
{ {
if (this->cause) { static std::string get_trace(std::string&& msg)
std::rethrow_exception(this->cause); {
} namespace bw = backward;
}
std::string Error::typeName() const { return detail::symbolDemangle(typeid(*this).name()); }
Backtrace* Error::getBacktrace() const { return this->backtrace.get(); } std::ostringstream stream;
const std::string& Error::getMsg() const { return this->msg; } stream << msg << "\n\n";
std::ostream& operator<<(std::ostream& os, const Error& e) bw::StackTrace stackTrace;
{ bw::TraceResolver resolver;
// output the exception cause stackTrace.load_here();
bool had_a_cause = true; resolver.load_stacktrace(stackTrace);
try {
e.rethrowCause();
had_a_cause = false;
} catch (Error& cause) {
os << cause << std::endl;
} catch (std::exception& cause) {
os << detail::symbolDemangle(typeid(cause).name()) << ": " << cause.what() << std::endl;
}
if (had_a_cause) { bw::Printer printer;
os << std::endl printer.color_mode = bw::ColorMode::always; // I always want pretty colors :^)
<< "The above exception was the direct cause " printer.print(stackTrace, stream);
"of the following exception:"
<< std::endl
<< std::endl;
}
// output the exception backtrace return stream.str();
auto* bt = e.getBacktrace();
if (bt != nullptr) {
os << *bt;
} else {
os << "origin:" << std::endl;
} }
os << e.typeName() << ":" << std::endl; BaseError::BaseError(std::string msg) : std::runtime_error(get_trace(std::move(msg))) {}
os << e.str();
return os;
}
/** std::ostream& operator<<(std::ostream& os, const BaseError& err)
* Prints a backtrace_symbol object. {
*/ os << err.what();
std::ostream& operator<<(std::ostream& os, const backtrace_symbol& bt_sym) return os;
{
// imitate the looks of a Python traceback.
os << " -> ";
if (bt_sym.functionname.empty()) {
os << '?';
} else {
os << bt_sym.functionname;
}
if (bt_sym.pc != nullptr) {
os << " " << detail::addrToString(bt_sym.pc);
} }
} // namespace detail
return os; Error::Error(const std::string& msg) : BaseError(msg) {}
}
/**
* Prints an entire Backtrace object.
*/
std::ostream& operator<<(std::ostream& os, const Backtrace& bt)
{
// imitate the looks of a Python traceback.
os << "Traceback (most recent call last):" << std::endl;
bt.getSymbols([&os](const backtrace_symbol* symbol) { os << *symbol << std::endl; }, true);
return os;
}
InternalError::InternalError(const std::string& msg) : Error{msg} {} InternalError::InternalError(const std::string& msg) : BaseError(msg) {}
LogicError::LogicError(const std::string& msg) : Error{msg} {} LogicError::LogicError(const std::string& msg) : BaseError(msg) {}
InvalidArgumentError::InvalidArgumentError(const std::string& msg) : Error{msg} {} InvalidArgumentError::InvalidArgumentError(const std::string& msg) : BaseError(msg) {}
BadCastError::BadCastError(const std::string& msg) : Error{msg} {} BadCastError::BadCastError(const std::string& msg) : BaseError(msg) {}
} // namespace elsa } // namespace elsa
...@@ -9,121 +9,32 @@ ...@@ -9,121 +9,32 @@
namespace elsa namespace elsa
{ {
// fwd-decls for Backtrace.h namespace detail
struct backtrace_symbol;
class Backtrace;
/**
* Base exception for every error that occurs in elsa.
*
* Usage:
* try {
* throw elsa::Error{"some info"};
* }
* catch (elsa::Error &err) {
* std::cout << "\x1b[31;1merror:\x1b[m\n"
* << err << std::endl;
* }
*
*/
class Error : public std::runtime_error
{ {
public:
Error(std::string msg, bool generate_backtrace = true, bool store_cause = true);
~Error() override = default;
/**
* String representation of this exception, as
* specialized by a child exception.
*/
virtual std::string str() const;
/**
* Returns the message's content.
*/
const char* what() const noexcept override;
/** /**
* Stores a pointer to the currently-handled exception in this->cause. * @brief Base exception/error class relying on backward-cpp to retrieve the stacktrace on
* error
*/ */
void storeCause(); class BaseError : public std::runtime_error
{
public:
BaseError(std::string msg);
};
/** std::ostream& operator<<(std::ostream& os, const BaseError& err);
* Calls this->backtrace->trimToCurrentStackFrame(),
* if this->backtrace is not nullptr.
*
* Designed to be used in catch clauses, to strip away all those
* unneeded symbols from program init upwards.
*/
void trimBacktrace();
/**
* Re-throws the exception cause, if the exception has one.
* Otherwise, does nothing.
*
* Use this when handling the exception, to handle the cause.
*/
void rethrowCause() const;
/**
* Get the type name of of the exception.
* Use it to display the name of a child exception.