Skip to content

Commit

Permalink
files: add error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
skim0119 committed Jul 12, 2024
1 parent ca9025f commit 575997c
Show file tree
Hide file tree
Showing 15 changed files with 478 additions and 0 deletions.
21 changes: 21 additions & 0 deletions backend/src/ErrorHandling/Abort.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// \file
/// Defines function ErrorHandling::abort.

#pragma once

#include <cstdio>
#include <cstdlib>
#include <exception>
#include <string>

namespace Parallel {
/// Abort the program with an error message.
[[noreturn]] inline void abort(const std::string& message) {
printf("%s", message.c_str());
std::exit(EXIT_SUCCESS);

// the following call is never reached, but suppresses the warning that
// a 'noreturn' functions does return
std::terminate(); // LCOV_EXCL_LINE
}
} // namespace Parallel
53 changes: 53 additions & 0 deletions backend/src/ErrorHandling/AbortWithErrorMessage.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "ErrorHandling/AbortWithErrorMessage.hpp"

#include <cstdio>
#include <sstream>

#include "ErrorHandling/Abort.hpp"

void abort_with_error_message(const char* expression, const char* file,
const int line, const char* pretty_function,
const std::string& message) {
std::ostringstream os;
os << "\n"
<< "############ ASSERT FAILED ############\n"
// << "Node: " << Parallel::my_node() << " Proc: " <<
// Parallel::my_proc()
// << "\n"
<< "Line: " << line << " of " << file << "\n"
<< "'" << expression << "' violated!\n"
<< "Function: " << pretty_function << "\n"
<< message << "\n"
<< "############ ASSERT FAILED ############\n"
<< "\n";
// We use printf instead of abort to print the error message because in the
// case of an executable not using Charm++'s main function the call to abort
// will segfault before anything is printed.
printf("%s", os.str().c_str());
Parallel::abort("");
}

void abort_with_error_message(const char* file, const int line,
const char* pretty_function,
const std::string& message) {
std::ostringstream os;
os << "\n"
<< "############ ERROR ############\n"
// << "Node: " << Parallel::my_node() << " Proc: " <<
// Parallel::my_proc()
// << "\n"
<< "Line: " << line << " of " << file << "\n"
<< "Function: " << pretty_function << "\n"
<< message << "\n"
<< "############ ERROR ############\n"
<< "\n";
// We use printf instead of abort to print the error message because in the
// case of an executable not using Charm++'s main function the call to abort
// will segfault before anything is printed.
// printf("%s", os.c_str());
printf("%s", os.str().c_str());
Parallel::abort("");
}
22 changes: 22 additions & 0 deletions backend/src/ErrorHandling/AbortWithErrorMessage.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Distributed under the MIT License.
// See LICENSE.txt for details.

/// \file
/// Declares function abort_with_error_message

#pragma once

#include <string>

/// \ingroup ErrorHandlingGroup
/// Compose an error message with an expression and abort the program.
[[noreturn]] void abort_with_error_message(const char* expression,
const char* file, int line,
const char* pretty_function,
const std::string& message);

/// \ingroup ErrorHandlingGroup
/// Compose an error message and abort the program.
[[noreturn]] void abort_with_error_message(const char* file, int line,
const char* pretty_function,
const std::string& message);
63 changes: 63 additions & 0 deletions backend/src/ErrorHandling/Assert.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Distributed under the MIT License.
// See LICENSE.txt for details.

/// \file
/// Defines macro ASSERT.

#pragma once

#include <sstream>
#include <string>

#include "ErrorHandling/Abort.hpp"
#include "ErrorHandling/AbortWithErrorMessage.hpp"
#include "ErrorHandling/FloatingPointExceptions.hpp"

/*!
* \ingroup ErrorHandlingGroup
* \brief Assert that an expression should be true.
*
* If the preprocessor macro ELASTICA_DEBUG is defined and the expression is
* false, an error message is printed to the standard error stream, and the
* program aborts. ASSERT should be used to catch coding errors as it does
* nothing in production code.
* \param a the expression that must be true
* \param m the error message as an ostream
*/
//#ifdef ELASTICA_DEBUG
#ifndef NDEBUG
// isocpp.org recommends using an `if (true)` instead of a `do
// while(false)` for macros because the latter can mess with inlining
// in some (old?) compilers:
// https://isocpp.org/wiki/faq/misc-technical-issues#macros-with-multi-stmts
// https://isocpp.org/wiki/faq/misc-technical-issues#macros-with-if
// However, Intel's reachability analyzer (as of version 16.0.3
// 20160415) can't figure out that the else branch and everything
// after it is unreachable, causing warnings (and possibly suboptimal
// code generation).
#define ELASTICA_ASSERT(a, m) \
do { \
if (!(a)) { \
disable_floating_point_exceptions(); \
std::ostringstream avoid_name_collisions_ASSERT; \
/* clang-tidy: macro arg in parentheses */ \
avoid_name_collisions_ASSERT << m; /* NOLINT */ \
abort_with_error_message(#a, __FILE__, __LINE__, \
static_cast<const char*>(__PRETTY_FUNCTION__), \
avoid_name_collisions_ASSERT.str()); \
} \
} while (false)
#else
#define ELASTICA_ASSERT(a, m) \
do { \
if (false) { \
static_cast<void>(a); \
disable_floating_point_exceptions(); \
std::ostringstream avoid_name_collisions_ASSERT; \
/* clang-tidy: macro arg in parentheses */ \
avoid_name_collisions_ASSERT << m; /* NOLINT */ \
static_cast<void>(avoid_name_collisions_ASSERT); \
enable_floating_point_exceptions(); \
} \
} while (false)
#endif
21 changes: 21 additions & 0 deletions backend/src/ErrorHandling/Breakpoint.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "ErrorHandling/Breakpoint.hpp"

#include <csignal>

void breakpoint() {
// We send ourselves a SIGTRAP and ignore it. If we're not being
// traced (e.g. being run in a debugger), that doesn't do much, but if we are
// then the tracer (debugger) can see the signal delivery. We don't reset the
// signal handler afterwards in case this is called on multiple threads; we
// don't want one thread reenabling the default handler just before another
// calls raise().
struct sigaction handler {};
handler.sa_handler = SIG_IGN; // NOLINT
handler.sa_flags = 0;
sigemptyset(&handler.sa_mask);
sigaction(SIGTRAP, &handler, nullptr);
raise(SIGTRAP);
}
9 changes: 9 additions & 0 deletions backend/src/ErrorHandling/Breakpoint.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// \file
/// Defines function ErrorHandling::breakpoint.

#pragma once

/*!
* \brief Raise `SIGTRAP` so that debuggers will trap.
*/
void breakpoint();
28 changes: 28 additions & 0 deletions backend/src/ErrorHandling/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Distributed under the MIT License. See LICENSE.txt for details.

set(LIBRARY ErrorHandling)

add_elastica_library(${LIBRARY})

elastica_target_sources(
${LIBRARY}
PRIVATE
AbortWithErrorMessage.cpp
Breakpoint.cpp
FloatingPointExceptions.cpp
NestedExceptions.cpp
)

elastica_target_headers(
${LIBRARY}
INCLUDE_DIRECTORY ${CMAKE_SOURCE_DIR}/elastica
AbortWithErrorMessage.hpp
Abort.hpp
Assert.hpp
Breakpoint.hpp
Error.hpp
ExpectsAndEnsures.hpp
Exit.hpp
FloatingPointExceptions.hpp
NestedExceptions.hpp
)
63 changes: 63 additions & 0 deletions backend/src/ErrorHandling/Error.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Reused from SpECTRE : https://spectre-code.org/
// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <sstream>
#include <string>

#include "ErrorHandling/Abort.hpp"
#include "ErrorHandling/AbortWithErrorMessage.hpp"
#include "ErrorHandling/Breakpoint.hpp"
#include "ErrorHandling/FloatingPointExceptions.hpp"

/*!
* \ingroup ErrorHandlingGroup
* \brief prints an error message to the standard error stream and aborts the
* program.
*
* ERROR should not be used for coding errors, but instead for user errors
* or failure modes of numerical algorithms. An acceptable use for error is also
* in the default case of a switch statement.
* \param m an arbitrary output stream.
*/
// isocpp.org recommends using an `if (true)` instead of a `do
// while(false)` for macros because the latter can mess with inlining
// in some (old?) compilers:
// https://isocpp.org/wiki/faq/misc-technical-issues#macros-with-multi-stmts
// https://isocpp.org/wiki/faq/misc-technical-issues#macros-with-if
// However, Intel's reachability analyzer (as of version 16.0.3
// 20160415) can't figure out that the else branch and everything
// after it is unreachable, causing warnings (and possibly suboptimal
// code generation).
#define ERROR(m) \
do { \
disable_floating_point_exceptions(); \
std::ostringstream avoid_name_collisions_ERROR; \
/* clang-tidy: macro arg in parentheses */ \
avoid_name_collisions_ERROR << m; /* NOLINT */ \
abort_with_error_message(__FILE__, __LINE__, \
static_cast<const char*>(__PRETTY_FUNCTION__), \
avoid_name_collisions_ERROR.str()); \
} while (false)

/*!
* \ingroup ErrorHandlingGroup
* \brief prints an error message to the standard error and aborts the
* program.
*
* CERROR is just like ERROR and so the same guidelines apply. However, because
* it does not use std::stringstream it can be used in some constexpr
* functions where ERROR cannot be.
* \param m error message as a string, may need to use string literals
*/
#define CERROR(m) \
do { \
breakpoint(); \
using std::string_literals::operator""s; \
Parallel::abort("\n################ ERROR ################\nLine: "s + \
std::to_string(__LINE__) + " of file '"s + __FILE__ + \
"'\n"s + m + /* NOLINT */ \
"\n#######################################\n"s); \
} while (false)
16 changes: 16 additions & 0 deletions backend/src/ErrorHandling/Exit.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// \file
/// Defines function ErrorHandling::abort.

#pragma once

#include <exception>

namespace Parallel {
/// Abort the program with an error message.
[[noreturn]] inline void exit() {
std::exit(EXIT_SUCCESS);
// the following call is never reached, but suppresses the warning that
// a 'noreturn' functions does return
std::terminate(); // LCOV_EXCL_LINE
}
} // namespace Parallel
72 changes: 72 additions & 0 deletions backend/src/ErrorHandling/ExpectsAndEnsures.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Distributed under the MIT License.
// See LICENSE.txt for details.

/// \file
/// Defines macros Expects and Ensures

#pragma once

#include "ErrorHandling/Error.hpp"

// part of GSL, but GSL depends on Expects and Ensures...
#ifndef UNLIKELY
#if defined(__clang__) || defined(__GNUC__)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
#define UNLIKELY(x) (x)
#endif
#endif

// part of GSL, but GSL depends on Expects and Ensures...
#ifndef LIKELY
#if defined(__clang__) || defined(__GNUC__)
#define LIKELY(x) __builtin_expect(!!(x), 1)
#else
#define LIKELY(x) (x)
#endif
#endif

/*!
* \ingroup ErrorHandlingGroup
* \brief check expectation of pre-conditions of a function
*
* The Expects macro sets the preconditions to a function's arguments, it is a
* contract (C++20) that must be satisfied. See the CppCoreGuidelines for
* details.
* \param cond the expression that is expected to be true
*/
#if defined(ELASTICA_DEBUG) || defined(EXPECTS_ENSURES)
#define Expects(cond) \
if (UNLIKELY(!(cond))) { \
CERROR("Expects violated: "s + #cond); \
} else \
static_cast<void>(0)
#else
#define Expects(cond) \
if (false) { \
static_cast<void>(cond); \
} else \
static_cast<void>(0)
#endif

/*!
* \ingroup ErrorHandlingGroup
* \brief Check that a post-condition of a function is true
*
* The Ensures macro sets the postconditions of function, it is a contract
* (C++20) that must be satisfied. See the CppCoreGuidelines for details.
* \param cond the expression that is expected to be true
*/
#if defined(ELASTICA_DEBUG) || defined(EXPECTS_ENSURES)
#define Ensures(cond) \
if (UNLIKELY(!(cond))) { \
CERROR("Ensures violated: "s + #cond); \
} else \
static_cast<void>(0)
#else
#define Ensures(cond) \
if (false) { \
static_cast<void>(cond); \
} else \
static_cast<void>(0)
#endif
Loading

0 comments on commit 575997c

Please sign in to comment.