/****************************************************************************** * Top contributors (to current version): * Tim King, Mathias Preiner, Andres Noetzli * * This file is part of the cvc5 project. * * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS * in the top-level source directory and their institutional affiliations. * All rights reserved. See the file COPYING in the top-level source * directory for licensing information. * **************************************************************************** * * Assertion utility classes, functions and macros. * * Assertion macros assert a condition and aborts() the process if the * condition is not satisfied. These macro leave a hanging ostream for the user * to specify additional information about the failure. * * Example usage: * AlwaysAssert(x >= 0) << "x must be positive."; * * Assert is an AlwaysAssert that is only enabled in debug builds. * Assert(pointer != nullptr); * * CVC5_FATAL() can be used to indicate unreachable code. * * Note: The AlwaysAssert and Assert macros are not safe for use in * signal-handling code. */ #include "cvc5_private_library.h" #ifndef CVC5__CHECK_H #define CVC5__CHECK_H #include #include #include "base/exception.h" #include "cvc4_export.h" // Define CVC5_NO_RETURN macro replacement for [[noreturn]]. #if defined(SWIG) #define CVC5_NO_RETURN // SWIG does not yet support [[noreturn]] so emit nothing instead. #else #define CVC5_NO_RETURN [[noreturn]] // Not checking for whether the compiler supports [[noreturn]] using // __has_cpp_attribute as GCC 4.8 is too widespread and does not support this. // We instead assume this is C++11 (or later) and [[noreturn]] is available. #endif // defined(SWIG) // Define CVC5_PREDICT_FALSE(x) that helps the compiler predict that x will be // false (if there is compiler support). #ifdef __has_builtin #if __has_builtin(__builtin_expect) #define CVC5_PREDICT_FALSE(x) (__builtin_expect(x, false)) #define CVC5_PREDICT_TRUE(x) (__builtin_expect(x, true)) #else #define CVC5_PREDICT_FALSE(x) x #define CVC5_PREDICT_TRUE(x) x #endif #else #define CVC5_PREDICT_FALSE(x) x #define CVC5_PREDICT_TRUE(x) x #endif #ifdef __has_cpp_attribute #if __has_cpp_attribute(fallthrough) #define CVC5_FALLTHROUGH [[fallthrough]] #endif // __has_cpp_attribute(fallthrough) #endif // __has_cpp_attribute #ifndef CVC5_FALLTHROUGH #define CVC5_FALLTHROUGH #endif namespace cvc5 { // Implementation notes: // To understand FatalStream and OStreamVoider, it is useful to understand // how a AlwaysAssert is structured. AlwaysAssert(cond) is roughly the following // pattern: // cond ? (void)0 : OstreamVoider() & FatalStream().stream() // This is a carefully crafted message to achieve a hanging ostream using // operator precedence. The line `AlwaysAssert(cond) << foo << bar;` will bind // as follows: // `cond ? ((void)0) : (OSV() & ((FS().stream() << foo) << bar));` // Once the expression is evaluated, the destructor ~FatalStream() of the // temporary object is then run, which abort()'s the process. The role of the // OStreamVoider() is to match the void type of the true branch. // Class that provides an ostream and whose destructor aborts! Direct usage of // this class is discouraged. class CVC4_EXPORT FatalStream { public: FatalStream(const char* function, const char* file, int line); CVC5_NO_RETURN ~FatalStream(); std::ostream& stream(); private: void Flush(); }; // Helper class that changes the type of an std::ostream& into a void. See // "Implementation notes" for more information. class OstreamVoider { public: OstreamVoider() {} // The operator precedence between operator& and operator<< is critical here. void operator&(std::ostream&) {} }; // CVC5_FATAL() always aborts a function and provides a convenient way of // formatting error messages. This can be used instead of a return type. // // Example function that returns a type Foo: // Foo bar(T t) { // switch(t.type()) { // ... // default: // CVC5_FATAL() << "Unknown T type " << t.enum(); // } // } #define CVC5_FATAL() \ FatalStream(__PRETTY_FUNCTION__, __FILE__, __LINE__).stream() /* GCC <= 9.2 ignores CVC5_NO_RETURN of ~FatalStream() if * used in template classes (e.g., CDHashMap::save()). As a workaround we * explicitly call abort() to let the compiler know that the * corresponding function call will not return. */ #define SuppressWrongNoReturnWarning abort() // If `cond` is true, log an error message and abort the process. // Otherwise, does nothing. This leaves a hanging std::ostream& that can be // inserted into. #define CVC5_FATAL_IF(cond, function, file, line) \ CVC5_PREDICT_FALSE(!(cond)) \ ? (void)0 : OstreamVoider() & FatalStream(function, file, line).stream() // If `cond` is false, log an error message and abort()'s the process. // Otherwise, does nothing. This leaves a hanging std::ostream& that can be // inserted into using operator<<. Example usages: // AlwaysAssert(x >= 0); // AlwaysAssert(x >= 0) << "x must be positive"; // AlwaysAssert(x >= 0) << "expected a positive value. Got " << x << " // instead"; #define AlwaysAssert(cond) \ CVC5_FATAL_IF(!(cond), __PRETTY_FUNCTION__, __FILE__, __LINE__) \ << "Check failure\n\n " << #cond << "\n" // Assert is a variant of AlwaysAssert() that is only checked when // CVC5_ASSERTIONS is defined. We rely on the optimizer to remove the deadcode. #ifdef CVC5_ASSERTIONS #define Assert(cond) AlwaysAssert(cond) #else #define Assert(cond) \ CVC5_FATAL_IF(false, __PRETTY_FUNCTION__, __FILE__, __LINE__) #endif class AssertArgumentException : public Exception { protected: AssertArgumentException() : Exception() {} void construct(const char* header, const char* extra, const char* function, const char* file, unsigned line, const char* fmt, va_list args); void construct(const char* header, const char* extra, const char* function, const char* file, unsigned line); public: AssertArgumentException(const char* condStr, const char* argDesc, const char* function, const char* file, unsigned line, const char* fmt, ...); AssertArgumentException(const char* condStr, const char* argDesc, const char* function, const char* file, unsigned line); }; /* class AssertArgumentException */ #define Unreachable() CVC5_FATAL() << "Unreachable code reached" #define Unhandled() CVC5_FATAL() << "Unhandled case encountered" #define Unimplemented() CVC5_FATAL() << "Unimplemented code encountered" #define InternalError() CVC5_FATAL() << "Internal error detected" #define IllegalArgument(arg, msg...) \ throw ::cvc5::IllegalArgumentException( \ "", \ #arg, \ __PRETTY_FUNCTION__, \ ::cvc5::IllegalArgumentException::formatVariadic(msg).c_str()); // This cannot use check argument directly as this forces // CheckArgument to use a va_list. This is unsupported in Swig. #define PrettyCheckArgument(cond, arg, msg...) \ do \ { \ if (__builtin_expect((!(cond)), false)) \ { \ throw ::cvc5::IllegalArgumentException( \ #cond, \ #arg, \ __PRETTY_FUNCTION__, \ ::cvc5::IllegalArgumentException::formatVariadic(msg).c_str()); \ } \ } while (0) #define AlwaysAssertArgument(cond, arg, msg...) \ do \ { \ if (__builtin_expect((!(cond)), false)) \ { \ throw ::cvc5::AssertArgumentException( \ #cond, #arg, __PRETTY_FUNCTION__, __FILE__, __LINE__, ##msg); \ } \ } while (0) #ifdef CVC5_ASSERTIONS #define AssertArgument(cond, arg, msg...) AlwaysAssertArgument(cond, arg, ##msg) #define DebugCheckArgument(cond, arg, msg...) CheckArgument(cond, arg, ##msg) #else /* ! CVC5_ASSERTIONS */ #define AssertArgument(cond, arg, msg...) /*__builtin_expect( ( cond ), true \ )*/ #define DebugCheckArgument( \ cond, arg, msg...) /*__builtin_expect( ( cond ), true )*/ #endif /* CVC5_ASSERTIONS */ } // namespace cvc5 #endif /* CVC5__CHECK_H */