Modernise Lumiera Error baseclass

This commit is contained in:
Fischlurch 2018-04-01 23:45:00 +02:00
parent d6167c1845
commit 21e47e014a
4 changed files with 177 additions and 164 deletions

View file

@ -45,11 +45,6 @@ using std::exception;
namespace lumiera {
typedef const char* CStr;
typedef const char* const CCStr;
namespace error {
/** the message shown to the user per default
@ -58,22 +53,16 @@ namespace lumiera {
* @todo to be localised
*/
inline const string
default_usermsg (Error* exception_obj) throw()
default_usermsg (Error* exception_obj) noexcept
{
return string("Sorry, Lumiera encountered an internal error. (")
+ util::typeStr(*exception_obj) + ")";
}
inline CStr
default_or_given (CCStr id)
{
return id? id : LUMIERA_ERROR_STATE;
}
CStr
detailInfo ()
{
CCStr detailinfo = lumiera_error_extra();
CStr detailinfo = lumiera_error_extra();
return isnil (detailinfo)? "Lumiera errorstate detected"
: detailinfo;
}
@ -90,16 +79,14 @@ namespace lumiera {
LUMIERA_ERROR_DEFINE (ASSERTION, "assertion failure");
/* some further generic error situations */
LUMIERA_ERROR_DEFINE (LIFECYCLE, "Lifecycle assumptions violated");
LUMIERA_ERROR_DEFINE (WRONG_TYPE, "runtime type mismatch");
LUMIERA_ERROR_DEFINE (LIFECYCLE, "Lifecycle assumptions violated");
LUMIERA_ERROR_DEFINE (WRONG_TYPE, "runtime type mismatch");
LUMIERA_ERROR_DEFINE (ITER_EXHAUST, "end of sequence reached");
LUMIERA_ERROR_DEFINE (CAPACITY, "predefined fixed storage capacity");
LUMIERA_ERROR_DEFINE (INDEX_BOUNDS, "index out of bounds");
LUMIERA_ERROR_DEFINE (BOTTOM_VALUE, "invalid or NIL value");
LUMIERA_ERROR_DEFINE (UNCONNECTED, "missing connection");
LUMIERA_ERROR_DEFINE (UNIMPLEMENTED, "using a feature not yet implemented....");
LUMIERA_ERROR_DEFINE (UNCONNECTED, "missing connection");
LUMIERA_ERROR_DEFINE (UNIMPLEMENTED,"using a feature not yet implemented....");
} // namespace error
@ -109,38 +96,29 @@ namespace lumiera {
/** @note we set the C-style errorstate as a side effect */
Error::Error (string description, CCStr id) throw()
: std::exception (),
id_ (error::default_or_given (id)),
msg_ (error::default_usermsg (this)),
desc_ (description),
cause_ ("")
{
lumiera_error_set (this->id_, description.c_str ());
}
Error::Error (string description, lumiera_err const id) noexcept
: std::exception{}
, id_{id}
, msg_{error::default_usermsg (this)}
, desc_{description}
, cause_{}
{
lumiera_error_set (this->id_, description.c_str());
}
Error::Error (std::exception const& cause,
string description, CCStr id) throw()
: std::exception (),
id_ (error::default_or_given (id)),
msg_ (error::default_usermsg (this)),
desc_ (description),
cause_ (extractCauseMsg(cause))
{
lumiera_error_set (this->id_, description.c_str ());
}
string description, lumiera_err const id) noexcept
: std::exception{}
, id_{id}
, msg_{error::default_usermsg (this)}
, desc_{description}
, cause_{extractCauseMsg(cause)}
{
lumiera_error_set (this->id_, description.c_str());
}
/** @note copy ctor behaves like chaining, i.e setting the cause_. */
Error::Error (const Error& ref) throw()
: std::exception (),
id_ (ref.id_),
msg_ (ref.msg_),
desc_ (ref.desc_),
cause_ (extractCauseMsg(ref))
{ }
/** Description of the problem, including the internal char constant
@ -149,7 +127,7 @@ namespace lumiera {
* generated output as well.
*/
CStr
Error::what() const throw()
Error::what() const noexcept
{
if (isnil (this->what_))
{
@ -165,9 +143,9 @@ namespace lumiera {
* first exception encountered in a chain of exceptions
*/
const string
Error::extractCauseMsg (const exception& cause) throw()
Error::extractCauseMsg (const exception& cause) noexcept
{
const Error* err=dynamic_cast<const Error*> (&cause);
const Error* err = dynamic_cast<const Error*> (&cause);
if (err)
{
if (isnil (err->cause_))
@ -175,7 +153,6 @@ namespace lumiera {
else
return err->cause_; // cause was caused by another exception
}
// unknown other exception type
return cause.what ();
}

View file

@ -69,16 +69,16 @@ typedef const char* lumiera_err;
* @param err name of the error without the 'LUMIERA_ERROR_' prefix (example: NO_MEMORY)
*/
#define LUMIERA_ERROR_DECLARE(err) \
extern lumiera_err LUMIERA_ERROR_##err
extern lumiera_err const LUMIERA_ERROR_##err
/**
* Definition and initialization of an error constant.
* Definition and initialisation of an error constant.
* This macro eases the error definition in implementation files
* @param err name of the error without the 'LUMIERA_ERROR_' prefix (example: NO_MEMORY)
* @param msg message describing the error in plain english (example: "memory allocation failed")
* @param msg message describing the error in plain English (example: "memory allocation failed")
*/
#define LUMIERA_ERROR_DEFINE(err, msg) \
lumiera_err LUMIERA_ERROR_##err = "LUMIERA_ERROR_" #err ":" msg
lumiera_err const LUMIERA_ERROR_##err = "LUMIERA_ERROR_" #err ":" msg
/**
* Helper macro to raise an error for the current thread.

View file

@ -28,7 +28,7 @@
** 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 non-cleare error state is detected.
** when a unclear error state is detected.
**
** @see error-state.c
** @see error.hpp
@ -42,67 +42,106 @@
#include "include/logging.h"
#include "include/lifecycle.h"
#include "lib/error.h"
#include <exception>
#include <string>
namespace lumiera {
using std::string;
using CStr = const char*;
#define LERR_(_NAME_) LUMIERA_ERROR_##_NAME_
/** error-ID for unspecified exceptions */
LUMIERA_ERROR_DECLARE(EXCEPTION);
LUMIERA_ERROR_DECLARE(EXCEPTION);
/**
* Interface and Base class of all Exceptions thrown
* from within Lumiera (C++) code. Common operations
* for getting an diagnostic message and for obtaining
* the root cause, i.e. the first exception encountered
* in a chain of exceptions.
* Interface and Base definition for all Lumiera Exceptions.
* Provides common operations for getting an diagnostic message
* and to obtaining the _root cause_ message, i.e. the message
* from the first exception encountered in a chain of exceptions.
*/
class Error : public std::exception
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:
Error (string description="", const char* const id=LUMIERA_ERROR_EXCEPTION) throw();
Error (std::exception const& cause,
string description="", const char* const id=LUMIERA_ERROR_EXCEPTION) throw();
virtual ~Error () noexcept { }; ///< this is an interface
Error (const Error&) throw();
virtual ~Error () throw() {};
/** yield a diagnostic message characterising the problem */
virtual const char* what () const throw();
Error (string description=""
,lumiera_err const id =LERR_(EXCEPTION)) noexcept;
Error (std::exception const& cause
,string description=""
,lumiera_err const id =LERR_(EXCEPTION)) noexcept;
/** the internal Lumiera-error-ID (was set as C-errorstate in ctor) */
const char* getID () const throw() { return this->id_; }
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 */
const string& getUsermsg () const throw();
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
* 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)
*/
const string& rootCause () const throw() { return this->cause_; }
string const&
rootCause () const noexcept
{
return cause_;
}
/** replace the previous or default friendly message for the user. To be localised. */
Error& setUsermsg (const string& newMsg) throw() { this->msg_ = newMsg; return *this; }
/** 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 (const string& text) throw() { this->desc_.insert (0,text); return *this; }
Error&
prependInfo (string const& text) noexcept
{
desc_.insert (0, text);
return *this;
}
private:
const char* 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
static const string extractCauseMsg (std::exception const&) throw();
static const string
extractCauseMsg (std::exception const&) noexcept;
};
@ -115,7 +154,7 @@ namespace lumiera {
namespace error {
/** global function for handling unknown exceptions
* encountered at functions declaring not to throw
* 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.
@ -148,43 +187,41 @@ namespace lumiera {
/** Macro for creating derived exception classes properly
* integrated into Lumiera's exception hierarchy. Using
* this macro assures that the new class will get the full
* set of constructors and behaviour common to all exception
* classes, so it should be used when creating an derived
* exception type for more then strictly local purposes
*/
#define LUMIERA_EXCEPTION_DECLARE(CLASS, PARENT, _ID_) \
class CLASS : public PARENT \
{ \
public: \
CLASS (std::string description="", \
const char* id=_ID_) throw() \
: PARENT (description, id) {} \
\
CLASS (std::exception const& cause, \
std::string description="", \
const char* id=_ID_) throw() \
: PARENT (cause, description, id) {} \
/**
* 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}
{ }
LumieraError (std::exception const& cause
,std::string description=""
,lumiera_err const id=eID) noexcept
: PAR{cause, description, id}
{ }
};
//-------------------------CLASS-----PARENT--ID----------------------
LUMIERA_EXCEPTION_DECLARE (Logic, Error, LUMIERA_ERROR_LOGIC);
LUMIERA_EXCEPTION_DECLARE (Fatal, Logic, LUMIERA_ERROR_FATAL);
LUMIERA_EXCEPTION_DECLARE (Config, Error, LUMIERA_ERROR_CONFIG);
LUMIERA_EXCEPTION_DECLARE (State, Error, LUMIERA_ERROR_STATE);
LUMIERA_EXCEPTION_DECLARE (Flag, State, LUMIERA_ERROR_FLAG);
LUMIERA_EXCEPTION_DECLARE (Invalid, Error, LUMIERA_ERROR_INVALID);
LUMIERA_EXCEPTION_DECLARE (External, Error, LUMIERA_ERROR_EXTERNAL);
//----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 ();
void install_unexpectedException_handler();
/** @return error detail-info if currently set, a default message else */
const char* detailInfo ();
CStr detailInfo();
} // namespace error
@ -213,22 +250,14 @@ namespace lumiera {
*/
template<class EX>
inline void
maybeThrow(string description)
maybeThrow (string description ="")
{
if (lumiera_err errorFlag =lumiera_error())
{
throw EX (error::Flag( error::detailInfo()
, errorFlag)
throw EX (error::Flag{error::detailInfo(), errorFlag}
,description);
} }
template<class EX>
inline void
maybeThrow()
{
maybeThrow<EX>("");
}
} // namespace lumiera
@ -256,7 +285,7 @@ namespace lumiera {
/**************************************************//**
* if NoBug is used, redefine some macros
* 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....

View file

@ -28,41 +28,39 @@
#include "lib/error.h"
#include "lib/error.hpp"
#include "lib/test/run.hpp"
#include "lib/format-cout.hpp"
#include "lib/util.hpp"
#include <string>
#include <iostream>
#include <exception>
#include <stdexcept>
#include <string>
using std::runtime_error;
using std::exception;
using std::string;
using std::cout;
namespace lumiera {
namespace test {
/** local specific error-constant for use in the
LUMIERA_ERROR_DEFINE (LIFE_AND_UNIVERSE, "and everything?");
/** local specific error-constant for use in the
* constructor of the nested SpecificError class.
*/
LUMIERA_ERROR_DEFINE(LIFE_AND_UNIVERSE, "and everything?");
LUMIERA_ERROR_DEFINE(DERIVED, "convoluted exception");
LUMIERA_ERROR_DECLARE(DERIVED);
LUMIERA_ERROR_DEFINE (DERIVED, "convoluted exception");
/** declare a specific Error class with parent class error::external */
LUMIERA_EXCEPTION_DECLARE (DerivedError, error::External, LUMIERA_ERROR_DERIVED);
/** define a specific Error subclass derived from error::external */
using DerivedError = error::LumieraError<LERR_(DERIVED), error::External>;
/******************************************************//**
* Some aspects of C++ exception handling.
* Not to be confused with the basic C-style error value
* Not to be confused with the basic C-style error value
* mechanism used by the low-level parts of the backend.
* Both approaches are laregely orthogonal, but the
* Both approaches are largely orthogonal, but the
* C++ exception handling uses the C-style error constants.
*
*/
@ -71,8 +69,8 @@ namespace lumiera {
typedef ExceptionError_test test;
virtual void run (Arg arg)
{
if (0 < arg.size() && arg[1]=="terminate")
terminateUnknown ();
if (0 < arg.size() and arg[1]=="terminate")
terminateUnknown();
catcher (&test::throwSpecial, "");
@ -109,22 +107,22 @@ namespace lumiera {
* which we are able to catch because it is derived from std::exception.
* We don't need to know the exact type, but we can classify the error situation
* as a "state error" and throw an error::State, passing on the root cause.
* Some levels up, this error get caught and the root cause can be
* Some levels up, this error get caught and the root cause can be
* extracted successfully.
*/
void nestedThrower (string msg) throw(Error)
void nestedThrower (string msg)
{
try { throwExternal(msg); }
catch (std::exception& e)
{
cout << "intermediate handler caught: " << e.what()
cout << "intermediate handler caught: " << e.what()
<< "....will rethrow as error::State\n";
throw error::State (e);
}
}
/** @test repeated repackaging and rethrowing */
void doubleNestedTh (string msg) throw(error::Config)
void doubleNestedTh (string msg)
{
try { nestedThrower (msg); }
catch (Error& e)
@ -142,25 +140,25 @@ namespace lumiera {
void checkErrorIntegration()
{
lumiera_error ();
CHECK (!lumiera_error ());
CHECK (not lumiera_error());
Error err1;
Error err2("boo",LUMIERA_ERROR_DERIVED);
Error err2("boo",LERR_(DERIVED));
CHECK (err1.getID () == lumiera_error ()); // (we didn't clear the first one!)
Error err3("boooo",LUMIERA_ERROR_DERIVED);
Error err3("boooo",LERR_(DERIVED));
CHECK (err3.getID () == lumiera_error ());
SpecificError err4;
CHECK (err4.getID () == LUMIERA_ERROR_LIFE_AND_UNIVERSE);
CHECK (err4.getID () == LERR_(LIFE_AND_UNIVERSE));
CHECK (err4.getID () == lumiera_error ());
CHECK (!lumiera_error ());
CHECK (not lumiera_error());
}
void detectErrorflag (string) { throwOnError(); }
void detectErrorflagChained (string msg) { maybeThrow<error::Logic>(msg); }
void detectErrorflagChained (string msg) { maybeThrow<error::Logic> (msg); }
/** @test verify throwing of Exceptions
@ -168,14 +166,14 @@ namespace lumiera {
*/
void checkErrorFlagPropagation()
{
lumiera_error_set(LUMIERA_ERROR_LIFE_AND_UNIVERSE, "what is the answer?");
lumiera_error_set(LERR_(LIFE_AND_UNIVERSE), "what is the answer?");
CHECK (lumiera_error_peek());
catcher (&test::detectErrorflag, "");
CHECK (LUMIERA_ERROR_LIFE_AND_UNIVERSE == lumiera_error_peek());
CHECK (LERR_(LIFE_AND_UNIVERSE) == lumiera_error_peek());
catcher (&test::detectErrorflagChained, "the big bang");
CHECK (LUMIERA_ERROR_LIFE_AND_UNIVERSE == lumiera_error());
CHECK (LERR_(LIFE_AND_UNIVERSE) == lumiera_error());
}
@ -186,8 +184,8 @@ namespace lumiera {
{
error::Logic err1;
error::Config err2(err1);
error::Config err3(err2); //note: using copy ctor behaves like chaining
Error err4(err1); // note: copy ctor
error::Config err3(err2);
Error err4(err1); // note: copy ctor
std::runtime_error rerr("what a shame");
error::External err5(rerr);
@ -195,7 +193,7 @@ namespace lumiera {
CHECK (err2.rootCause() == err1.what());
CHECK (err3.rootCause() == err1.what());
CHECK (err4.rootCause() == err1.what());
CHECK (err4.rootCause() == err1.rootCause()); // mere copy is not a root cause
CHECK (err5.rootCause() == rerr.what());
CHECK (err6.rootCause() == rerr.what());
@ -204,31 +202,40 @@ namespace lumiera {
/** @test terminate the Application by throwing an undeclared exception.
* this should result in the global unknown() handler to be called,
* so usually it will terminate the test run.
* so usually it will terminate the test run.
* @note inside error.hpp, an initialisation hook has been installed into
* AppState, causing our own unknown() handler to be installed and
* invoked, which gives additional diagnostics.*/
void terminateUnknown () throw()
void terminateUnknown () noexcept
{
throw Error("You'll never get me, won't you?");
}
/** a very specific Exception class
* local to this scope and with
* local to this scope and with
* additional behaviour.
*/
class SpecificError : public error::Invalid
*/
class SpecificError
: public error::Invalid
{
int value;
int value_;
public:
SpecificError () : Invalid("don't panic",LUMIERA_ERROR_LIFE_AND_UNIVERSE), value(42) {}
int revealIt () { return value; }
SpecificError()
: error::Invalid{"don't panic", LUMIERA_ERROR_LIFE_AND_UNIVERSE}
, value_(42)
{ }
int
revealIt()
{
return value_;
}
};
/** helper: provides a bunch of catch-clauses and
/** helper: provides a bunch of catch-clauses and
* runs the given member functions within
*/
void catcher (void (ExceptionError_test::*funky)(string), string context)