/* xspan -- a minimally invasive checked memory smart pointer

   This file is part of the UPX executable compressor.

   Copyright (C) Markus Franz Xaver Johannes Oberhumer
   All Rights Reserved.

   UPX and the UCL library are free software; you can redistribute them
   and/or modify them under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.
   If not, write to the Free Software Foundation, Inc.,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   Markus F.X.J. Oberhumer
   <markus@oberhumer.com>
 */

/*************************************************************************
//
**************************************************************************/

#if CLANG_FORMAT_DUMMY_CLASS
template <class T>
class CSelf {
#endif

public:
    // befriend all
    template <class>
    friend struct PtrOrSpan;
    template <class>
    friend struct PtrOrSpanOrNull;
    template <class>
    friend struct Span;

    typedef T element_type;
    typedef typename std::add_lvalue_reference<T>::type reference;
    typedef typename std::add_pointer<T>::type pointer;
    typedef size_t size_type;

#if XSPAN_CONFIG_ENABLE_IMPLICIT_CONVERSION
public:
    operator pointer() const noexcept { return ptr; }
#endif

private:
#if XSPAN_CONFIG_ENABLE_DEBUG
    XSpanDebugFile f;
#endif
    pointer ptr; // current view into (base, base+size_in_bytes) iff base != nullptr
    pointer base;
    size_type size_in_bytes;

// debug - internal sanity check; also serves as pseudo-documentation
#if DEBUG
    noinline void assertInvariants() const may_throw {
        if __acc_cte (configRequirePtr)
            assert(ptr != nullptr);
        if __acc_cte (configRequireBase)
            assert(base != nullptr);
        if __acc_cte ((configRequirePtr || ptr != nullptr) &&
                      (configRequireBase || base != nullptr))
            xspan_check_range(ptr, base, size_in_bytes);
    }
#else
forceinline void assertInvariants() const noexcept {}
#endif

    static inline pointer makeNotNull(pointer p) {
        if very_unlikely (p == nullptr)
            xspan_fail_nullptr();
        return p;
    }
    // enforce config invariants at constructor time - static functions
    static inline pointer makePtr(pointer p) {
        if __acc_cte (configRequirePtr && p == nullptr)
            xspan_fail_nullptr();
        return p;
    }
    static inline pointer makeBase(pointer b) {
        if __acc_cte (configRequireBase && b == nullptr)
            xspan_fail_nullbase();
        return b;
    }
    // inverse logic for ensuring valid pointers from existing objects
    inline pointer ensurePtr() const {
        if __acc_cte (!configRequirePtr && ptr == nullptr)
            xspan_fail_nullptr();
        return ptr;
    }
    inline pointer ensureBase() const {
        if __acc_cte (!configRequireBase && base == nullptr)
            xspan_fail_nullbase();
        return base;
    }

public:
#if DEBUG
    ~CSelf() noexcept {
        try {
            invalidate();
        } catch (...) {
            std::terminate();
        }
    }
#else
forceinline ~CSelf() noexcept {}
#endif
    noinline void invalidate() {
        assertInvariants();
        ptr_invalidate_and_poison(ptr); // point to non-null invalid address
        base = ptr;                     // point to non-null invalid address
        size_in_bytes = 0;
        assertInvariants();
    }

    // constructors from pointers
    CSelf(pointer first, XSpanCount count)
        : ptr(makePtr(first)), base(makeBase(first)),
          size_in_bytes(xspan_mem_size<T>(count.count)) {
        assertInvariants();
    }
    CSelf(XSPAN_DEBUG_ARGS pointer first, XSpanCount count)
        : XSPAN_DEBUG_IMPL ptr(makePtr(first)), base(makeBase(first)),
          size_in_bytes(xspan_mem_size<T>(count.count)) {
        assertInvariants();
    }
    CSelf(pointer first, XSpanSizeInBytes bytes)
        : ptr(makePtr(first)), base(makeBase(first)),
          size_in_bytes(xspan_mem_size<char>(bytes.size_in_bytes)) {
        assertInvariants();
    }
    CSelf(XSPAN_DEBUG_ARGS pointer first, XSpanSizeInBytes bytes)
        : XSPAN_DEBUG_IMPL ptr(makePtr(first)), base(makeBase(first)),
          size_in_bytes(xspan_mem_size<char>(bytes.size_in_bytes)) {
        assertInvariants();
    }
    // enable this constructor only if the underlying type is char or void
    template <class U>
    CSelf(U *first, size_type count, XSPAN_REQUIRES_SIZE_1_A)
        : ptr(makePtr(first)), base(makeBase(first)), size_in_bytes(xspan_mem_size<T>(count)) {
        assertInvariants();
    }
    template <class U>
    CSelf(XSPAN_DEBUG_ARGS U *first, size_type count, XSPAN_REQUIRES_SIZE_1_A)
        : XSPAN_DEBUG_IMPL ptr(makePtr(first)), base(makeBase(first)),
          size_in_bytes(xspan_mem_size<T>(count)) {
        assertInvariants();
    }
    CSelf(pointer first, XSpanCount count, pointer base_)
        : ptr(makePtr(first)), base(makeBase(base_)),
          size_in_bytes(xspan_mem_size<T>(count.count)) {
        // check invariants
        if __acc_cte ((configRequirePtr || ptr != nullptr) &&
                      (configRequireBase || base != nullptr))
            xspan_check_range(ptr, base, size_in_bytes);
        // double sanity check
        assertInvariants();
    }
    CSelf(XSPAN_DEBUG_ARGS pointer first, XSpanCount count, pointer base_)
        : XSPAN_DEBUG_IMPL ptr(makePtr(first)), base(makeBase(base_)),
          size_in_bytes(xspan_mem_size<T>(count.count)) {
        // check invariants
        if __acc_cte ((configRequirePtr || ptr != nullptr) &&
                      (configRequireBase || base != nullptr))
            xspan_check_range(ptr, base, size_in_bytes);
        // double sanity check
        assertInvariants();
    }
    CSelf(pointer first, XSpanSizeInBytes bytes, pointer base_)
        : ptr(makePtr(first)), base(makeBase(base_)),
          size_in_bytes(xspan_mem_size<char>(bytes.size_in_bytes)) {
        // check invariants
        if __acc_cte ((configRequirePtr || ptr != nullptr) &&
                      (configRequireBase || base != nullptr))
            xspan_check_range(ptr, base, size_in_bytes);
        // double sanity check
        assertInvariants();
    }
    CSelf(XSPAN_DEBUG_ARGS pointer first, XSpanSizeInBytes bytes, pointer base_)
        : XSPAN_DEBUG_IMPL ptr(makePtr(first)), base(makeBase(base_)),
          size_in_bytes(xspan_mem_size<char>(bytes.size_in_bytes)) {
        // check invariants
        if __acc_cte ((configRequirePtr || ptr != nullptr) &&
                      (configRequireBase || base != nullptr))
            xspan_check_range(ptr, base, size_in_bytes);
        // double sanity check
        assertInvariants();
    }
    // enable this constructor only if the underlying type is char or void
    template <class U>
    CSelf(pointer first, size_type count, U *base_, XSPAN_REQUIRES_SIZE_1_A)
        : ptr(makePtr(first)), base(makeBase(base_)), size_in_bytes(xspan_mem_size<T>(count)) {
        // check invariants
        if __acc_cte ((configRequirePtr || ptr != nullptr) &&
                      (configRequireBase || base != nullptr))
            xspan_check_range(ptr, base, size_in_bytes);
        // double sanity check
        assertInvariants();
    }
    template <class U>
    CSelf(XSPAN_DEBUG_ARGS pointer first, size_type count, U *base_, XSPAN_REQUIRES_SIZE_1_A)
        : XSPAN_DEBUG_IMPL ptr(makePtr(first)), base(makeBase(base_)),
          size_in_bytes(xspan_mem_size<T>(count)) {
        // check invariants
        if __acc_cte ((configRequirePtr || ptr != nullptr) &&
                      (configRequireBase || base != nullptr))
            xspan_check_range(ptr, base, size_in_bytes);
        // double sanity check
        assertInvariants();
    }

#ifdef UPX_VERSION_HEX
    // constructors from MemBuffer
    CSelf(MemBuffer &mb)
        : CSelf(makeNotNull((pointer) membuffer_get_void_ptr(mb)),
                XSpanSizeInBytes(membuffer_get_size_in_bytes(mb))) {}
    CSelf(XSPAN_DEBUG_ARGS MemBuffer &mb)
        : CSelf(XSPAN_DEBUG_PASS makeNotNull((pointer) membuffer_get_void_ptr(mb)),
                XSpanSizeInBytes(membuffer_get_size_in_bytes(mb))) {}
    CSelf(pointer first, MemBuffer &mb)
        : CSelf(first, XSpanSizeInBytes(membuffer_get_size_in_bytes(mb)),
                makeNotNull((pointer) membuffer_get_void_ptr(mb))) {}
    CSelf(XSPAN_DEBUG_ARGS pointer first, MemBuffer &mb)
        : CSelf(XSPAN_DEBUG_PASS first, XSpanSizeInBytes(membuffer_get_size_in_bytes(mb)),
                makeNotNull((pointer) membuffer_get_void_ptr(mb))) {}
    CSelf(std::nullptr_t, MemBuffer &) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS std::nullptr_t, MemBuffer &) XSPAN_DELETED_FUNCTION;
#endif

    // disable constructors from nullptr to catch compile-time misuse
private:
    CSelf(std::nullptr_t, XSpanCount) XSPAN_DELETED_FUNCTION;
    CSelf(std::nullptr_t, XSpanCount, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(const void *, XSpanCount, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(std::nullptr_t, XSpanSizeInBytes) XSPAN_DELETED_FUNCTION;
    CSelf(std::nullptr_t, XSpanSizeInBytes, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(const void *, XSpanSizeInBytes, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(std::nullptr_t, size_type) XSPAN_DELETED_FUNCTION;
    CSelf(std::nullptr_t, size_type, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(const void *, size_type, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS std::nullptr_t, XSpanCount) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS std::nullptr_t, XSpanCount, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS const void *, XSpanCount, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS std::nullptr_t, XSpanSizeInBytes) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS std::nullptr_t, XSpanSizeInBytes, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS const void *, XSpanSizeInBytes, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS std::nullptr_t, size_type) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS std::nullptr_t, size_type, std::nullptr_t) XSPAN_DELETED_FUNCTION;
    CSelf(XSPAN_DEBUG_ARGS const void *, size_type, std::nullptr_t) XSPAN_DELETED_FUNCTION;

    // unchecked constructor
protected:
    enum ModeUnchecked { Unchecked };
    CSelf(ModeUnchecked, pointer p, size_type bytes, pointer b)
        : ptr(p), base(b), size_in_bytes(bytes) {
        assertInvariants();
    }
    // unchecked assignment
    Self &assign(ModeUnchecked, pointer p, size_type bytes, pointer b) {
        ptr = p;
        base = b;
        size_in_bytes = bytes;
        assertInvariants();
        return *this;
    }
#if 0
    Self &assign(ModeUnchecked, const Self &other) {
        ptr = other.ptr;
        base = other.base;
        size_in_bytes = other.size_in_bytes;
#if XSPAN_CONFIG_ENABLE_DEBUG && 1
        if (f.src_file == nullptr)
            f = other.f;
#endif
        assertInvariants();
        return *this;
    }
#endif

public:
    // assignment - here we can rely on invariants enforced at construction time by makePtr/makeBase
    // NOTE: *this remains unmodified in case of failure
    Self &assign(pointer other) {
        assertInvariants();
        other = makePtr(other);
        if __acc_cte ((configRequirePtr || other != nullptr) &&
                      (configRequireBase || base != nullptr))
            xspan_check_range(other, base, size_in_bytes);
        // ok
        ptr = other;
        assertInvariants();
        return *this;
    }
    Self &assign(const Self &other) {
        assertInvariants();
        other.assertInvariants();
        if __acc_cte (!configRequireBase && base == nullptr) {
            // magic 1: if base is unset, automatically set base/size_in_bytes from other
            if __acc_cte ((configRequirePtr || other.ptr != nullptr) &&
                          (configRequireBase || other.base != nullptr))
                xspan_check_range(other.ptr, other.base, other.size_in_bytes);
            // ok
            ptr = other.ptr;
            base = other.base;
            size_in_bytes = other.size_in_bytes;
        } else {
            // magic 2: assert same base (but ignore size_in_bytes !)
            if __acc_cte (configRequireBase || other.base != nullptr)
                if very_unlikely (base != other.base)
                    xspan_fail_not_same_base();
            if __acc_cte ((configRequirePtr || other.ptr != nullptr) &&
                          (configRequireBase || base != nullptr))
                xspan_check_range(other.ptr, base, size_in_bytes);
            // ok
            ptr = other.ptr;
        }
#if XSPAN_CONFIG_ENABLE_DEBUG && 1
        if (f.src_file == nullptr)
            f = other.f;
#endif
        assertInvariants();
        return *this;
    }

    Self &operator=(pointer other) { return assign(other); }

    Self &operator=(const Self &other) { return assign(other); }

    // FIXME: this is not called??
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(Self &)
    operator=(const CSelf<U> &other) {
        // assert(0);
        return assign(Self(other));
    }

#ifdef UPX_VERSION_HEX
    Self &operator=(MemBuffer &mb) { return assign(Self(mb)); }
#endif

    // subtraction - ptrdiff_t
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(ptrdiff_t)
    operator-(const CSelf<U> &other) const {
        assertInvariants();
        other.assertInvariants();
        return check_ptrdiff(ptr, other.ptr);
    }

    // subspan (creates a new value)
    Self subspan(ptrdiff_t offset, ptrdiff_t count) const {
        pointer p_begin = check_add(ptr, offset);
        pointer p_end = check_add(p_begin, count);
        if (p_begin <= p_end) {
            Self r = Self(Unchecked, p_begin, (p_end - p_begin) * sizeof(T), p_begin);
#if XSPAN_CONFIG_ENABLE_DEBUG
            r.f = f;
#endif
            return r;
        } else {
            Self r = Self(Unchecked, p_end, (p_begin - p_end) * sizeof(T), p_end);
#if XSPAN_CONFIG_ENABLE_DEBUG
            r.f = f;
#endif
            return r;
        }
    }
    // subspan (creates a new value)
    Self subspan(ptrdiff_t offset) const { return subspan(offset, size() - offset); }

    // cast to a different type (creates a new value)
    template <class U>
    CSelf<U> type_cast() const {
        typedef CSelf<U> R;
        typedef typename R::pointer rpointer;
        R r = R(R::Unchecked, upx::ptr_static_cast<rpointer>(ptr), size_in_bytes,
                upx::ptr_static_cast<rpointer>(base));
#if XSPAN_CONFIG_ENABLE_DEBUG
        r.f = f;
#endif
        return r;
    }

    bool operator==(pointer other) const noexcept { return ptr == other; }
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator==(U *other) const noexcept {
        return ptr == other;
    }
    bool operator!=(pointer other) const noexcept { return ptr != other; }
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator!=(U *other) const noexcept {
        return ptr != other;
    }

    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator==(const PtrOrSpan<U> &other) const noexcept {
        return ptr == other.ptr;
    }
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator==(const PtrOrSpanOrNull<U> &other) const noexcept {
        return ptr == other.ptr;
    }
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator==(const Span<U> &other) const noexcept {
        return ptr == other.ptr;
    }

    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator!=(const PtrOrSpan<U> &other) const noexcept {
        return ptr != other.ptr;
    }
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator!=(const PtrOrSpanOrNull<U> &other) const noexcept {
        return ptr != other.ptr;
    }
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator!=(const Span<U> &other) const noexcept {
        return ptr != other.ptr;
    }

    // check for notNull here
    bool operator<(std::nullptr_t) const XSPAN_DELETED_FUNCTION;
    bool operator<(pointer other) const { return ensurePtr() < makeNotNull(other); }

    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator<(const PtrOrSpan<U> &other) const {
        return ensurePtr() < other.ensurePtr();
    }
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator<(const PtrOrSpanOrNull<U> &other) const {
        return ensurePtr() < other.ensurePtr();
    }
    template <class U>
    XSPAN_REQUIRES_CONVERTIBLE_R(bool)
    operator<(const Span<U> &other) const {
        return ensurePtr() < other.ensurePtr();
    }

    // dereference
    reference operator*() const { return *check_deref(ptr); }

    // array access
    reference operator[](ptrdiff_t i) const { return *check_deref(ptr, i); }

    // arrow operator
    pointer operator->() const { return check_deref(ptr); }

    Self &operator++() {
        ptr = check_add(ptr, 1);
        return *this;
    }
    Self operator++(int) {
        Self tmp = *this;
        ++*this;
        return tmp;
    }
    Self &operator--() {
        ptr = check_add(ptr, -1);
        return *this;
    }
    Self operator--(int) {
        Self tmp = *this;
        --*this;
        return tmp;
    }

    Self &operator+=(ptrdiff_t n) {
        ptr = check_add(ptr, n);
        return *this;
    }
    Self &operator-=(ptrdiff_t n) {
        ptr = check_add(ptr, -n);
        return *this;
    }

    Self operator+(ptrdiff_t n) const {
        pointer first = check_add(ptr, n);
        Self r = Self(Unchecked, first, size_in_bytes, base);
#if XSPAN_CONFIG_ENABLE_DEBUG
        r.f = f;
#endif
        return r;
    }
    Self operator-(ptrdiff_t n) const {
        pointer first = check_add(ptr, -n);
        Self r = Self(Unchecked, first, size_in_bytes, base);
#if XSPAN_CONFIG_ENABLE_DEBUG
        r.f = f;
#endif
        return r;
    }

private:
    pointer check_deref(pointer p) const may_throw {
        assertInvariants();
        if __acc_cte (!configRequirePtr && p == nullptr)
            xspan_fail_nullptr();
        if __acc_cte (configRequireBase || base != nullptr)
            xspan_check_range(p, base, size_in_bytes - sizeof(T));
        return p;
    }
    pointer check_deref(pointer p, ptrdiff_t n) const may_throw {
        assertInvariants();
        if __acc_cte (!configRequirePtr && p == nullptr)
            xspan_fail_nullptr();
        xspan_mem_size_assert_ptrdiff<T>(n);
        p += n;
        if __acc_cte (configRequireBase || base != nullptr)
            xspan_check_range(p, base, size_in_bytes - sizeof(T));
        return p;
    }
    pointer check_add(pointer p, ptrdiff_t n) const may_throw {
        assertInvariants();
        if __acc_cte (!configRequirePtr && p == nullptr)
            xspan_fail_nullptr();
        xspan_mem_size_assert_ptrdiff<T>(n);
        p += n;
        if __acc_cte (configRequireBase || base != nullptr)
            xspan_check_range(p, base, size_in_bytes);
        return p;
    }
    ptrdiff_t check_ptrdiff(pointer a, pointer b) const may_throw {
        assertInvariants();
        if very_unlikely (a == nullptr && b != nullptr)
            xspan_fail_nullptr();
        if very_unlikely (a != nullptr && b == nullptr)
            xspan_fail_nullptr();
        if (a != nullptr && b != nullptr)
            (void) ptr_diff_bytes(a, b);
        return a - b;
    }

    // disable taking the address => force passing by reference
    // [I'm not too sure about this design decision, but we can always allow it if needed]
    Self *operator&() const XSPAN_DELETED_FUNCTION;

public: // raw access
    pointer raw_ptr() const noexcept { return ptr; }
    pointer raw_base() const noexcept { return base; }
    size_type raw_size_in_bytes() const noexcept { return size_in_bytes; }

    pointer raw_bytes(size_t bytes) const may_throw {
        assertInvariants();
        if (bytes > 0) {
            if __acc_cte (!configRequirePtr && ptr == nullptr)
                xspan_fail_nullptr();
            if __acc_cte (configRequireBase || base != nullptr)
                xspan_check_range(ptr, base, size_in_bytes - bytes);
            if very_unlikely (__acc_cte(VALGRIND_CHECK_MEM_IS_ADDRESSABLE(ptr, bytes) != 0))
                throwCantPack("raw_bytes valgrind-check-mem");
        }
        return ptr;
    }

    // like C++20 std::span
    pointer data() const noexcept { return ptr; }
    pointer data(size_t bytes) const may_throw { return raw_bytes(bytes); } // UPX extra
    size_type size() const { return size_bytes() / sizeof(element_type); }
    size_type size_bytes() const {
        assertInvariants();
        if __acc_cte (!configRequirePtr && ptr == nullptr)
            return 0;
        if __acc_cte (!configRequireBase && base == nullptr)
            return 0;
        const charptr p_begin = upx::ptr_static_cast<const charptr>(ptr);
        const charptr p_end = upx::ptr_static_cast<const charptr>(base) + size_in_bytes;
        return p_end - p_begin;
    }

#if CLANG_FORMAT_DUMMY_CLASS
}; // class
#endif

/* vim:set ts=4 sw=4 et: */
