Basic source code snippet system (#98)
This commit is contained in:
parent
736643358b
commit
d99f1745d2
@ -221,6 +221,7 @@ target_sources(
|
||||
src/unwind/unwind_with_unwind.cpp
|
||||
src/unwind/unwind_with_winapi.cpp
|
||||
src/unwind/unwind_with_dbghelp.cpp
|
||||
src/snippets/snippet.cpp
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
|
||||
@ -251,6 +251,9 @@ namespace cpptrace {
|
||||
`cpptrace::demangle` provides a helper function for name demangling, since it has to implement that helper internally
|
||||
anyways.
|
||||
|
||||
`cpptrace::get_snippet` gets a text snippet, if possible, from for the given source file for +/- `context_size` lines
|
||||
around `line`.
|
||||
|
||||
`cpptrace::isatty` and the fileno definitions are useful for deciding whether to use color when printing stack traces.
|
||||
|
||||
`cpptrace::register_terminate_handler()` is a helper function to set a custom `std::terminate` handler that prints a
|
||||
@ -259,6 +262,12 @@ stack trace from a cpptrace exception (more info below) and otherwise behaves li
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
std::string demangle(const std::string& name);
|
||||
std::string get_snippet(
|
||||
const std::string& path,
|
||||
std::size_t line,
|
||||
std::size_t context_size,
|
||||
bool color = false
|
||||
);
|
||||
bool isatty(int fd);
|
||||
|
||||
extern const int stdin_fileno;
|
||||
|
||||
@ -179,6 +179,9 @@ namespace cpptrace {
|
||||
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;
|
||||
@ -194,6 +197,7 @@ namespace cpptrace {
|
||||
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();
|
||||
};
|
||||
|
||||
@ -236,6 +240,12 @@ namespace cpptrace {
|
||||
|
||||
// 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;
|
||||
|
||||
@ -20,15 +20,7 @@
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "binary/safe_dl.hpp"
|
||||
|
||||
#define ESC "\033["
|
||||
#define RESET ESC "0m"
|
||||
#define RED ESC "31m"
|
||||
#define GREEN ESC "32m"
|
||||
#define YELLOW ESC "33m"
|
||||
#define BLUE ESC "34m"
|
||||
#define MAGENTA ESC "35m"
|
||||
#define CYAN ESC "36m"
|
||||
#include "snippets/snippet.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
@ -173,10 +165,10 @@ namespace cpptrace {
|
||||
std::size_t counter,
|
||||
const stacktrace_frame& frame
|
||||
) {
|
||||
const auto reset = color ? ESC "0m" : "";
|
||||
const auto green = color ? ESC "32m" : "";
|
||||
const auto yellow = color ? ESC "33m" : "";
|
||||
const auto blue = color ? ESC "34m" : "";
|
||||
const auto reset = color ? RESET : "";
|
||||
const auto green = color ? GREEN : "";
|
||||
const auto yellow = color ? YELLOW : "";
|
||||
const auto blue = color ? BLUE : "";
|
||||
stream
|
||||
<< '#'
|
||||
<< std::setw(static_cast<int>(frame_number_width))
|
||||
@ -253,6 +245,45 @@ namespace cpptrace {
|
||||
}
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets() const {
|
||||
print_with_snippets(std::cerr, true);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream) const {
|
||||
print_with_snippets(stream, true);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
|
||||
print_with_snippets(stream, color, true, nullptr);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
|
||||
if(
|
||||
color && (
|
||||
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
|
||||
)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
stream<<(header ? header : "Stack trace (most recent call first):") << '\n';
|
||||
std::size_t counter = 0;
|
||||
if(frames.empty()) {
|
||||
stream<<"<empty trace>" << '\n';
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
print_frame(stream, color, frame_number_width, counter, frame);
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
if(frame.line.has_value() && !frame.filename.empty()) {
|
||||
stream << detail::get_snippet(frame.filename, frame.line.value(), 2, color);
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
void stacktrace::clear() {
|
||||
frames.clear();
|
||||
}
|
||||
@ -368,6 +399,10 @@ namespace cpptrace {
|
||||
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);
|
||||
}
|
||||
|
||||
129
src/snippets/snippet.cpp
Normal file
129
src/snippets/snippet.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
#include "snippet.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
constexpr std::int64_t max_size = 1024 * 1024 * 10; // 10 MiB
|
||||
|
||||
class snippet_manager {
|
||||
bool loaded_contents;
|
||||
std::string contents;
|
||||
// for index i, gives the index in `contents` of one past the end of the line (i.e. the \n or contents.end())
|
||||
std::vector<std::size_t> line_table;
|
||||
public:
|
||||
snippet_manager(const std::string& path) : loaded_contents(false) {
|
||||
std::ifstream file;
|
||||
try {
|
||||
file.open(path, std::ios::ate);
|
||||
if(file.is_open()) {
|
||||
std::ifstream::pos_type size = file.tellg();
|
||||
if(size == std::ifstream::pos_type(-1) || size > max_size) {
|
||||
return;
|
||||
}
|
||||
// else load file
|
||||
file.seekg(0, std::ios::beg);
|
||||
contents.resize(size);
|
||||
if(!file.read(&contents[0], size)) {
|
||||
// error ...
|
||||
}
|
||||
build_line_table();
|
||||
loaded_contents = true;
|
||||
}
|
||||
} catch(const std::ifstream::failure&) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_line(std::size_t line) const { // 0-indexed line TODO: reconsider
|
||||
if(!loaded_contents || line >= line_table.size()) {
|
||||
return "";
|
||||
} else if(line == 0) {
|
||||
return contents.substr(0, line_table[line]);
|
||||
} else {
|
||||
return contents.substr(line_table[line - 1] + 1, line_table[line] - line_table[line - 1] - 1);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t num_lines() const {
|
||||
return line_table.size();
|
||||
}
|
||||
|
||||
bool ok() const {
|
||||
return loaded_contents;
|
||||
}
|
||||
private:
|
||||
void build_line_table() {
|
||||
std::size_t pos = 0;
|
||||
while(true) {
|
||||
std::size_t new_pos = contents.find('\n', pos);
|
||||
if(new_pos == std::string::npos) {
|
||||
line_table.push_back(contents.size());
|
||||
break;
|
||||
} else {
|
||||
line_table.push_back(new_pos);
|
||||
pos = new_pos + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::mutex snippet_manager_mutex;
|
||||
std::unordered_map<std::string, const snippet_manager> snippet_managers;
|
||||
|
||||
const snippet_manager& get_manager(const std::string& path) {
|
||||
std::unique_lock<std::mutex> lock(snippet_manager_mutex);
|
||||
auto it = snippet_managers.find(path);
|
||||
if(it == snippet_managers.end()) {
|
||||
return snippet_managers.insert({path, snippet_manager(path)}).first->second;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_snippet(const std::string& path, std::size_t target_line, std::size_t context_size, bool color) {
|
||||
target_line--;
|
||||
const auto& manager = get_manager(path);
|
||||
if(!manager.ok()) {
|
||||
return "";
|
||||
}
|
||||
auto begin = target_line <= context_size ? 0 : target_line - context_size;
|
||||
auto original_begin = begin;
|
||||
auto end = std::min(target_line + context_size, manager.num_lines() - 1);
|
||||
std::vector<std::string> lines;
|
||||
for(auto line = begin; line <= end; line++) {
|
||||
lines.push_back(manager.get_line(line));
|
||||
}
|
||||
// trim blank lines
|
||||
while(begin < target_line && lines[begin - original_begin].empty()) {
|
||||
begin++;
|
||||
}
|
||||
while(end > target_line && lines[end - original_begin].empty()) {
|
||||
end--;
|
||||
}
|
||||
// make the snippet
|
||||
std::string snippet;
|
||||
constexpr std::size_t margin_width = 8;
|
||||
for(auto line = begin; line <= end; line++) {
|
||||
if(color && line == target_line) {
|
||||
snippet += YELLOW;
|
||||
}
|
||||
auto line_str = std::to_string(line);
|
||||
snippet += std::string(margin_width - line_str.size(), ' ') + line_str + ": ";
|
||||
if(color && line == target_line) {
|
||||
snippet += RESET;
|
||||
}
|
||||
snippet += lines[line - original_begin] + "\n";
|
||||
}
|
||||
return snippet;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/snippets/snippet.hpp
Normal file
14
src/snippets/snippet.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef SNIPPET_HPP
|
||||
#define SNIPPET_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
// 1-indexed line
|
||||
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -41,6 +41,15 @@
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
|
||||
#define ESC "\033["
|
||||
#define RESET ESC "0m"
|
||||
#define RED ESC "31m"
|
||||
#define GREEN ESC "32m"
|
||||
#define YELLOW ESC "33m"
|
||||
#define BLUE ESC "34m"
|
||||
#define MAGENTA ESC "35m"
|
||||
#define CYAN ESC "36m"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
// Placed here instead of utils because it's used by error.hpp and utils.hpp
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
void trace() {
|
||||
cpptrace::generate_trace().print();
|
||||
cpptrace::generate_trace().print_with_snippets();
|
||||
throw cpptrace::logic_error("foobar");
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user