feat: new exception handling model.
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
|
||||
namespace di::data_format {
|
||||
|
||||
using namespace io;
|
||||
|
||||
void BoundSymbolList::read(const fs::path& path) {
|
||||
std::ifstream ifs(path);
|
||||
if (!ifs) {
|
||||
throw std::runtime_error("Failed to open data path.");
|
||||
}
|
||||
if (!ifs) throw UnableToOpenException(path);
|
||||
|
||||
auto data = nlohmann::json::parse(ifs);
|
||||
|
||||
@@ -20,9 +20,7 @@ void BoundSymbolList::read(const fs::path& path) {
|
||||
|
||||
void BoundSymbolList::write(const fs::path& path) const {
|
||||
std::ofstream ofs(path);
|
||||
if (!ofs) {
|
||||
throw std::runtime_error("Failed to open file!");
|
||||
}
|
||||
if (!ofs) throw UnableToOpenException(path);
|
||||
|
||||
nlohmann::json data;
|
||||
for (const auto& entity : m_entities) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "data_format/raw_text.h"
|
||||
|
||||
using namespace di::io;
|
||||
|
||||
namespace di::data_format {
|
||||
|
||||
void RawText::read(const fs::path& path) {
|
||||
@@ -12,9 +14,7 @@ void RawText::read(const fs::path& path) {
|
||||
|
||||
void RawText::write(const fs::path& path) const {
|
||||
std::ofstream ofs(path);
|
||||
if (!ofs) {
|
||||
throw std::runtime_error("Failed to open save file.");
|
||||
}
|
||||
if (!ofs) throw UnableToOpenException(path);
|
||||
|
||||
ofs << m_data;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
HSTR(VarTemplateSpecialization);
|
||||
#undef HSTR
|
||||
default:
|
||||
throw std::invalid_argument("Unexpected decl type.");
|
||||
throw EnumTransformException(str, Enum{});
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
HSTR(VarTemplateSpecialization);
|
||||
#undef HSTR
|
||||
default:
|
||||
throw std::runtime_error("Unexpected decl type.");
|
||||
throw EnumTransformException(m_data, Enum{});
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "data_format/typed_symbol_list.h"
|
||||
|
||||
using namespace di::io;
|
||||
|
||||
namespace di::data_format {
|
||||
|
||||
void TypedSymbolList::read(const fs::path& path) {
|
||||
std::ifstream ifs(path);
|
||||
if (!ifs) {
|
||||
throw std::runtime_error("Failed to open symlist file.");
|
||||
}
|
||||
if (!ifs) throw UnableToOpenException(path);
|
||||
|
||||
std::string line;
|
||||
while (std::getline(ifs, line)) {
|
||||
@@ -14,10 +14,7 @@ void TypedSymbolList::read(const fs::path& path) {
|
||||
|
||||
auto sep_pos = line.find(", ");
|
||||
if (sep_pos == std::string::npos) {
|
||||
throw std::runtime_error(
|
||||
"Symbol data is not included declType, please re-generate "
|
||||
"symlist file with -record-decl-name."
|
||||
);
|
||||
throw MissingDeclTypeException(path, line);
|
||||
}
|
||||
|
||||
auto decl_type_s = line.substr(0, sep_pos);
|
||||
|
||||
@@ -26,4 +26,20 @@ private:
|
||||
std::unordered_set<TypedSymbol> m_data;
|
||||
};
|
||||
|
||||
class MissingDeclTypeException
|
||||
: public RuntimeException<MissingDeclTypeException> {
|
||||
public:
|
||||
explicit MissingDeclTypeException(
|
||||
const fs::path& list_file_path,
|
||||
std::string_view current_line
|
||||
)
|
||||
: RuntimeException(
|
||||
"The symbol data file does not contain the decl_type. Please "
|
||||
"regenerate it using the -record-decl-name option."
|
||||
) {
|
||||
add_context("path", list_file_path.string());
|
||||
add_context("current_line", current_line);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace di::data_format
|
||||
|
||||
124
src/error.h
Normal file
124
src/error.h
Normal file
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
namespace di {
|
||||
|
||||
class BaseException {
|
||||
public:
|
||||
virtual std::string what() const = 0;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const BaseException& e) {
|
||||
os << e.what();
|
||||
return os;
|
||||
}
|
||||
|
||||
protected:
|
||||
BaseException() = default;
|
||||
};
|
||||
|
||||
template <typename Derived>
|
||||
class RuntimeException : public BaseException {
|
||||
public:
|
||||
template <typename... Args>
|
||||
explicit RuntimeException(
|
||||
const std::format_string<Args...> fmt,
|
||||
Args&&... args
|
||||
)
|
||||
: m_reason(std::format(fmt, std::forward<Args>(args)...)),
|
||||
m_stacktrace(std::stacktrace::current()) {}
|
||||
|
||||
constexpr std::string category() const {
|
||||
return static_cast<const Derived*>(this)->category();
|
||||
}
|
||||
|
||||
std::string what() const override {
|
||||
// [exception.base] Reason...
|
||||
//
|
||||
// Context Information:
|
||||
// val = 1
|
||||
//
|
||||
// StackTrace:
|
||||
// #1 example_func(int) at /app/example.cpp:12
|
||||
// #2 ...
|
||||
// ...
|
||||
|
||||
std::string context_information;
|
||||
if (!m_context_information.empty()) {
|
||||
context_information = "\n\nContext Information: \n";
|
||||
for (const auto& [key, value] : m_context_information) {
|
||||
context_information += std::format(" {} = {}", key, value);
|
||||
}
|
||||
}
|
||||
|
||||
std::string stacktrace = "\n\nStackTrace: \n";
|
||||
int stack_idx = -1; // ignore first entry.
|
||||
for (const auto& entry : m_stacktrace) {
|
||||
stack_idx++;
|
||||
if (stack_idx == 0) continue;
|
||||
auto func_name = entry.description();
|
||||
auto source_file = entry.source_file();
|
||||
if (func_name.empty()) func_name = "<unknown>";
|
||||
if (source_file.empty()) source_file = "<\?\?>";
|
||||
stacktrace += std::format(
|
||||
" #{} {} at {}:{}",
|
||||
stack_idx,
|
||||
func_name,
|
||||
source_file,
|
||||
entry.source_line()
|
||||
);
|
||||
}
|
||||
|
||||
return std::format(
|
||||
"[{}] {}{}{}\n",
|
||||
category(),
|
||||
m_reason,
|
||||
context_information,
|
||||
stacktrace
|
||||
);
|
||||
}
|
||||
|
||||
protected:
|
||||
template <typename T>
|
||||
constexpr void add_context(const std::string& key, T&& value) {
|
||||
m_context_information[key] = std::format("{}", std::forward<T>(value));
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_reason;
|
||||
|
||||
std::unordered_map<std::string, std::string> m_context_information;
|
||||
std::stacktrace m_stacktrace;
|
||||
};
|
||||
|
||||
class UnixException : public RuntimeException<UnixException> {
|
||||
public:
|
||||
explicit UnixException()
|
||||
: RuntimeException<UnixException>("{}", std::strerror(errno)) {
|
||||
add_context("errno", errno);
|
||||
}
|
||||
|
||||
constexpr std::string category() const { return "exception.unix"; }
|
||||
};
|
||||
|
||||
class EnumTransformException : public RuntimeException<EnumTransformException> {
|
||||
public:
|
||||
// TODO: compile-time reflection.
|
||||
// TODO: remove helper.
|
||||
|
||||
template <typename T>
|
||||
explicit EnumTransformException(int enum_val, T _helper = {})
|
||||
: RuntimeException("Unable to convert string to enumeration value because "
|
||||
"input value is bad.") {
|
||||
add_context("enum_type", typeid(T).name());
|
||||
add_context("value", enum_val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
explicit EnumTransformException(std::string_view enum_str, T _helper = {})
|
||||
: RuntimeException("Unable to convert enumeration value to string because "
|
||||
"input value is bad.") {
|
||||
add_context("enum_type", typeid(T).name());
|
||||
add_context("string", enum_str);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace di
|
||||
@@ -10,4 +10,20 @@ public:
|
||||
virtual void write(const fs::path& path) const = 0;
|
||||
};
|
||||
|
||||
class IOException : public RuntimeException<IOException> {
|
||||
public:
|
||||
using RuntimeException::RuntimeException;
|
||||
|
||||
constexpr std::string category() const { return "exception.io"; }
|
||||
};
|
||||
|
||||
class UnableToOpenException : public UnixException {
|
||||
public:
|
||||
explicit UnableToOpenException(const fs::path& path) {
|
||||
add_context("path", path.string());
|
||||
}
|
||||
|
||||
constexpr std::string category() const { return "exception.io.cantopen"; }
|
||||
};
|
||||
|
||||
} // namespace di::io
|
||||
|
||||
@@ -4,9 +4,7 @@ namespace di::io {
|
||||
|
||||
void StreamedIO::read(const fs::path& path) {
|
||||
m_file_stream.open(path, std::ios::binary);
|
||||
if (!m_file_stream) {
|
||||
throw std::runtime_error("Failed to open file!");
|
||||
}
|
||||
if (!m_file_stream) throw UnableToOpenException(path);
|
||||
}
|
||||
|
||||
void StreamedIO::write(const fs::path& path) const {
|
||||
|
||||
@@ -105,7 +105,7 @@ int main(int argc, char* argv[]) try {
|
||||
|
||||
std::println("Everything is OK.");
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
std::println("E: {}", e.what());
|
||||
return -1;
|
||||
} catch (const BaseException& e) {
|
||||
std::cerr << e;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using namespace di;
|
||||
using namespace di::data_format;
|
||||
using namespace di::io;
|
||||
|
||||
auto load_args(int argc, char* argv[]) {
|
||||
argparse::ArgumentParser program("blob-extractor");
|
||||
@@ -47,14 +48,12 @@ int main(int argc, char* argv[]) try {
|
||||
});
|
||||
|
||||
std::ofstream ofs(args.m_output_path);
|
||||
if (!ofs) {
|
||||
throw std::runtime_error("Failed to open file!");
|
||||
}
|
||||
if (!ofs) throw UnableToOpenException(args.m_output_path);
|
||||
|
||||
ofs << data.dump(4);
|
||||
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
std::println("E: {}", e.what());
|
||||
} catch (const BaseException& e) {
|
||||
std::cerr << e;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ int main(int argc, char* argv[]) try {
|
||||
|
||||
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
std::println("E: {}", e.what());
|
||||
} catch (const BaseException& e) {
|
||||
std::cerr << e;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ int main(int argc, char* argv[]) try {
|
||||
pdb.write(args.output_path);
|
||||
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
std::println("E: {}", e.what());
|
||||
} catch (const BaseException& e) {
|
||||
std::cerr << e;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user