check.h raw

   1  // Copyright (c) 2019-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #ifndef LIMENKA_UTIL_CHECK_H
   6  #define LIMENKA_UTIL_CHECK_H
   7  
   8  #include <attributes.h>
   9  
  10  #include <cassert> // IWYU pragma: export
  11  #include <stdexcept>
  12  #include <string>
  13  #include <string_view>
  14  #include <utility>
  15  
  16  constexpr bool G_FUZZING{
  17  #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
  18      true
  19  #else
  20      false
  21  #endif
  22  };
  23  
  24  extern bool g_detail_test_only_CheckFailuresAreExceptionsNotAborts;
  25  struct test_only_CheckFailuresAreExceptionsNotAborts {
  26      test_only_CheckFailuresAreExceptionsNotAborts() { g_detail_test_only_CheckFailuresAreExceptionsNotAborts = true; };
  27      ~test_only_CheckFailuresAreExceptionsNotAborts() { g_detail_test_only_CheckFailuresAreExceptionsNotAborts = false; };
  28  };
  29  
  30  std::string StrFormatInternalBug(std::string_view msg, std::string_view file, int line, std::string_view func);
  31  
  32  class NonFatalCheckError : public std::runtime_error
  33  {
  34  public:
  35      NonFatalCheckError(std::string_view msg, std::string_view file, int line, std::string_view func);
  36  };
  37  
  38  /** Helper for CHECK_NONFATAL() */
  39  template <typename T>
  40  T&& inline_check_non_fatal(LIFETIMEBOUND T&& val, const char* file, int line, const char* func, const char* assertion)
  41  {
  42      if (!val) {
  43          throw NonFatalCheckError{assertion, file, line, func};
  44      }
  45      return std::forward<T>(val);
  46  }
  47  
  48  #if defined(NDEBUG)
  49  #error "Cannot compile without assertions!"
  50  #endif
  51  
  52  /** Helper for Assert() */
  53  void assertion_fail(std::string_view file, int line, std::string_view func, std::string_view assertion);
  54  
  55  /** Helper for Assert()/Assume() */
  56  template <bool IS_ASSERT, typename T>
  57  constexpr T&& inline_assertion_check(LIFETIMEBOUND T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion)
  58  {
  59      if (IS_ASSERT || std::is_constant_evaluated() || G_FUZZING
  60  #ifdef ABORT_ON_FAILED_ASSUME
  61          || true
  62  #endif
  63      ) {
  64          if (!val) {
  65              assertion_fail(file, line, func, assertion);
  66          }
  67      }
  68      return std::forward<T>(val);
  69  }
  70  
  71  // All macros may use __func__ inside a lambda, so put them under nolint.
  72  // NOLINTBEGIN(bugprone-lambda-function-name)
  73  
  74  #define STR_INTERNAL_BUG(msg) StrFormatInternalBug((msg), __FILE__, __LINE__, __func__)
  75  
  76  /**
  77   * Identity function. Throw a NonFatalCheckError when the condition evaluates to false
  78   *
  79   * This should only be used
  80   * - where the condition is assumed to be true, not for error handling or validating user input
  81   * - where a failure to fulfill the condition is recoverable and does not abort the program
  82   *
  83   * For example in RPC code, where it is undesirable to crash the whole program, this can be generally used to replace
  84   * asserts or recoverable logic errors. A NonFatalCheckError in RPC code is caught and passed as a string to the RPC
  85   * caller, which can then report the issue to the developers.
  86   */
  87  #define CHECK_NONFATAL(condition) \
  88      inline_check_non_fatal(condition, __FILE__, __LINE__, __func__, #condition)
  89  
  90  /** Identity function. Abort if the value compares equal to zero */
  91  #define Assert(val) inline_assertion_check<true>(val, __FILE__, __LINE__, __func__, #val)
  92  
  93  /**
  94   * Assume is the identity function.
  95   *
  96   * - Should be used to run non-fatal checks. In debug builds it behaves like
  97   *   Assert()/assert() to notify developers and testers about non-fatal errors.
  98   *   In production it doesn't warn or log anything.
  99   * - For fatal errors, use Assert().
 100   * - For non-fatal errors in interactive sessions (e.g. RPC or command line
 101   *   interfaces), CHECK_NONFATAL() might be more appropriate.
 102   */
 103  #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
 104  
 105  /**
 106   * NONFATAL_UNREACHABLE() is a macro that is used to mark unreachable code. It throws a NonFatalCheckError.
 107   */
 108  #define NONFATAL_UNREACHABLE()                                        \
 109      throw NonFatalCheckError(                                         \
 110          "Unreachable code reached (non-fatal)", __FILE__, __LINE__, __func__)
 111  
 112  // NOLINTEND(bugprone-lambda-function-name)
 113  
 114  #endif // LIMENKA_UTIL_CHECK_H
 115