Improve exception what() message, tab-format the output
Some checks failed
buildbot/mdml-cgi-darwin-macos-builder Build done.
buildbot/mdml-cgi-linux-podman-builder Build done.
buildbot/mdml-cgi-freebsd-jail-builder Build done.

Add platform detection static class with flags.

Close #8 - Exceptions with stack traces.
This commit is contained in:
S David 2023-10-20 19:23:16 -04:00
parent 9ffbaa5abe
commit d8173aae98
5 changed files with 228 additions and 73 deletions

View File

@ -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
View 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 :

View File

@ -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;

View File

@ -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();
}

View File

@ -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 :