Improve exception what() message, tab-format the output
Add platform detection static class with flags. Close #8 - Exceptions with stack traces.
This commit is contained in:
parent
9ffbaa5abe
commit
d8173aae98
@ -19,30 +19,28 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#define NOT_IMPLEMENTED std::runtime_error("Unimplemented Method");
|
||||
|
||||
#define THROW_EXCEPTION(message) \
|
||||
throw mdml::exception(message, generate_stacktrace(2))
|
||||
|
||||
namespace mdml {
|
||||
|
||||
class exception : public std::exception {
|
||||
|
||||
public:
|
||||
explicit exception(
|
||||
optional_reference<std::exception> innerException = std::nullopt,
|
||||
const std::optional<std::string> backtrace = std::nullopt
|
||||
);
|
||||
exception(const std::runtime_error& e);
|
||||
exception(const char* error_message = default_error);
|
||||
exception(const std::exception& inner);
|
||||
|
||||
exception& operator=(const exception&) = delete;
|
||||
|
||||
virtual const char* what() const noexcept override;
|
||||
const std::string& stacktrace() const noexcept;
|
||||
|
||||
const char* getStacktrace() const noexcept;
|
||||
constexpr static auto default_error = "An exception has ocurred!";
|
||||
|
||||
private:
|
||||
void build_what_message();
|
||||
|
||||
std::string error_message;
|
||||
std::string what_message;
|
||||
std::optional<std::string> stacktrace;
|
||||
std::string stack_trace;
|
||||
|
||||
std::exception_ptr inner_exception_ptr;
|
||||
};
|
||||
}
|
||||
|
43
include/platform.hpp
Normal file
43
include/platform.hpp
Normal file
@ -0,0 +1,43 @@
|
||||
/* platform.hpp
|
||||
* Copyright © 2023 Saul D. Beniquez
|
||||
* License: Mozilla Public License v. 2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
* v.2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace mdml {
|
||||
|
||||
struct Platform {
|
||||
#ifdef __linux__
|
||||
static constexpr bool LINUX = true;
|
||||
#else
|
||||
static constexpr bool LINUX = false;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
static constexpr bool WINDOWS = true;
|
||||
#else
|
||||
static constexpr bool WINDOWS = false;
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
static constexpr bool MACOS = true;
|
||||
#else
|
||||
static constexpr bool MACOS = false;
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
static constexpr bool FREEBSD = true;
|
||||
#else
|
||||
static constexpr bool FREEBSD = false;
|
||||
#endif
|
||||
|
||||
// I don't own any AIX, Solaris, HP-UX, or pure Darwin systems, sorry :)
|
||||
};
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// vim: set foldmethod=marker foldmarker=#region,#endregion textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include "debuginfo.hpp"
|
||||
#include "platform.hpp"
|
||||
|
||||
#if defined(HAVE_EXECINFO_H) && !defined(__linux__)
|
||||
#include <cxxabi.h>
|
||||
@ -22,12 +23,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
constexpr bool FREEBSD = true;
|
||||
#else
|
||||
constexpr bool FREEBSD = false;
|
||||
#endif
|
||||
|
||||
namespace mdml {
|
||||
namespace {
|
||||
|
||||
@ -74,9 +69,12 @@ generate_stacktrace(unsigned short framesToRemove)
|
||||
size_t columns_to_print = 0;
|
||||
|
||||
// preconfigure column length for certain platforms
|
||||
if (FREEBSD) {
|
||||
if (Platform::FREEBSD) {
|
||||
columns_to_print = 2;
|
||||
} else if (Platform::MACOS) {
|
||||
columns_to_print = 4;
|
||||
}
|
||||
|
||||
for (i = framesToRemove; i < frames; ++i) {
|
||||
std::string word;
|
||||
std::stringstream line_stream(strs[i]);
|
||||
@ -84,8 +82,9 @@ generate_stacktrace(unsigned short framesToRemove)
|
||||
|
||||
// Create a list of words for this stack trace line
|
||||
while (line_stream >> word) {
|
||||
if (FREEBSD && (word.find('<') != word.npos &&
|
||||
word.find('>') != word.npos)) {
|
||||
if (columns_to_print != 0 &&
|
||||
(word.find('<') != word.npos &&
|
||||
word.find('>') != word.npos)) {
|
||||
auto extracted_symbol =
|
||||
extract_mangled_symbol(word);
|
||||
word = extracted_symbol;
|
||||
|
@ -14,41 +14,35 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#define NOT_IMPLEMENTED std::runtime_error("Unimplemented Method");
|
||||
|
||||
using mdml::exception;
|
||||
|
||||
exception::exception(
|
||||
optional_reference<std::exception> nestedException,
|
||||
const std::optional<std::string> backtrace
|
||||
)
|
||||
: std::exception(), stacktrace(std::nullopt), inner_exception_ptr(nullptr)
|
||||
constexpr unsigned DEFAULT_STACKFRAMES_TO_STRIP = 2;
|
||||
|
||||
// Helper classes and functions. #region
|
||||
|
||||
exception::exception(const char* error_message)
|
||||
: std::exception()
|
||||
, error_message(error_message)
|
||||
, what_message()
|
||||
, stack_trace(mdml::generate_stacktrace(DEFAULT_STACKFRAMES_TO_STRIP))
|
||||
, inner_exception_ptr()
|
||||
{
|
||||
if (nestedException.has_value()) {
|
||||
this->inner_exception_ptr =
|
||||
std::make_exception_ptr(nestedException.value().get());
|
||||
}
|
||||
|
||||
if (!backtrace.has_value()) {
|
||||
this->stacktrace = generate_stacktrace(2);
|
||||
} else {
|
||||
this->stacktrace = backtrace;
|
||||
}
|
||||
|
||||
build_what_message();
|
||||
}
|
||||
|
||||
exception::exception(const std::runtime_error& e)
|
||||
: std::exception(), inner_exception_ptr(), stacktrace(std::nullopt)
|
||||
exception::exception(const std::exception& inner)
|
||||
: std::exception(inner)
|
||||
, error_message(inner.what())
|
||||
, what_message()
|
||||
, inner_exception_ptr(std::make_exception_ptr(&inner))
|
||||
, stack_trace(mdml::generate_stacktrace(DEFAULT_STACKFRAMES_TO_STRIP))
|
||||
{
|
||||
inner_exception_ptr = std::make_exception_ptr(e);
|
||||
this->stacktrace = generate_stacktrace(2);
|
||||
|
||||
build_what_message();
|
||||
}
|
||||
|
||||
@ -58,33 +52,48 @@ exception::what() const noexcept
|
||||
return this->what_message.c_str();
|
||||
}
|
||||
|
||||
const char*
|
||||
exception::getStacktrace() const noexcept
|
||||
const std::string&
|
||||
exception::stacktrace() const noexcept
|
||||
{
|
||||
return this->stacktrace->c_str();
|
||||
return this->stack_trace;
|
||||
}
|
||||
|
||||
std::string
|
||||
prepend_tabs_to_lines(const std::string& input)
|
||||
{
|
||||
std::ostringstream results_buffer;
|
||||
std::istringstream input_buffer(input);
|
||||
|
||||
// Function to add a tab character before each line
|
||||
auto addTabBeforeLine = [&results_buffer](const std::string& line) {
|
||||
results_buffer << '\t' << line << '\n';
|
||||
};
|
||||
|
||||
// Process each line and add a tab character before it
|
||||
std::string line;
|
||||
while (std::getline(input_buffer, line)) {
|
||||
addTabBeforeLine(line);
|
||||
}
|
||||
|
||||
return results_buffer.str();
|
||||
}
|
||||
void
|
||||
exception::build_what_message()
|
||||
{
|
||||
std::stringstream buffer;
|
||||
std::string indented_stacktrace =
|
||||
prepend_tabs_to_lines(this->stack_trace);
|
||||
|
||||
buffer << "mdml::exception::what(): { " << std::endl;
|
||||
|
||||
buffer << " error: " << error_message << std::endl;
|
||||
|
||||
if (inner_exception_ptr) {
|
||||
try {
|
||||
std::rethrow_exception(inner_exception_ptr);
|
||||
} catch (const std::exception& rethrown) {
|
||||
buffer << rethrown.what();
|
||||
}
|
||||
} else {
|
||||
buffer << "An error has ocurred. (No Inner exception)";
|
||||
}
|
||||
buffer << std::endl;
|
||||
|
||||
if (this->stacktrace.has_value()) {
|
||||
buffer << std::endl << "Backtrace: " << std::endl;
|
||||
buffer << this->stacktrace.value() << std::endl;
|
||||
}
|
||||
buffer << " stack_trace: " << std::endl
|
||||
<< indented_stacktrace << std::endl;
|
||||
|
||||
buffer << "}; " << std::endl;
|
||||
what_message = buffer.str();
|
||||
}
|
||||
|
||||
|
@ -12,24 +12,130 @@
|
||||
#include "tests-common.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
BEGIN_TEST_SUITE("exceptions")
|
||||
namespace Match = Catch::Matchers;
|
||||
using Catch::CaseSensitive;
|
||||
|
||||
void
|
||||
throw_local_variable()
|
||||
{
|
||||
// @todo: move this to a runtime-test.cpp file
|
||||
TEST("Test the test framework")
|
||||
{
|
||||
REQUIRE_THROWS([]() {
|
||||
throw std::runtime_error("Error Message");
|
||||
}());
|
||||
auto local = std::logic_error("This is a stack variable");
|
||||
|
||||
try {
|
||||
throw mdml::exception(std::runtime_error("ERROR"));
|
||||
} catch (mdml::exception& e) {
|
||||
std::cout << "Caught exception: " << e.what()
|
||||
<< std::endl;
|
||||
// Stack variables should not be used outside their stack frames.
|
||||
throw mdml::exception(local);
|
||||
}
|
||||
|
||||
BEGIN_TEST_SUITE("mdml::exception")
|
||||
{
|
||||
auto throw_an_exception = []() {
|
||||
throw mdml::exception("An error occurred!!");
|
||||
};
|
||||
|
||||
TEST("TEST: Can throw new exception type")
|
||||
{
|
||||
REQUIRE_THROWS(throw_an_exception());
|
||||
}
|
||||
|
||||
TEST("TEST: Can construct exception various ways")
|
||||
{
|
||||
SECTION("1. Blank constructor")
|
||||
{
|
||||
mdml::exception obj;
|
||||
}
|
||||
SECTION("2. With string parameter")
|
||||
{
|
||||
mdml::exception obj("Sample Error");
|
||||
}
|
||||
SECTION("3. With STL exception")
|
||||
{
|
||||
mdml::exception obj(std::runtime_error("Sample Error"));
|
||||
}
|
||||
SECTION("4. With destroyed stack")
|
||||
{
|
||||
auto nested_function_call = []() {
|
||||
throw_local_variable();
|
||||
};
|
||||
try {
|
||||
nested_function_call();
|
||||
} catch (std::exception& e) {
|
||||
REQUIRE_THAT(
|
||||
e.what(),
|
||||
Match::ContainsSubstring(
|
||||
"This is a stack variable"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST("TEST: what() message reflects error")
|
||||
{
|
||||
|
||||
SECTION("1. Unspecified error or exception")
|
||||
{
|
||||
mdml::exception obj;
|
||||
|
||||
REQUIRE_THAT(
|
||||
obj.what(),
|
||||
Match::ContainsSubstring(
|
||||
mdml::exception::default_error,
|
||||
CaseSensitive::Yes
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
} // #endregion
|
||||
SECTION("2. custom error or exception")
|
||||
{
|
||||
constexpr auto test_message = "This is a test.";
|
||||
mdml::exception test_object_one(test_message);
|
||||
mdml::exception test_object_two(
|
||||
std::logic_error("Makes no sense")
|
||||
);
|
||||
SECTION(" a: what() does not contain default message")
|
||||
{
|
||||
REQUIRE_THAT(
|
||||
test_object_one.what(),
|
||||
!Match::ContainsSubstring(
|
||||
mdml::exception::default_error
|
||||
)
|
||||
);
|
||||
}
|
||||
SECTION(" b: what() displays custom message")
|
||||
{
|
||||
REQUIRE_THAT(
|
||||
test_object_one.what(),
|
||||
Match::ContainsSubstring(test_message)
|
||||
);
|
||||
}
|
||||
SECTION(" c: what() contains inner exception message")
|
||||
{
|
||||
REQUIRE_THAT(
|
||||
test_object_two.what(),
|
||||
Match::ContainsSubstring("Makes no sense")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST("TEST: what() contains stacktrace with Catch2 runtime methods")
|
||||
{
|
||||
mdml::exception test_object("Test");
|
||||
SECTION(" a: what() does not contain default message")
|
||||
{
|
||||
REQUIRE_THAT(
|
||||
test_object.what(),
|
||||
Match::ContainsSubstring("Catch::RunContext")
|
||||
);
|
||||
SUCCEED(test_object.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// vim: set foldmethod=marker foldmarker=#region,#endregion textwidth=80 ts=4 sts=0 sw=4 noexpandtab ft=cpp.doxygen :
|
||||
|
Loading…
Reference in New Issue
Block a user