diff --git a/Exception.h b/Exception.h new file mode 100644 index 0000000..31a4519 --- /dev/null +++ b/Exception.h @@ -0,0 +1,329 @@ +static_assert( __cplusplus > 2020'00 ); + +#pragma once + +#include + +#include + +#include +#include +#include +#include + +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 © )= delete; + Violation( Violation © ) : 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; + } +} diff --git a/Exception.test/Makefile b/Exception.test/Makefile new file mode 100644 index 0000000..3141187 --- /dev/null +++ b/Exception.test/Makefile @@ -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 diff --git a/Exception.test/exception.cc b/Exception.test/exception.cc new file mode 100644 index 0000000..a9883f8 --- /dev/null +++ b/Exception.test/exception.cc @@ -0,0 +1,94 @@ +static_assert( __cplusplus > 2020'00 ); + +#include + +#include + +#include +#include +#include + +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 > >; + }; +}