1
0
forked from Alepha/Alepha

First draft of more streamlined tagged Exceptions.

This commit is contained in:
2022-10-16 01:52:16 -04:00
parent e170f3f172
commit 8a75be721e
3 changed files with 431 additions and 0 deletions

329
Exception.h Normal file
View File

@ -0,0 +1,329 @@
static_assert( __cplusplus > 2020'00 );
#pragma once
#include <Alepha/Alepha.h>
#include <cstdlib>
#include <string>
#include <exception>
#include <string_view>
#include <typeindex>
namespace Alepha::Hydrogen
{
namespace detail::exceptions::inline exports
{
/*!
* @file
*
* Exception grades.
*
* In Alepha, there are several "grades" of exception types which can be thrown. The grade of
* an exception is "branded" into its name as the last word in camel case.
*
* * `Throwable`: All exceptions inherit from this interface. Catching this will
* catch anything which is part of this system. Normally you just ignore this grade.
*
* * `Notification`: When a thread is cancelled using interrupts, all exceptions thrown
* to do so are derived from this type. Alepha threads are setup to catch and discard
* exceptions of this grade in the thread start function.
*
* * `Exception`: This is the exception grade you would typically want to recover from.
* Catch this grade, typically. They should contain sufficient information in their
* data pack to facilitate a proper programmatic recovery. All exceptions of this grade
* will also have `std::exception` as one of its bases, thus code which is unaware of
* Alepha exceptions, but handles basic standard exceptions cleanly, will work just fine.
*
* * `Error`: This exception grade represents a form of moderately-unrecoverable condition.
* The `Error` grade typically indicates a condition that prevents the current thread or
* program state from being easily transitioned to a recovered state. However, a very
* large state transform, such as a top-of-thread-callstack handler, may be able to
* recover. For example, no more available operating system file handles. Tearing down
* several client handlers in a multi-client server might alleviate this condition.
*
* * `Violation`: This exception grade represents an impossible to recover from condition.
* Think of the handler for `Violation` as-if it were a `[[noreturn]]` function. Catch
* handlers for this grade should only clean up resources which might cause persistent
* state corruption. A program probably SHOULD ignore these and allow a core dump to
* be emitted. An example of this might be: Runtime detection of dereference of a
* raw pointer set to `nullptr`. This would typically indicate some kind of program
* bug. `Violation::~Violation` is setup to call `abort()` when called.
*/
class Throwable
{
public:
virtual ~Throwable()= default;
virtual const char *message() const noexcept= 0;
template< typename Target >
const Target &
as() const
{
if( not is_a< const Target * >() )
{
// TODO: Structured exception recovery here...
}
return dynamic_cast< const Target & >( *this );
}
template< typename Target >
bool
is_a() const noexcept
{
return dynamic_cast< const Target * >( this );
}
};
class AnyTaggedThrowable
: virtual public Throwable
{
public:
virtual std::type_index tag() const noexcept= 0;
};
template< typename Tag >
class TaggedThrowable
: virtual public AnyTaggedThrowable
{
public:
std::type_index
tag() const noexcept final
{
return typeid( std::type_identity< Tag > );
}
};
template< typename ... Bases >
struct bases : virtual public Bases... {};
// `Notification`s are "events" or "interrupts" that
// if ignored will gracefully terminate the current
// thread, but not halt the program.
class Notification : public virtual Throwable {};
class AnyTaggedNotification
: public virtual bases< Notification, AnyTaggedThrowable > {};
template< typename tag >
class TaggedNotification
: public virtual bases< TaggedThrowable< tag >, AnyTaggedNotification > {};
using Interrupt= Notification;
using AnyTaggedInterrupt= AnyTaggedNotification;
template< typename tag >
using TaggedInterrupt= TaggedNotification< tag >;
// `Exception`s are recoverable at any point.
class ExceptionBridgeInterface
{
public:
virtual ~ExceptionBridgeInterface()= default;
virtual const char *what() const noexcept= 0;
};
class Exception : virtual public bases< Throwable >, virtual private ExceptionBridgeInterface { public: using ExceptionBridgeInterface::what; };
class AnyTaggedException : virtual public bases< Exception, AnyTaggedThrowable > {};
template< typename tag >
class TaggedException : virtual public bases< TaggedThrowable< tag >, AnyTaggedException > {};
// `Error`s are only really recoverable by terminating
// the major procedure underway. Like terminating the
// entire thread or similar. Essentially, arbitrarily
// localized recovery is impossible.
class Error: virtual public bases< Throwable > {};
class AnyTaggedError : virtual public bases< Error, AnyTaggedThrowable > {};
template< typename tag >
class TaggedError : virtual bases< AnyTaggedError, TaggedThrowable< tag > > {};
// `Violation`s are unrecoverable events which happen to
// the process. They are impossible to recover from.
// Handlers for this should be treated almost like
// `[[noreturn]]` functions. Mostly one would catch
// this class if they intended to perform a bit of local
// persistent state sanitization and then continue the
// unwind process
class Violation
: virtual public bases< Throwable >
{
private:
bool active= true;
public:
~Violation() override { if( not active ) abort(); }
Violation( const Violation &copy )= delete;
Violation( Violation &copy ) : active( copy.active ) { copy.active= false; }
};
class AnyTaggedViolation : virtual public bases< Violation, AnyTaggedThrowable > {};
template< typename tag >
class TaggedViolation : virtual public bases< AnyTaggedViolation, TaggedThrowable< tag > > {};
template< typename T >
concept DerivedFromException= std::is_base_of_v< Exception, T >;
class NamedResourceThrowable
: public virtual bases< Throwable >
{
public:
virtual std::string_view resourceName() const noexcept= 0;
};
class AnyTaggedNamedResourceThrowable
: virtual public bases< NamedResourceThrowable, AnyTaggedThrowable > {};
template< typename tag >
class TaggedNamedResourceThrowable
: virtual public bases< AnyTaggedNamedResourceThrowable, TaggedThrowable< tag > > {};
class NamedResourceNotification
: public virtual bases< Notification, NamedResourceThrowable > {};
class AnyTaggedNamedResourceNotification
: public virtual bases< NamedResourceNotification, AnyTaggedNotification, AnyTaggedNamedResourceThrowable > {};
template< typename tag >
class TaggedNamedResourceNotification
: public virtual bases< AnyTaggedNamedResourceNotification, TaggedNotification< tag >, TaggedNamedResourceThrowable< tag > > {};
class NamedResourceException
: public virtual bases< Exception, NamedResourceThrowable > {};
class AnyTaggedNamedResourceException
: public virtual bases< NamedResourceException, AnyTaggedException, AnyTaggedNamedResourceThrowable > {};
template< typename tag >
class TaggedNamedResourceException
: public virtual bases< AnyTaggedNamedResourceException, TaggedException< tag >, TaggedNamedResourceThrowable< tag > > {};
class NamedResourceError
: public virtual bases< Error, NamedResourceThrowable > {};
class AnyTaggedNamedResourceError
: public virtual bases< NamedResourceError, AnyTaggedError, AnyTaggedNamedResourceThrowable > {};
template< typename tag >
class TaggedNamedResourceError
: public virtual bases< AnyTaggedNamedResourceError, TaggedError< tag >, TaggedNamedResourceThrowable< tag > > {};
class NamedResourceViolation
: public virtual bases< Violation, NamedResourceThrowable > {};
class AnyTaggedNamedResourceViolation
: public virtual bases< NamedResourceViolation, AnyTaggedViolation, AnyTaggedNamedResourceThrowable > {};
template< typename tag >
class TaggedNamedResourceViolation
: public virtual bases< AnyTaggedNamedResourceViolation, TaggedViolation< tag >, TaggedNamedResourceThrowable< tag > > {};
class AllocationThrowable
: public virtual bases< Throwable >
{
public:
virtual std::size_t allocationAmount() const noexcept= 0;
};
class AnyTaggedAllocationThrowable
: public virtual bases< AllocationThrowable, AnyTaggedThrowable > {};
template< typename tag >
class TaggedAllocationThrowable
: public virtual bases< AnyTaggedAllocationThrowable, TaggedThrowable< tag > > {};
class AllocationException
: virtual public bases< Exception, AllocationThrowable > {};
class AnyTaggedAllocationException
: virtual public bases< AllocationException, AnyTaggedAllocationThrowable, AnyTaggedException > {};
template< typename tag >
class TaggedAllocationException
: virtual public bases< AnyTaggedAllocationException, TaggedAllocationThrowable< tag >, TaggedException< tag > > {};
class AllocationError
: virtual public bases< Error, AllocationThrowable > {};
class AnyTaggedAllocationError
: virtual public bases< AllocationError, AnyTaggedAllocationThrowable, AnyTaggedError > {};
template< typename tag >
class TaggedAllocationError
: virtual public bases< AnyTaggedAllocationError, TaggedAllocationThrowable< tag >, TaggedError< tag > > {};
class AllocationViolation
: virtual public bases< Violation, AllocationThrowable > {};
class AnyTaggedAllocationViolation
: virtual public bases< AllocationViolation, AnyTaggedAllocationThrowable, AnyTaggedViolation > {};
template< typename tag >
class TaggedAllocationViolation
: virtual public bases< AnyTaggedAllocationViolation, TaggedAllocationThrowable< tag >, TaggedViolation< tag > > {};
class MessageStorage
: virtual public Throwable
{
private:
std::string storage;
public:
explicit MessageStorage( std::string storage ) : storage( std::move( storage ) ) {}
const char *message() const noexcept { return storage.c_str(); }
};
class AllocationAmountStorage
: virtual public AllocationThrowable
{
private:
std::size_t storage;
public:
std::size_t allocationAmount() const noexcept final { return storage; }
};
class AllocationExceptionBridge
: virtual public std::bad_alloc, public virtual ExceptionBridgeInterface, virtual public Exception
{
public:
const char *what() const noexcept override { return message(); }
};
class RegularExceptionBridge
: virtual public std::exception, public virtual ExceptionBridgeInterface, virtual public Exception
{
public:
const char *what() const noexcept override { return message(); }
};
template< typename Kind >
auto
build_exception( std::string message )
{
if constexpr( false ) {}
else if constexpr( std::is_base_of_v< AllocationException, Kind > )
{
class Exception
: virtual public Kind, virtual private AllocationExceptionBridge, virtual private MessageStorage, virtual private AllocationAmountStorage
{
public:
explicit Exception( std::string message ) : MessageStorage( std::move( message ) ) {}
};
return Exception{ std::move( message ) };
}
else if constexpr( std::is_base_of_v< Exception, Kind > )
{
class Exception
: virtual public Kind, virtual private RegularExceptionBridge, virtual private MessageStorage
{
public:
explicit Exception( std::string message ) : MessageStorage( std::move( message ) ) {}
};
return Exception{ std::move( message ) };
}
else if constexpr( true )
{
class Thrown
: virtual public Kind, virtual private MessageStorage
{
explicit Thrown( std::string message ) : MessageStorage( std::move( message ) ) {}
};
return Thrown{ std::move( message ) };
}
}
}
inline namespace exports {}
namespace exports::inline exceptions
{
using namespace detail::exceptions::exports;
}
}

8
Exception.test/Makefile Normal file
View File

@ -0,0 +1,8 @@
CXXFLAGS+= -std=c++2a -I ../
CXXFLAGS+= -g -O0
CXX=clang++-12
CXXFLAGS+= -Wno-inline-namespace-reopened-noninline
CXXFLAGS+= -Wno-unused-comparison
all: exception

View File

@ -0,0 +1,94 @@
static_assert( __cplusplus > 2020'00 );
#include <Alepha/Exception.h>
#include <type_traits>
#include <Alepha/Testing/test.h>
#include <Alepha/Testing/TableTest.h>
#include <Alepha/Utility/evaluation.h>
namespace
{
using Alepha::exports::types::argcnt_t;
using Alepha::exports::types::argvec_t;
}
int
main( const argcnt_t argcnt, const argvec_t argvec )
{
return Alepha::Testing::runAllTests( argcnt, argvec );
}
namespace
{
namespace util= Alepha::Utility;
using namespace Alepha::Testing::exports;
struct tag;
template< typename CatchException >
void
testException()
{
try
{
throw Alepha::build_exception< Alepha::TaggedAllocationException< struct tag > >( "This is my allocation exception" );
}
catch( const CatchException &ex )
{
std::cout << "Caught as `" << boost::core::demangle( typeid( CatchException ).name() ) << "` : ";
if constexpr( std::is_base_of_v< std::exception, CatchException > ) std::cout << ex.what();
else std::cout << ex.message();
std::cout << std::endl;
if constexpr( std::is_base_of_v< Alepha::AnyTaggedThrowable, CatchException > )
{
std::cout << "Tag type: `" << boost::core::demangle( ex.tag().name() ) << '`' << std::endl;
}
//std::cout << "Local tag type: `" << typeid( std::type_identity< struct tag > ).name() << '`' << std::endl;
}
}
template< typename BuildException, typename CatchException >
bool
catchable()
{
try
{
throw Alepha::build_exception< BuildException >( "This is my allocation exception" );
}
catch( const CatchException &ex ) { std::cerr << "Caught" << std::endl; return true; }
catch( ... ) { std::cerr << "Not caught" << std::endl; return false; }
}
auto tests= Alepha::Utility::enroll <=[]
{
"smoke"_test <=[] () -> bool
{
testException< std::bad_alloc >();
testException< Alepha::TaggedException< tag > >();
testException< Alepha::AnyTaggedException >();
testException< std::exception >();
testException< Alepha::Exception >();
testException< Alepha::Throwable >();
return true;
};
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, std::bad_alloc >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, std::exception >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::Throwable >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::AnyTaggedThrowable >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::TaggedThrowable< tag > >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::Exception >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::AnyTaggedException >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::TaggedException< tag > >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::AllocationThrowable >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::AnyTaggedAllocationThrowable >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::TaggedAllocationThrowable< tag > >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::AllocationException >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::AnyTaggedAllocationException >;
"catch"_test <=catchable< Alepha::TaggedAllocationException< tag >, Alepha::TaggedAllocationException< tag > >;
};
}