Only minor rearrangements necessary to make that possible with C++20
And while at this change (which requires a full rebuild of Lumiera)
- simplify the defined comparison operators, as C++20 can infer most variations
- also mark various usages of `const char*` either as Literal or CStr
Remark: regarding copyright, up to now this is entirely my work,
with two major creation steps in 2008 (conception) and
in 2017 (introduction of a symbol table)
339 lines
12 KiB
C++
339 lines
12 KiB
C++
/*
|
||
ERROR.hpp - Lumiera Exception Interface (C++)
|
||
|
||
Copyright (C)
|
||
2008,2010 Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||
under the terms of the GNU General Public License as published by the
|
||
Free Software Foundation; either version 2 of the License, or (at your
|
||
option) any later version. See the file COPYING for further details.
|
||
|
||
*/
|
||
|
||
|
||
/** @file error.hpp
|
||
** Lumiera error handling (C++ interface).
|
||
** This header declares the Lumiera exception hierarchy,
|
||
** plus some of the most commonly used error flag values.
|
||
** Within Lumiera, C-style error states and C++-style exceptions
|
||
** are tightly integrated. Creating an exception sets the error flag,
|
||
** and there are helpers available to throw an exception automatically
|
||
** when a unclear error state is detected.
|
||
**
|
||
** @see error-state.c
|
||
** @see error.hpp
|
||
**
|
||
*/
|
||
|
||
|
||
#ifndef LUMIERA_ERROR_HPP_
|
||
#define LUMIERA_ERROR_HPP_
|
||
|
||
#define _STDBOOL_H // prevent <atomic> from including stdbool.h
|
||
|
||
#include "include/logging.h"
|
||
#include "lib/hash-standard.hpp"
|
||
#include "lib/error.h"
|
||
|
||
#include <exception>
|
||
#include <string>
|
||
|
||
using CStr = const char*;
|
||
|
||
|
||
#define LERR_(_NAME_) lumiera::error::LUMIERA_ERROR_##_NAME_
|
||
|
||
namespace lumiera {
|
||
namespace error {
|
||
/** error-ID for unspecified exceptions */
|
||
LUMIERA_ERROR_DECLARE(EXCEPTION);
|
||
}
|
||
|
||
using std::string;
|
||
|
||
|
||
|
||
/**
|
||
* Interface and Base definition for all Lumiera Exceptions.
|
||
* Provides common operations for getting a diagnostic message
|
||
* and to obtain the _root cause_ message, i.e. the message
|
||
* from the first exception encountered in a chain of exceptions.
|
||
*/
|
||
class Error
|
||
: public std::exception
|
||
{
|
||
lumiera_err const id_; ///< an LUMIERA_ERROR id, which is set as errorstate on construction
|
||
string msg_; ///< friendly message intended for users (to be localised)
|
||
string desc_; ///< detailed description of the error situation for the developers
|
||
mutable string what_; ///< buffer for generating the detailed description on demand
|
||
const string cause_; ///< description of first exception encountered in the chain
|
||
|
||
|
||
public:
|
||
virtual ~Error () noexcept { }; ///< this is an interface
|
||
|
||
|
||
Error (string description=""
|
||
,lumiera_err const id =LERR_(EXCEPTION)) noexcept;
|
||
Error (std::exception const& cause
|
||
,string description=""
|
||
,lumiera_err const id =LERR_(EXCEPTION)) noexcept;
|
||
|
||
Error (Error &&) = default;
|
||
Error (Error const&) = default;
|
||
Error& operator= (Error &&) = delete;
|
||
Error& operator= (Error const&) = delete;
|
||
|
||
/** std::exception interface : yield a diagnostic message */
|
||
virtual CStr
|
||
what () const noexcept override;
|
||
|
||
/** the internal Lumiera-error-ID
|
||
* (was set as C-errorstate in ctor) */
|
||
lumiera_err
|
||
getID () const noexcept
|
||
{
|
||
return id_;
|
||
}
|
||
|
||
/** extract the message to be displayed for the user */
|
||
string const&
|
||
getUsermsg () const noexcept
|
||
{
|
||
return msg_;
|
||
}
|
||
|
||
/** If this exception was caused by a chain of further exceptions,
|
||
* return the description of the first one registered in this throw sequence.
|
||
* This works only if every exceptions thrown as a consequence of another exception
|
||
* is properly constructed by passing the original exception to the constructor
|
||
* @return the description string, maybe empty (if there is no known root cause)
|
||
*/
|
||
string const&
|
||
rootCause () const noexcept
|
||
{
|
||
return cause_;
|
||
}
|
||
|
||
/** replace the previous or default friendly message for the user.
|
||
* @note to be localised / translated.
|
||
*/
|
||
Error&
|
||
setUsermsg (string const& newMsg) noexcept
|
||
{
|
||
msg_ = newMsg;
|
||
return *this;
|
||
}
|
||
|
||
/** give additional developer info. Typically used at intermediate handlers to add context. */
|
||
Error&
|
||
prependInfo (string const& text) noexcept
|
||
{
|
||
desc_.insert (0, text);
|
||
return *this;
|
||
}
|
||
|
||
|
||
private:
|
||
static const string
|
||
extractCauseMsg (std::exception const&) noexcept;
|
||
};
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/* === Exception sub-categories === */
|
||
|
||
namespace error {
|
||
|
||
/** global function for handling unknown exceptions
|
||
* encountered at functions declaring not to throw
|
||
* this kind of exception. Basically, any such event
|
||
* can be considered a severe design flaw; we can just
|
||
* add some diagnostics prior to halting.
|
||
*/
|
||
void lumiera_unexpectedException () noexcept;
|
||
|
||
/** throw an error::Fatal indicating "assertion failure" */
|
||
void assertion_terminate (const string& location);
|
||
|
||
|
||
/* constants to be used as error IDs */
|
||
LUMIERA_ERROR_DECLARE (LOGIC ); ///< contradiction to internal logic assumptions detected
|
||
LUMIERA_ERROR_DECLARE (FATAL ); ///< unable to cope with, internal logic floundered
|
||
LUMIERA_ERROR_DECLARE (CONFIG ); ///< execution aborted due to misconfiguration
|
||
LUMIERA_ERROR_DECLARE (STATE ); ///< unforeseen internal state
|
||
LUMIERA_ERROR_DECLARE (FLAG ); ///< non-cleared lumiera errorstate from C code
|
||
LUMIERA_ERROR_DECLARE (INVALID ); ///< invalid input or parameters encountered
|
||
LUMIERA_ERROR_DECLARE (EXTERNAL ); ///< failure in external service the application relies on
|
||
LUMIERA_ERROR_DECLARE (ASSERTION); ///< assertion failure
|
||
|
||
/* generic error situations */
|
||
LUMIERA_ERROR_DECLARE (LIFECYCLE); ///< Lifecycle assumptions violated
|
||
LUMIERA_ERROR_DECLARE (WRONG_TYPE); ///< runtime type mismatch
|
||
LUMIERA_ERROR_DECLARE (ITER_EXHAUST); ///< end of sequence reached
|
||
LUMIERA_ERROR_DECLARE (CAPACITY); ///< predefined fixed storage capacity
|
||
LUMIERA_ERROR_DECLARE (SAFETY_LIMIT); ///< exceeding fixed internal safety limit
|
||
LUMIERA_ERROR_DECLARE (INDEX_BOUNDS); ///< index out of bounds
|
||
LUMIERA_ERROR_DECLARE (BOTTOM_VALUE); ///< invalid or NIL value
|
||
LUMIERA_ERROR_DECLARE (UNCONNECTED); ///< missing connection
|
||
LUMIERA_ERROR_DECLARE (UNIMPLEMENTED);///< unimplemented feature
|
||
|
||
|
||
|
||
/**
|
||
* Derived specific exceptions within Lumiera's exception hierarchy.
|
||
*/
|
||
template<lumiera_err const& eID, class PAR =Error>
|
||
class LumieraError
|
||
: public PAR
|
||
{
|
||
public:
|
||
LumieraError (std::string description=""
|
||
,lumiera_err const id=eID) noexcept
|
||
: PAR{description, id? id:eID}
|
||
{ }
|
||
LumieraError (std::exception const& cause
|
||
,std::string description=""
|
||
,lumiera_err const id=eID) noexcept
|
||
: PAR{cause, description, id? id:eID}
|
||
{ }
|
||
};
|
||
|
||
//----CLASS-------------------ID--------------PARENT------
|
||
using Logic = LumieraError<LERR_(LOGIC)>;
|
||
using Fatal = LumieraError<LERR_(FATAL), Logic>;
|
||
using State = LumieraError<LERR_(STATE)>;
|
||
using Flag = LumieraError<LERR_(FLAG), State>;
|
||
using Invalid = LumieraError<LERR_(INVALID)>;
|
||
using Config = LumieraError<LERR_(CONFIG), Invalid>;
|
||
using External = LumieraError<LERR_(EXTERNAL)>;
|
||
|
||
|
||
/** install our own handler for undeclared exceptions. Will be
|
||
* called automatically ON_BASIC_INIT when linking exception.cpp */
|
||
void install_unexpectedException_handler();
|
||
|
||
/** @return error detail-info if currently set, a default message else */
|
||
CStr detailInfo();
|
||
|
||
} // namespace error
|
||
|
||
|
||
|
||
/**
|
||
* Check the lumiera error state, which maybe was set by C-code.
|
||
* @throw Errorflag exception to signal an detected lumiera error
|
||
* @note specific error code and information is enclosed in
|
||
* the raised exception; the error state is _not cleared_.
|
||
*/
|
||
inline void
|
||
throwOnError()
|
||
{
|
||
if (lumiera_err errorFlag =lumiera_error())
|
||
{
|
||
throw error::Flag( error::detailInfo()
|
||
, errorFlag);
|
||
} }
|
||
|
||
/** Check the lumiera error state and throw a specific exception
|
||
* in case a non-cleared errorflag is detected. No-op else.
|
||
* @throw instance of the lumiera::Error subclass provided as
|
||
* template parameter, containing an lumiera::error::Flag
|
||
* as root cause to denote the detected error-flag state.
|
||
*/
|
||
template<class EX>
|
||
inline void
|
||
maybeThrow (string description ="")
|
||
{
|
||
if (lumiera_err errorFlag =lumiera_error())
|
||
{
|
||
throw EX (error::Flag{error::detailInfo(), errorFlag}
|
||
,description);
|
||
} }
|
||
|
||
|
||
} // namespace lumiera
|
||
|
||
/**************************************************//**
|
||
* convenience shortcut for a sequence of catch blocks
|
||
* just logging and consuming an error. Typically
|
||
* this sequence will be used within destructors,
|
||
* which, by convention, must not throw
|
||
*/
|
||
#define ERROR_LOG_AND_IGNORE(_FLAG_,_OP_DESCR_) \
|
||
catch (std::exception& problem) \
|
||
{ \
|
||
CStr errID = lumiera_error(); \
|
||
WARN (_FLAG_, "%s failed: %s", _OP_DESCR_, problem.what()); \
|
||
TRACE (debugging, "Error flag was: %s", errID);\
|
||
} \
|
||
catch (...) \
|
||
{ \
|
||
CStr errID = lumiera_error(); \
|
||
ERROR (_FLAG_, "%s failed with unknown exception; " \
|
||
"error flag is: %s" \
|
||
, _OP_DESCR_, errID?errID:"??"); \
|
||
}
|
||
|
||
#define ERROR_LOG_AND_RETHROW(_FLAG_,_OP_DESCR_) \
|
||
catch (std::exception& problem) \
|
||
{ \
|
||
CStr errID = lumiera_error(); \
|
||
WARN (_FLAG_, "%s failed: %s", _OP_DESCR_, problem.what()); \
|
||
TRACE (debugging, "Error flag was: %s", errID); \
|
||
throw; \
|
||
} \
|
||
catch (...) \
|
||
{ \
|
||
CStr errID = lumiera_error(); \
|
||
ERROR (_FLAG_, "%s failed with unknown exception; " \
|
||
"error flag is: %s" \
|
||
, _OP_DESCR_, errID?errID:"??"); \
|
||
throw; \
|
||
}
|
||
|
||
/******************************************************//**
|
||
* convenience shortcut to catch and absorb any exception,
|
||
* then returning a default value instead. This scheme is
|
||
* typically used within signal handlers in the GTK UI,
|
||
* since GTK (written in C) can not propagate exceptions
|
||
*/
|
||
#define ON_EXCEPTION_RETURN(_VAL_,_OP_DESCR_) \
|
||
catch (std::exception& problem) \
|
||
{ \
|
||
CStr errID = lumiera_error(); \
|
||
WARN (stage, "%s (Handler) failed: %s", \
|
||
_OP_DESCR_, problem.what()); \
|
||
TRACE (debugging, "Error flag was: %s", errID); \
|
||
return (_VAL_); \
|
||
} \
|
||
catch (...) \
|
||
{ \
|
||
CStr errID = lumiera_error(); \
|
||
ERROR (stage, "(Handler) %s failed with " \
|
||
"unknown exception; error flag is: %s" \
|
||
, _OP_DESCR_, errID?errID:"??"); \
|
||
return (_VAL_); \
|
||
}
|
||
|
||
|
||
|
||
/**************************************************//**
|
||
* if NoBug is used, redefine some macros
|
||
* to rather throw Lumiera Errors instead of aborting
|
||
*/
|
||
#if 0 ///////////////////////////////////TODO disabled for now. NoBug aborts are hard and may hold some locks. There are hooks to allow throwing from NoBug TODO use them....
|
||
#ifdef NOBUG_ABORT
|
||
#undef NOBUG_ABORT
|
||
#define LUMIERA_NOBUG_LOCATION \
|
||
std::string (NOBUG_BASENAME(__FILE__)) +":"+ NOBUG_STRINGIZE(__LINE__) + ", function " + __func__
|
||
#define NOBUG_ABORT \
|
||
lumiera::error::assertion_terminate (LUMIERA_NOBUG_LOCATION);
|
||
#endif
|
||
#endif
|
||
|
||
#endif // LUMIERA_ERROR_HPP_
|