text: #pragma once
  #include "threadalloc.h"
  
  namespace snmalloc
  {
    /**
     * Should we check loads?  This defaults to on in debug builds, off in
     * release (store-only checks) and can be overridden by defining the macro
     * `SNMALLOC_CHECK_LOADS` to true or false.
     */
    static constexpr bool CheckReads =
  #ifdef SNMALLOC_CHECK_LOADS
      SNMALLOC_CHECK_LOADS
  #else
      DEBUG
  #endif
      ;
  
    /**
     * Should we fail fast when we encounter an error?  With this set to true, we
     * just issue a trap instruction and crash the process once we detect an
     * error. With it set to false we print a helpful error message and then crash
     * the process.  The process may be in an undefined state by the time the
     * check fails, so there are potentially security implications to turning this
     * off. It defaults to true for debug builds, false for release builds and
     * can be overridden by defining the macro `SNMALLOC_FAIL_FAST` to true or
     * false.
     */
    static constexpr bool FailFast =
  #ifdef SNMALLOC_FAIL_FAST
      SNMALLOC_FAIL_FAST
  #else
      !DEBUG
  #endif
      ;
  
    /**
     * Report an error message for a failed bounds check and then abort the
     * program.
     * `p` is the input pointer and `len` is the offset from this pointer of the
     * bounds.  `msg` is the message that will be reported along with the
     * start and end of the real object's bounds.
     */
    SNMALLOC_SLOW_PATH SNMALLOC_UNUSED_FUNCTION inline void
      report_fatal_bounds_error [[noreturn]] (
        void* p, size_t len, const char* msg, decltype(ThreadAlloc::get())& alloc)
    {
      report_fatal_error(
        "{}: {} is in allocation {}--{}, offset {} is past the end\n",
        msg,
        p,
        alloc.template external_pointer<Start>(p),
        alloc.template external_pointer<OnePastEnd>(p),
        len);
    }
  
    /**
     * The direction for a bounds check.
     */
    enum class CheckDirection
    {
      /**
       * A read bounds check, performed only when read checks are enabled.
       */
      Read,
  
      /**
       * A write bounds check, performed unconditionally.
       */
      Write
    };
  
    /**
     * Check whether a pointer + length is in the same object as the pointer.
     * Fail with the error message from the third argument if not.
     *
     * The template parameter indicates whether this is a read.  If so, this
     * function is a no-op when `CheckReads` is false.
     */
    template<
      CheckDirection Direction = CheckDirection::Write,
      bool CheckBoth = CheckReads>
    SNMALLOC_FAST_PATH_INLINE void
    check_bounds(const void* ptr, size_t len, const char* msg = "")
    {
      if constexpr ((Direction == CheckDirection::Write) || CheckBoth)
      {
        auto& alloc = ThreadAlloc::get();
        void* p = const_cast<void*>(ptr);
  
        if (SNMALLOC_UNLIKELY(!alloc.check_bounds(ptr, len)))
        {
          if constexpr (FailFast)
          {
            UNUSED(p, len, msg);
            SNMALLOC_FAST_FAIL();
          }
          else
          {
            report_fatal_bounds_error(p, len, msg, alloc);
          }
        }
      }
      else
      {
        UNUSED(ptr, len, msg);
      }
    }
  
  } // namespace snmalloc
  