Created
December 4, 2019 18:48
-
-
Save travisdowns/eeb5845cd4c1c0380a896a1d50eb854d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* cxxstacks.hpp | |
*/ | |
#ifndef CXXSTACKS_HPP_ | |
#define CXXSTACKS_HPP_ | |
#include <string.h> | |
#include <string> | |
#include "backward-cpp/backward.hpp" | |
/** | |
* The maximum number of frames we will attempt to capture at exception time: | |
* frames beyond this will be omitted. | |
*/ | |
#define CXXSTACKS_MAX_FRAMES 256 | |
namespace cxxstack { | |
using namespace backward; | |
class TraceFormatter { | |
public: | |
virtual std::string formatFrame(size_t frame_num, const ResolvedTrace &rt) const = 0; | |
virtual ~TraceFormatter() {} | |
}; | |
/** | |
* A default formatter that prints the stacks in some reasonable format. | |
*/ | |
class DefaultFormatter : public TraceFormatter { | |
virtual std::string formatFrame(size_t frame_num, const ResolvedTrace &trace) const override { | |
(void)frame_num; | |
const std::string& function = trace.source.function.empty() ? trace.object_function : trace.source.function; | |
std::stringstream ss; | |
addFrame(ss, function, trace.source, trace.object_filename); | |
ss << " [" << trace.addr << "]" << std::endl; | |
for (const auto &loc : trace.inliners) { | |
addFrame(ss, loc.function, loc, trace.object_filename); | |
ss << " (inlined)" << std::endl; | |
} | |
return ss.str(); | |
} | |
static void addFrame(std::stringstream& ss, const std::string& function, const ResolvedTrace::SourceLoc &loc, const std::string& binaryName) { | |
ss << "\tat " << abbreviateFunction(function) << formatSourceLoc(loc) << " in " << extractFilename(binaryName); | |
} | |
/** | |
* C++ function names get nasty long especially with templates parameters (which may themselves be templates, etc). | |
* Try to cut down on the length by removing the outermost template arguments. | |
*/ | |
static std::string abbreviateFunction(const std::string &fname) { | |
if (fname.find('<') == std::string::npos) { | |
return fname; | |
} | |
std::string trimmed; | |
int nesting = 0; | |
for (char c : fname) { | |
if (c == '>') { | |
nesting--; | |
} | |
if (nesting == 0) { | |
trimmed += c; | |
} | |
if (c == '<') { | |
nesting++; | |
} | |
} | |
return trimmed; | |
} | |
static std::string extractFilename(const std::string &path) { | |
size_t last_slash = path.find_last_of('/'); | |
return last_slash == std::string::npos ? path : path.substr(last_slash + 1); | |
} | |
static std::string formatSourceLoc(const ResolvedTrace::SourceLoc &loc) { | |
if (!loc.filename.empty() || loc.line != 0) { | |
return std::string(" (") + extractFilename(loc.filename) + ":" + std::to_string(loc.line) + ")"; | |
} else { | |
return ""; | |
} | |
} | |
}; | |
template <typename B> | |
class stacked_exception : public B { | |
std::string name; | |
StackTrace st; | |
mutable char* whatmsg; | |
protected: | |
/** | |
* The name of this exception, by default the name of the underlying (base) exception class. | |
*/ | |
virtual std::string exception_name() const noexcept { | |
return name; | |
} | |
public: | |
explicit stacked_exception(const std::string& msg, const std::string& name = "") | |
: B(msg), name{name}, whatmsg(nullptr) | |
{ | |
st.load_here(CXXSTACKS_MAX_FRAMES); | |
} | |
virtual const char* what() const noexcept { | |
if (whatmsg) { | |
return whatmsg; | |
} | |
const std::string& name = exception_name(); | |
std::string msg; | |
msg += (name.empty() ? "" : name + " : ") + B::what() + "\n"; | |
msg += stack_trace(); | |
try { | |
whatmsg = new char[msg.length() + 1]; | |
memcpy(whatmsg, msg.c_str(), msg.length() + 1); | |
} catch (std::bad_alloc&) { | |
return "stacked_exception: allocation failed"; | |
} | |
return whatmsg; | |
} | |
std::string stack_trace() const noexcept { | |
return stack_trace(DefaultFormatter{}); | |
} | |
virtual std::string stack_trace(const TraceFormatter& formatter) const noexcept | |
{ | |
TraceResolver resolver; | |
resolver.load_stacktrace(st); | |
std::string full_trace; | |
for (size_t i = 0; i < st.size(); ++i) { | |
ResolvedTrace trace = resolver.resolve(st[i]); | |
if (!skipFrame(trace)) { | |
full_trace += formatter.formatFrame(i, trace); | |
} | |
} | |
return full_trace; | |
} | |
/** | |
* We want to skip some frames: | |
* 1) The first few frames (the exact number depends on the optimization level) which are in the guts of | |
* the cxxstacks and backward implementation and don't have anything to do with user code. | |
* 2) The last frame which has IP = -1 and no other details (usually precedes _start). | |
*/ | |
static bool skipFrame(const ResolvedTrace& t) { | |
for (auto impl_string : {"backward::details::unwind", "backward::StackTraceImpl", "cxxstack"}) { | |
if (t.object_function.find(impl_string) != std::string::npos) { | |
return true; | |
} | |
} | |
return t.addr == (void *)-1; | |
} | |
virtual ~stacked_exception() { | |
delete[] whatmsg; | |
} | |
}; | |
struct logic_error : public stacked_exception<std::logic_error> { | |
explicit logic_error(const std::string& msg) : stacked_exception<std::logic_error>(msg, "std::logic_error") {} | |
}; | |
} // namespace cxxstack | |
#endif /* CXXSTACKS_HPP_ */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment