#pragma once #include #include #include namespace Mikage { namespace Exceptions { std::string generate_backtrace(); /** * Exception to throw upon violation internal contracts that are expected to * hold regardless of invalid emulation or user inputs. * * ValidateContract provides a drop-in replacement for assert that throws * exceptions of this kind. */ struct ContractViolated : std::runtime_error { ContractViolated(std::string_view condition, std::string_view function, std::string_view file, int line) : std::runtime_error(FormatMessage(condition, function, file, line) + generate_backtrace()) { } static std::string FormatMessage(std::string_view condition, std::string_view function, std::string_view file, int line); }; /** * Exception to throw when the given situation is valid but handling it is not * implemented currently. * * Examples of this are unhandled (but valid) enum inputs to emulated APIs or * unhandled corner cases in internal libraries. * * This exception may also be used in situations where it's not entirely clear * whether the behavior may or may not be technically valid. */ struct NotImplemented : std::runtime_error { template NotImplemented(const char* message, T&&... ts) : std::runtime_error(fmt::format(fmt::runtime(message), std::forward(ts)...) + "\n" + generate_backtrace()) { } }; /** * Exception to throw when the given situation is invalid, such as by violating * preconditions of an emulated API. * * Examples of this are out-of-range parameters and invalid enum values. * * When it's not entirely clear whether a situation should be considered * valid or not, prefer NotImplemented. */ struct Invalid : std::runtime_error { template Invalid(const char* message, T&&... ts) : std::runtime_error(fmt::format(fmt::runtime(message), std::forward(ts)...) + "\n" + generate_backtrace()) { } }; /** * Exception to throw when the user provides invalid input, for example from * a command-line argument or a configuration file. * * Emulation logic should never use this. */ struct InvalidUserInput : std::runtime_error { template InvalidUserInput(const char* message, T&&... ts) : std::runtime_error(fmt::format(fmt::runtime(message), std::forward(ts)...) + "\n" + generate_backtrace()) { } }; } // namespace Exceptions } // namespace Mikage // ValidateContract is implemented as a macro so that we can stringify the failed condition #ifdef NDEBUG #define ValidateContract(cond) do { if (!(cond)) { \ throw Mikage::Exceptions::ContractViolated(#cond, __PRETTY_FUNCTION__, __FILE__, __LINE__); \ } } while (false) #else // Use assert() directly in debug mode #define ValidateContract(cond) do { if (!(cond)) { \ fputs(Mikage::Exceptions::generate_backtrace().c_str(), stderr); \ assert((cond)); \ } } while (false) #endif