/* 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>
 */

// lots of tests (and probably quite a number of redundant tests)

#include "../conf.h"

/*************************************************************************
// raw_bytes
**************************************************************************/

TEST_CASE("raw_bytes ptr") {
    upx_uint16_t *ptr = nullptr;
    CHECK_NOTHROW(raw_bytes(ptr, 0));
    CHECK_THROWS(raw_bytes(ptr, 1));
    CHECK_THROWS(raw_index_bytes(ptr, 0, 0));
    CHECK_THROWS(raw_index_bytes(ptr, 1, 0));
    CHECK_THROWS(raw_index_bytes(ptr, 0, 1));
    upx_uint16_t buf[4];
    ptr = buf;
    CHECK(ptr_udiff_bytes(raw_index_bytes(ptr, 1, 1), ptr) == 2u);
    CHECK(ptr_udiff_bytes(raw_index_bytes(ptr, 4, 0), ptr) == 8u);
    UNUSED(ptr);
}

TEST_CASE("raw_bytes bounded array") {
    upx_uint16_t buf[4];
    CHECK_NOTHROW(raw_bytes(buf, 8));
    CHECK_THROWS(raw_bytes(buf, 9));
    CHECK_NOTHROW(raw_index_bytes(buf, 4, 0));
    CHECK_THROWS(raw_index_bytes(buf, 4, 1));
    CHECK_NOTHROW(raw_index_bytes(buf, 3, 2));
    CHECK_THROWS(raw_index_bytes(buf, 3, 3));
    CHECK(ptr_udiff_bytes(raw_index_bytes(buf, 1, 1), buf) == 2u);
    CHECK(ptr_udiff_bytes(raw_index_bytes(buf, 4, 0), buf) == 8u);
    UNUSED(buf);
}

/*************************************************************************
// basic xspan
**************************************************************************/

TEST_CASE("basic xspan usage") {
    alignas(4) char buf[4] = {0, 1, 2, 3};

    SUBCASE("XSPAN_x") {
        XSPAN_0(char) a0 = nullptr;

        XSPAN_0(char) b0 = buf;
        XSPAN_P(char) bp = buf;

        XSPAN_0(char) c0 = XSPAN_0_MAKE(char, buf);
        XSPAN_P(char) cp = XSPAN_P_MAKE(char, buf);
        XSPAN_S(char) cs = XSPAN_S_MAKE(char, buf, sizeof(buf));

        XSPAN_0(const char) const x0 = XSPAN_0_MAKE(const char, buf);
        XSPAN_P(const char) const xp = XSPAN_P_MAKE(const char, buf);
        XSPAN_S(const char) const xs = XSPAN_S_MAKE(const char, buf, sizeof(buf));
        XSPAN_P(const char) const yp = xs;
        XSPAN_0(const char) const z0p = yp;
        XSPAN_0(const char) const z0s = xs;

        CHECK((a0 == nullptr));
        CHECK(c0 == b0);
        CHECK(cp == bp);
        CHECK(cs == bp);
        CHECK(x0 == z0p);
        CHECK(xp == z0s);

        CHECK_NOTHROW(raw_bytes(a0, 0));
        CHECK_THROWS(raw_bytes(a0, 1));
        CHECK_THROWS(raw_index_bytes(a0, 0, 0));

        CHECK(raw_bytes(b0, 0) == buf);
        CHECK(raw_bytes(bp, 0) == buf);
        // info: these will fail if we ever add an overload for bounded-arrays
#if WITH_XSPAN >= 2
        CHECK(b0.raw_size_in_bytes() == 0u);
        CHECK(bp.raw_size_in_bytes() == 0u);
#endif
        CHECK(raw_bytes(c0, 4) == buf);
        CHECK(raw_index_bytes(c0, 1, 3) == buf + 1);
        CHECK(raw_bytes(cp, 4) == buf);
        CHECK(raw_index_bytes(cp, 1, 3) == buf + 1);
        CHECK(raw_bytes(cs, 4) == buf);
        CHECK(raw_index_bytes(cs, 1, 3) == buf + 1);
#if WITH_XSPAN >= 2
        CHECK_THROWS(raw_bytes(cs, 5));
        CHECK_THROWS(raw_index_bytes(cs, 1, 4));
#endif

        XSPAN_0(upx_uint16_t) c0_2 = XSPAN_TYPE_CAST(upx_uint16_t, c0 + 2);
        XSPAN_P(upx_uint16_t) cp_2 = XSPAN_TYPE_CAST(upx_uint16_t, cp + 2);
        XSPAN_S(upx_uint16_t) cs_2 = XSPAN_TYPE_CAST(upx_uint16_t, cs + 2);
        CHECK(ptr_udiff_bytes(c0_2, c0) == 2u);
        CHECK(ptr_udiff_bytes(cp_2, c0) == 2u);
        CHECK(ptr_udiff_bytes(cs_2, c0) == 2u);
        CHECK(ptr_udiff_bytes(c0_2, cp) == 2u);
        CHECK(ptr_udiff_bytes(cp_2, cp) == 2u);
        CHECK(ptr_udiff_bytes(cs_2, cp) == 2u);
        CHECK(ptr_udiff_bytes(c0_2, cs) == 2u);
        CHECK(ptr_udiff_bytes(cp_2, cs) == 2u);
        CHECK(ptr_udiff_bytes(cs_2, cs) == 2u);
        XSPAN_0(upx_uint16_t) c0_2b = XSPAN_TYPE_CAST(upx_uint16_t, c0) + 1;
        XSPAN_P(upx_uint16_t) cp_2b = XSPAN_TYPE_CAST(upx_uint16_t, cp) + 1;
        XSPAN_S(upx_uint16_t) cs_2b = XSPAN_TYPE_CAST(upx_uint16_t, cs) + 1;
        CHECK(c0_2 == c0_2b);
        CHECK(cp_2 == cp_2b);
        CHECK(cs_2 == cs_2b);

        CHECK(sizeof(*c0) == 1u);
        CHECK(sizeof(*c0_2) == 2u);
    }

    SUBCASE("XSPAN_x_VAR") {
        XSPAN_0_VAR(char, b0, buf);
        XSPAN_P_VAR(char, bp, buf);

        XSPAN_0_VAR(char, c0, buf, sizeof(buf));
        XSPAN_P_VAR(char, cp, buf, sizeof(buf));
        XSPAN_S_VAR(char, cs, buf, sizeof(buf));

        XSPAN_0_VAR(char, d0, buf + 1, sizeof(buf), buf);
        XSPAN_P_VAR(char, dp, buf + 1, sizeof(buf), buf);
        XSPAN_S_VAR(char, ds, buf + 1, sizeof(buf), buf);

        XSPAN_0_VAR(const char, const x0, buf, sizeof(buf));
        XSPAN_P_VAR(const char, const xp, buf, sizeof(buf));
        XSPAN_S_VAR(const char, const xs, buf, sizeof(buf));
        XSPAN_P_VAR(const char, const yp, xs);
        XSPAN_0_VAR(const char, const z0p, yp);
        XSPAN_0_VAR(const char, const z0s, xs);

        CHECK(c0 == b0);
        CHECK(cp == bp);
        CHECK(cs == bp);
        CHECK(d0 == dp);
        CHECK(d0 == ds);
        CHECK(x0 == z0p);
        CHECK(xp == z0s);

#if WITH_XSPAN >= 1 || __cplusplus >= 201103L
        XSPAN_0_VAR(char, a0, nullptr);
        CHECK((a0 == nullptr));
        CHECK_NOTHROW(raw_bytes(a0, 0));
        CHECK_THROWS(raw_bytes(a0, 1));
        CHECK_THROWS(raw_index_bytes(a0, 0, 0));
#endif

        CHECK(raw_bytes(b0, 0) == buf);
        CHECK(raw_bytes(bp, 0) == buf);
        // info: these will fail if we ever add an overload for bounded-arrays
#if WITH_XSPAN >= 2
        CHECK(b0.raw_size_in_bytes() == 0u);
        CHECK(bp.raw_size_in_bytes() == 0u);
#endif
        CHECK(raw_bytes(c0, 4) == buf);
        CHECK(raw_index_bytes(c0, 1, 3) == buf + 1);
        CHECK(raw_bytes(cp, 4) == buf);
        CHECK(raw_index_bytes(cp, 1, 3) == buf + 1);
        CHECK(raw_bytes(cs, 4) == buf);
        CHECK(raw_index_bytes(cs, 1, 3) == buf + 1);
#if WITH_XSPAN >= 2
        CHECK_THROWS(raw_bytes(cs, 5));
        CHECK_THROWS(raw_index_bytes(cs, 1, 4));
#endif

        XSPAN_0_VAR(upx_uint16_t, c0_2, XSPAN_TYPE_CAST(upx_uint16_t, c0 + 2));
        XSPAN_P_VAR(upx_uint16_t, cp_2, XSPAN_TYPE_CAST(upx_uint16_t, cp + 2));
        XSPAN_S_VAR(upx_uint16_t, cs_2, XSPAN_TYPE_CAST(upx_uint16_t, cs + 2));
        CHECK(ptr_udiff_bytes(c0_2, c0) == 2u);
        CHECK(ptr_udiff_bytes(cp_2, c0) == 2u);
        CHECK(ptr_udiff_bytes(cs_2, c0) == 2u);
        CHECK(ptr_udiff_bytes(c0_2, cp) == 2u);
        CHECK(ptr_udiff_bytes(cp_2, cp) == 2u);
        CHECK(ptr_udiff_bytes(cs_2, cp) == 2u);
        CHECK(ptr_udiff_bytes(c0_2, cs) == 2u);
        CHECK(ptr_udiff_bytes(cp_2, cs) == 2u);
        CHECK(ptr_udiff_bytes(cs_2, cs) == 2u);
        XSPAN_0_VAR(upx_uint16_t, c0_2b, XSPAN_TYPE_CAST(upx_uint16_t, c0) + 1);
        XSPAN_P_VAR(upx_uint16_t, cp_2b, XSPAN_TYPE_CAST(upx_uint16_t, cp) + 1);
        XSPAN_S_VAR(upx_uint16_t, cs_2b, XSPAN_TYPE_CAST(upx_uint16_t, cs) + 1);
        CHECK(c0_2 == c0_2b);
        CHECK(cp_2 == cp_2b);
        CHECK(cs_2 == cs_2b);

        CHECK(sizeof(*c0) == 1u);
        CHECK(sizeof(*c0_2) == 2u);
    }

    SUBCASE("xspan in class") {
        struct MyType {
            XSPAN_0(char) s0;
            XSPAN_P(char) sp;
            XSPAN_S(char) ss;
#if __cplusplus >= 201103L
            XSPAN_0(char) x0 = nullptr;
#endif
#if WITH_XSPAN >= 2
            // much nicer syntax when using fully checked xspan:
            MyType(char *b, size_t n, bool) : s0(b, n), sp(b, n), ss(b, n) {}
#endif
            MyType(char *b, size_t n)
                : s0(XSPAN_0_MAKE(char, b, n)), sp(XSPAN_P_MAKE(char, b, n)),
                  ss(XSPAN_S_MAKE(char, b, n)) {
                UNUSED(n);
            }
        };
        MyType x(buf, sizeof(buf));
        MyType y = MyType(buf, sizeof(buf));
        CHECK(x.s0 == y.sp);
    }
}

TEST_CASE("xspan array access") {
    const size_t N = 16;
    char buf[N];
    memset(buf, 0, sizeof(buf));
    XSPAN_0_VAR(char, c0, buf, sizeof(buf));
    XSPAN_P_VAR(char, cp, buf, sizeof(buf));
    XSPAN_S_VAR(char, cs, buf, sizeof(buf));
    for (size_t i = 0; i != N; ++i)
        c0[i] += 1;
    for (size_t i = 0; i != N; ++i)
        cp[i] += 1;
    for (size_t i = 0; i != N; ++i)
        cs[i] += 1;
#if __cplusplus >= 201103L
    for (auto ptr = c0; ptr != c0 + N; ++ptr)
        *ptr += 1;
    for (auto ptr = c0 + 0; ptr < c0 + N; ++ptr)
        *ptr += 1;
    for (auto ptr = cp; ptr != cp + N; ++ptr)
        *ptr += 1;
    for (auto ptr = cp + 0; ptr < cp + N; ++ptr)
        *ptr += 1;
    for (auto ptr = cs; ptr != cs + N; ++ptr)
        *ptr += 1;
    for (auto ptr = cs + 0; ptr < cs + N; ++ptr)
        *ptr += 1;
#endif
}

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

#if (WITH_XSPAN >= 2) && DEBUG

TEST_CASE("XSPAN_CONFIG_ENABLE_DEBUG") {
#if XSPAN_CONFIG_ENABLE_DEBUG
    CHECK(true);
#endif
}

TEST_CASE("PtrOrSpanOrNull") {
    char real_buf[2 + 6 + 2] = {126, 127, 0, 1, 2, 3, 4, 5, 124, 125};
    char *base_buf = real_buf + 2;
    char *const my_null = nullptr;
    typedef PtrOrSpanOrNull<char> Span0;

    // basic nullptr
    CHECK_NOTHROW(Span0(base_buf, 4, base_buf) = my_null);
    CHECK_NOTHROW(Span0(base_buf, 4, base_buf).assign(my_null));
    // basic range checking
    CHECK_NOTHROW(Span0(base_buf, 4, base_buf));
    CHECK_NOTHROW(Span0(base_buf, 0, base_buf));
    CHECK_NOTHROW(Span0(base_buf, 0, base_buf) - 0);
    CHECK_THROWS(Span0(base_buf, 0, base_buf) + 1);
    CHECK_THROWS(Span0(base_buf, 0, base_buf) - 1);
    CHECK_NOTHROW(Span0(base_buf, 4, base_buf) + 4);
    CHECK_THROWS(Span0(base_buf, 4, base_buf) + 5);
    CHECK_THROWS(Span0(base_buf - 1, 4, base_buf));
    CHECK_THROWS(Span0(base_buf + 1, 0, base_buf));
    // basic same base
    CHECK_NOTHROW(Span0(base_buf, 4, base_buf) = Span0(base_buf + 1, 3, base_buf));
    CHECK_NOTHROW(Span0(base_buf, 4, base_buf) = Span0(base_buf + 1, 1, base_buf));
    CHECK_NOTHROW(Span0(base_buf, 4, base_buf) = Span0(base_buf + 1, 5, base_buf));
    CHECK_THROWS(Span0(base_buf, 4, base_buf) = Span0(base_buf + 1, 3, base_buf + 1));

    Span0 a1(nullptr);
    assert(a1 == nullptr);
    assert(a1.raw_ptr() == nullptr);
    assert(a1.raw_base() == nullptr);
    assert(a1.raw_size_in_bytes() == 0u);
    CHECK_THROWS(*a1);
    CHECK_THROWS(a1[0]);

    Span0 a2 = nullptr;
    assert(a2 == nullptr);
    assert(a2.raw_ptr() == nullptr);
    assert(a2.raw_base() == nullptr);
    assert(a2.raw_size_in_bytes() == 0u);
    CHECK_THROWS(*a2);
    CHECK_THROWS(a2[0]);

    Span0 base0(nullptr, 4, base_buf);
    assert(base0.raw_ptr() == nullptr);
    assert(base0.raw_base() == base_buf);
    assert(base0.raw_size_in_bytes() == 4u);
    CHECK_THROWS(*base0);    // nullptr
    CHECK_THROWS(base0[0]);  // nullptr
    CHECK_THROWS(base0 + 1); // nullptr

    Span0 base4(base_buf, 4);
    assert(base4.raw_ptr() == base_buf);
    assert(base4.raw_base() == base_buf);
    assert(base4.raw_size_in_bytes() == 4u);

    a1 = base_buf;
    a1 = base0;
    assert(a1 == nullptr);
    assert(a1.raw_ptr() == nullptr);
    assert(a1.raw_base() == base_buf);
    assert(a1.raw_size_in_bytes() == 4u);
    a1 = base4;
    assert(a1 == base_buf);
    assert(a1.raw_ptr() == base_buf);
    assert(a1.raw_base() == base_buf);
    assert(a1.raw_size_in_bytes() == 4u);

    a1 = base_buf;
    assert(a1 != nullptr);
    a1 = base_buf + 1;
    CHECK(*a1++ == 1);
    CHECK(*++a1 == 3);
    CHECK(*a1 == 3);
    a1 = base_buf + 4; // at the end of buffer
    CHECK_THROWS(*a1);
    CHECK_THROWS(a1 = base_buf + 5); // range error
    assert(a1 == base_buf + 4);
    CHECK(a1[-4] == 0);
    CHECK_THROWS(a1[-5]); // range error
    a1 = base_buf;
    CHECK(*a1 == 0);

    Span0 new_base4(base_buf + 2, 4);
    CHECK_THROWS(a1 = new_base4); // not same base
    a2 = new_base4;
    CHECK_THROWS(a2 = base4); // not same base

    Span0 s0_no_base(nullptr);
    Span0 s0_with_base(nullptr, 4, base_buf);
    s0_no_base = nullptr;
    s0_with_base = nullptr;
    s0_with_base = s0_no_base;
    assert(s0_no_base.raw_base() == nullptr);
    assert(s0_with_base.raw_base() == base_buf);
    s0_no_base = s0_with_base;
    assert(s0_no_base.raw_base() == base_buf);
    assert(s0_no_base.raw_ptr() == nullptr);
    assert(s0_with_base.raw_ptr() == nullptr);
    s0_no_base = my_null;
    s0_with_base = my_null;
}

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

TEST_CASE("PtrOrSpan") {
    char real_buf[2 + 6 + 2] = {126, 127, 0, 1, 2, 3, 4, 5, 124, 125};
    char *base_buf = real_buf + 2;
    char *const my_null = nullptr;
    typedef PtrOrSpan<char> SpanP;

    // basic nullptr
    CHECK_THROWS(SpanP(base_buf, 4, base_buf) = my_null);
    CHECK_THROWS(SpanP(base_buf, 4, base_buf).assign(my_null));
    // basic range checking
    CHECK_NOTHROW(SpanP(base_buf, 4, base_buf));
    CHECK_NOTHROW(SpanP(base_buf, 0, base_buf));
    CHECK_NOTHROW(SpanP(base_buf, 0, base_buf) - 0);
    CHECK_THROWS(SpanP(base_buf, 0, base_buf) + 1);
    CHECK_THROWS(SpanP(base_buf, 0, base_buf) - 1);
    CHECK_NOTHROW(SpanP(base_buf, 4, base_buf) + 4);
    CHECK_THROWS(SpanP(base_buf, 4, base_buf) + 5);
    CHECK_THROWS(SpanP(base_buf - 1, 4, base_buf));
    CHECK_THROWS(SpanP(base_buf + 1, 0, base_buf));
    // basic same base
    CHECK_NOTHROW(SpanP(base_buf, 4, base_buf) = SpanP(base_buf + 1, 3, base_buf));
    CHECK_NOTHROW(SpanP(base_buf, 4, base_buf) = SpanP(base_buf + 1, 1, base_buf));
    CHECK_NOTHROW(SpanP(base_buf, 4, base_buf) = SpanP(base_buf + 1, 5, base_buf));
    CHECK_THROWS(SpanP(base_buf, 4, base_buf) = SpanP(base_buf + 1, 3, base_buf + 1));

    SpanP x1(base_buf, 0);
    assert(x1 != nullptr);
    assert(x1.raw_ptr() == base_buf);
    assert(x1.raw_base() == base_buf);
    assert(x1.raw_size_in_bytes() == 0u);
    CHECK_THROWS(*x1);
    CHECK_THROWS(x1[0]);

    SpanP a2 = base_buf;
    assert(a2 != nullptr);
    assert(a2.raw_ptr() == base_buf);
    assert(a2.raw_base() == nullptr);
    assert(a2.raw_size_in_bytes() == 0u);
    CHECK(*a2 == 0);
    CHECK(a2[1] == 1);

    SpanP base0(base_buf, 4, base_buf);
    assert(base0.raw_ptr() == base_buf);
    assert(base0.raw_base() == base_buf);
    assert(base0.raw_size_in_bytes() == 4u);

    SpanP base4(base_buf, 4);
    assert(base4.raw_ptr() == base_buf);
    assert(base4.raw_base() == base_buf);
    assert(base4.raw_size_in_bytes() == 4u);

    SpanP a1(base_buf, 4);
    a1 = base_buf;
    a1 = base0;
    assert(a1 == base0);
    assert(a1 != nullptr);
    assert(a1.raw_ptr() == base0.raw_ptr());
    assert(a1.raw_base() == base_buf);
    assert(a1.raw_size_in_bytes() == 4u);
    a1 = base4;
    assert(a1 == base_buf);
    assert(a1.raw_ptr() == base_buf);
    assert(a1.raw_base() == base_buf);
    assert(a1.raw_size_in_bytes() == 4u);

    a1 = base_buf;
    a1 = base_buf + 1;
    CHECK(*a1++ == 1);
    CHECK(*++a1 == 3);
    CHECK(*a1 == 3);
    a1 = base_buf + 4; // at the end of buffer
    CHECK_THROWS(*a1);
    CHECK_THROWS(a1 = base_buf + 5); // range error
    assert(a1 == base_buf + 4);
    CHECK(a1[-4] == 0);
    CHECK_THROWS(a1[-5]); // range error
    a1 = base_buf;
    CHECK(*a1 == 0);

    SpanP new_base4(base_buf + 2, 4);
    CHECK_THROWS(a1 = new_base4); // not same base
    a2 = new_base4;
    CHECK_THROWS(a2 = base4); // not same base

    SpanP sp_no_base(base_buf);
    SpanP sp_with_base(base_buf, 4, base_buf);
    assert(sp_no_base.raw_base() == nullptr);
    assert(sp_with_base.raw_base() == base_buf);
    CHECK_THROWS(sp_no_base = my_null);   // nullptr assignment
    CHECK_THROWS(sp_with_base = my_null); // nullptr assignment
#if XSPAN_CONFIG_ENABLE_SPAN_CONVERSION
    typedef PtrOrSpanOrNull<char> Span0;
    Span0 s0_no_base(nullptr);
    Span0 s0_with_base(nullptr, 4, base_buf);
    CHECK_THROWS(sp_no_base = s0_no_base);     // nullptr assignment
    CHECK_THROWS(sp_no_base = s0_with_base);   // nullptr assignment
    CHECK_THROWS(sp_with_base = s0_no_base);   // nullptr assignment
    CHECK_THROWS(sp_with_base = s0_with_base); // nullptr assignment
#endif
    UNUSED(my_null);
}

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

TEST_CASE("Span") {
    char real_buf[2 + 6 + 2] = {126, 127, 0, 1, 2, 3, 4, 5, 124, 125};
    char *base_buf = real_buf + 2;
    char *const my_null = nullptr;
    typedef Span<char> SpanS;

    // basic nullptr
    CHECK_THROWS(SpanS(base_buf, 4, base_buf) = my_null);
    CHECK_THROWS(SpanS(base_buf, 4, base_buf).assign(my_null));
    // basic range checking
    CHECK_NOTHROW(SpanS(base_buf, 4, base_buf));
    CHECK_NOTHROW(SpanS(base_buf, 0, base_buf));
    CHECK_NOTHROW(SpanS(base_buf, 0, base_buf) - 0);
    CHECK_THROWS(SpanS(base_buf, 0, base_buf) + 1);
    CHECK_THROWS(SpanS(base_buf, 0, base_buf) - 1);
    CHECK_NOTHROW(SpanS(base_buf, 4, base_buf) + 4);
    CHECK_THROWS(SpanS(base_buf, 4, base_buf) + 5);
    CHECK_THROWS(SpanS(base_buf - 1, 4, base_buf));
    CHECK_THROWS(SpanS(base_buf + 1, 0, base_buf));
    // basic same base
    CHECK_NOTHROW(SpanS(base_buf, 4, base_buf) = SpanS(base_buf + 1, 3, base_buf));
    CHECK_NOTHROW(SpanS(base_buf, 4, base_buf) = SpanS(base_buf + 1, 1, base_buf));
    CHECK_NOTHROW(SpanS(base_buf, 4, base_buf) = SpanS(base_buf + 1, 5, base_buf));
    CHECK_THROWS(SpanS(base_buf, 4, base_buf) = SpanS(base_buf + 1, 3, base_buf + 1));

    SpanS x1(base_buf, 0);
    assert(x1 != nullptr);
    assert(x1.raw_ptr() == base_buf);
    assert(x1.raw_base() == base_buf);
    assert(x1.raw_size_in_bytes() == 0u);
    CHECK_THROWS(*x1);
    CHECK_THROWS(x1[0]);

    SpanS a2(base_buf, 4);
    assert(a2 != nullptr);
    assert(a2.raw_ptr() == base_buf);
    assert(a2.raw_base() == base_buf);
    assert(a2.raw_size_in_bytes() == 4u);
    CHECK(*a2 == 0);
    CHECK(a2[1] == 1);

    SpanS base0(base_buf, 4, base_buf);
    assert(base0.raw_ptr() == base_buf);
    assert(base0.raw_base() == base_buf);
    assert(base0.raw_size_in_bytes() == 4u);

    SpanS base4(base_buf, 4);
    assert(base4.raw_ptr() == base_buf);
    assert(base4.raw_base() == base_buf);
    assert(base4.raw_size_in_bytes() == 4u);

    SpanS a1(base_buf, 4);
    a1 = base_buf;
    a1 = base0;
    assert(a1 == base0);
    assert(a1 != nullptr);
    assert(a1.raw_ptr() == base0.raw_ptr());
    assert(a1.raw_base() == base_buf);
    assert(a1.raw_size_in_bytes() == 4u);
    a1 = base4;
    assert(a1 == base_buf);
    assert(a1.raw_ptr() == base_buf);
    assert(a1.raw_base() == base_buf);
    assert(a1.raw_size_in_bytes() == 4u);

    a1 = base_buf;
    a1 = base_buf + 1;
    CHECK(*a1++ == 1);
    CHECK(*++a1 == 3);
    CHECK(*a1 == 3);
    a1 = base_buf + 4; // at the end of buffer
    CHECK_THROWS(*a1);
    CHECK_THROWS(a1 = base_buf + 5); // range error
    assert(a1 == base_buf + 4);
    CHECK(a1[-4] == 0);
    CHECK_THROWS(a1[-5]); // range error
    a1 = base_buf;
    CHECK(*a1 == 0);

    SpanS new_base4(base_buf + 2, 4);
    CHECK_THROWS(a1 = new_base4); // not same base
    CHECK_THROWS(a2 = new_base4); // not same base

    SpanS ss_with_base(base_buf, 4, base_buf);
    assert(ss_with_base.raw_base() == base_buf);
    CHECK_THROWS(ss_with_base = my_null); // nullptr assignment
#if XSPAN_CONFIG_ENABLE_SPAN_CONVERSION
    {
        typedef PtrOrSpanOrNull<char> Span0;
        // v0 nullptr, b0 base, b1 base + 1
        const Span0 v0_v0(nullptr);
        const Span0 v0_b0(nullptr, 4, base_buf);
        const Span0 v0_b1(nullptr, 3, base_buf + 1);
        const Span0 b0_v0(base_buf);
        const Span0 b0_b0(base_buf, 4, base_buf);
        CHECK_THROWS(XSPAN_0_MAKE(char, base_buf, 3, base_buf + 1)); // b0_b1
        const Span0 b1_v0(base_buf + 1);
        const Span0 b1_b0(base_buf + 1, 4, base_buf);
        const Span0 b1_b1(base_buf + 1, 3, base_buf + 1);
        CHECK_THROWS(ss_with_base = v0_v0); // nullptr assignment
        CHECK_THROWS(ss_with_base = v0_b0); // nullptr assignment
        CHECK_THROWS(ss_with_base = v0_b1); // nullptr assignment
        CHECK_NOTHROW(ss_with_base = b0_v0);
        CHECK_NOTHROW(ss_with_base = b0_b0);
        CHECK_NOTHROW(ss_with_base = b1_v0);
        CHECK_NOTHROW(ss_with_base = b1_b0);
        CHECK_THROWS(ss_with_base = b1_b1); // different base
        CHECK_THROWS(XSPAN_S_MAKE(char, v0_v0));
        CHECK_THROWS(XSPAN_S_MAKE(char, v0_b0));
        CHECK_THROWS(XSPAN_S_MAKE(char, v0_b1));
        CHECK_THROWS(XSPAN_S_MAKE(char, b0_v0));
        CHECK_NOTHROW(XSPAN_S_MAKE(char, b0_b0));
        CHECK_THROWS(XSPAN_S_MAKE(char, b1_v0));
        CHECK_NOTHROW(XSPAN_S_MAKE(char, b1_b0));
        CHECK_NOTHROW(XSPAN_S_MAKE(char, b1_b1));
        //
        CHECK((XSPAN_S_MAKE(char, b0_b0).raw_base() == base_buf));
        CHECK((XSPAN_S_MAKE(char, b1_b0).raw_base() == base_buf));
        CHECK((XSPAN_S_MAKE(char, b1_b1).raw_base() == base_buf + 1));
    }
    {
        typedef PtrOrSpan<char> SpanP;
        // v0 nullptr, b0 base, b1 base + 1
        const SpanP b0_v0(base_buf);
        const SpanP b0_b0(base_buf, 4, base_buf);
        CHECK_THROWS(XSPAN_P_MAKE(char, base_buf, 3, base_buf + 1)); // b0_b1
        const SpanP b1_v0(base_buf + 1);
        const SpanP b1_b0(base_buf + 1, 4, base_buf);
        const SpanP b1_b1(base_buf + 1, 3, base_buf + 1);
        CHECK_NOTHROW(ss_with_base = b0_v0);
        CHECK_NOTHROW(ss_with_base = b0_b0);
        CHECK_NOTHROW(ss_with_base = b1_v0);
        CHECK_NOTHROW(ss_with_base = b1_b0);
        CHECK_THROWS(ss_with_base = b1_b1); // different base
        CHECK_THROWS(XSPAN_S_MAKE(char, b0_v0));
        CHECK_NOTHROW(XSPAN_S_MAKE(char, b0_b0));
        CHECK_THROWS(XSPAN_S_MAKE(char, b1_v0));
        CHECK_NOTHROW(XSPAN_S_MAKE(char, b1_b0));
        CHECK_NOTHROW(XSPAN_S_MAKE(char, b1_b1));
        //
        CHECK((XSPAN_S_MAKE(char, b0_b0).raw_base() == base_buf));
        CHECK((XSPAN_S_MAKE(char, b1_b0).raw_base() == base_buf));
        CHECK((XSPAN_S_MAKE(char, b1_b1).raw_base() == base_buf + 1));
    }
#endif
    UNUSED(my_null);
}

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

TEST_CASE("Span void ptr") {
    static char a[4] = {0, 1, 2, 3};
    XSPAN_0(void) a0(a, 4);
    XSPAN_P(void) ap(a, 4);
    XSPAN_S(void) as(a, 4);
    XSPAN_0(const void) c0(a, 4);
    XSPAN_P(const void) cp(a, 4);
    XSPAN_S(const void) cs(a, 4);
    static const char b[4] = {0, 1, 2, 3};
    XSPAN_0(const void) b0(b, 4);
    XSPAN_P(const void) bp(b, 4);
    XSPAN_S(const void) bs(b, 4);
}

TEST_CASE("Span deref/array/arrow") {
    static char real_a[2 + 4 + 2] = {126, 127, 0, 1, 2, 3, 124, 125};
    static char *a = real_a + 2;
    XSPAN_0(char) a0(a, 4);
    XSPAN_P(char) ap(a, 4);
    XSPAN_S(char) as(a, 4);
    CHECK_THROWS(a0[4]);
    CHECK_THROWS(a0[-1]);
    CHECK_THROWS(a0[-2]);
    a0 += 2;
    CHECK(*a0 == 2);
    CHECK(a0[-1] == 1);
    CHECK(a0[0] == 2);
    CHECK(a0[1] == 3);
    ap += 2;
    CHECK(*ap == 2);
    CHECK(ap[-1] == 1);
    CHECK(ap[0] == 2);
    CHECK(ap[1] == 3);
    as += 2;
    CHECK(*as == 2);
    CHECK(as[-1] == 1);
    CHECK(as[0] == 2);
    CHECK(as[1] == 3);
}

TEST_CASE("Span subspan") {
    static char buf[4] = {0, 1, 2, 3};
    XSPAN_S(char) as(buf, 4);
    CHECK(as.subspan(1, 1)[0] == 1);
    CHECK((as + 1).subspan(1, 1)[0] == 2);
    CHECK((as + 2).subspan(0, -2)[0] == 0);
    CHECK_THROWS(as.subspan(1, 0)[0]);
    CHECK_THROWS(as.subspan(1, 1)[-1]);
    CHECK(as.subspan(1)[0] == 1);
    CHECK(as.subspan(2)[0] == 2);
    CHECK(as.subspan(3)[0] == 3);
}

TEST_CASE("Span constness") {
    static char buf[4] = {0, 1, 2, 3};

    // NOLINTBEGIN(performance-unnecessary-copy-initialization)

    XSPAN_0(char) b0(buf, 4);
    XSPAN_P(char) bp(buf, 4);
    XSPAN_S(char) bs(buf, 4);

    XSPAN_0(char) s0(b0);
    XSPAN_P(char) sp(bp);
    XSPAN_S(char) ss(bs);

    XSPAN_0(const char) b0c(buf, 4);
    XSPAN_P(const char) bpc(buf, 4);
    XSPAN_S(const char) bsc(buf, 4);

    XSPAN_0(const char) s0c(b0c);
    XSPAN_P(const char) spc(bpc);
    XSPAN_S(const char) ssc(bsc);

    XSPAN_0(const char) x0c(b0);
    XSPAN_P(const char) xpc(bp);
    XSPAN_S(const char) xsc(bs);

    // NOLINTEND(performance-unnecessary-copy-initialization)

    CHECK(ptr_diff_bytes(b0, buf) == 0);
    CHECK(ptr_diff_bytes(bp, buf) == 0);
    CHECK(ptr_diff_bytes(bs, buf) == 0);
    CHECK(ptr_diff_bytes(s0, buf) == 0);
    CHECK(ptr_diff_bytes(sp, buf) == 0);
    CHECK(ptr_diff_bytes(bs, buf) == 0);
    //
    CHECK(ptr_diff_bytes(s0, bp) == 0);
    CHECK(ptr_diff_bytes(s0, sp) == 0);
    CHECK(ptr_diff_bytes(s0, ss) == 0);
    //
    CHECK(ptr_diff_bytes(s0c, b0c) == 0);
    CHECK(ptr_diff_bytes(spc, bpc) == 0);
    CHECK(ptr_diff_bytes(ssc, bsc) == 0);
}

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

#if !defined(DOCTEST_CONFIG_DISABLE)
namespace {
int my_memcmp_v1(XSPAN_P(const void) a, XSPAN_0(const void) b, size_t n) {
    if (b == nullptr)
        return -2;
    XSPAN_0(const void) x(a);
    return memcmp(x, b, n);
}
int my_memcmp_v2(XSPAN_P(const char) a, XSPAN_0(const char) b, size_t n) {
    if (a == b)
        return 0;
    if (b == nullptr)
        return -2;
    a += 1;
    b -= 1;
    XSPAN_0(const char) x(a);
    XSPAN_0(const char) y = b;
    return memcmp(x, y, n);
}
} // namespace
#endif

TEST_CASE("PtrOrSpan") {
    static const char buf[4] = {0, 1, 2, 3};
    CHECK(my_memcmp_v1(buf, nullptr, 4) == -2);
    CHECK(my_memcmp_v2(buf + 4, buf + 4, 999) == 0);
    CHECK(my_memcmp_v2(buf, buf + 2, 3) == 0);
    UNUSED(buf);
}

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

TEST_CASE("PtrOrSpan char") {
    char real_buf[2 + 8 + 2] = {126, 127, 0, 1, 2, 3, 4, 5, 6, 7, 124, 125};
    char *buf = real_buf + 2;
    XSPAN_P(char) a(buf, XSpanSizeInBytes(8));
    XSPAN_P(char) b = a.subspan(0, 7);
    XSPAN_P(char) c = (b + 1).subspan(0, 6);
    a += 1;
    CHECK(*a == 1);
    *a++ += 1;
    *b++ = 1;
    CHECK(a == buf + 2);
    CHECK(b == buf + 1);
    CHECK(c == buf + 1);
    CHECK(*b == 2);
    CHECK(*c == 2);
    CHECK(a.raw_size_in_bytes() == 8u);
    CHECK(b.raw_size_in_bytes() == 7u);
    CHECK(c.raw_size_in_bytes() == 6u);
    CHECK(a.raw_base() == buf);
    CHECK(b.raw_base() == buf);
    CHECK(c.raw_base() == buf + 1);
#ifdef UPX_VERSION_HEX
    CHECK(get_le32(a) != 0);
#endif
    ++c;
    c++;
#ifdef UPX_VERSION_HEX
    CHECK(get_le32(c) != 0);
#endif
    ++c;
#ifdef UPX_VERSION_HEX
    CHECK_THROWS(get_le32(c));
#endif
    ++b;
    b++;
    b += 4;
    CHECK(b.raw_ptr() == buf + 7);
    CHECK_THROWS(*b);
    CHECK(a.raw_size_in_bytes() == 8u);
    a = b;
    CHECK(a.raw_size_in_bytes() == 8u);
    CHECK(a.raw_ptr() == buf + 7);
    a++;
    CHECK_THROWS(*a);
    CHECK_THROWS(raw_bytes(a, 1));
    a = b;
    CHECK_THROWS(a = c);
    *a = 0;
    a = buf;
#ifdef UPX_VERSION_HEX
    CHECK(upx_safe_strlen(a) == 7u);
#endif
}

TEST_CASE("PtrOrSpan int") {
    int buf[8] = {0, 11, 22, 33, 44, 55, 66, 77};
    XSPAN_P(const int) a(buf, XSpanCount(8));
    CHECK(a.raw_size_in_bytes() == 8 * sizeof(int));
    XSPAN_P(const int) b = a.subspan(0, 7);
    CHECK(b.raw_size_in_bytes() == 7 * sizeof(int));
    XSPAN_P(const int) c = (b + 1).subspan(0, 6);
    CHECK(c.raw_size_in_bytes() == 6 * sizeof(int));
    a += 1;
    CHECK(a == buf + 1);
    CHECK(*a == 11);
    CHECK(*a++ == 11);
    CHECK(a == buf + 2);
    CHECK(*a == 22);
    CHECK(*++a == 33);
    CHECK(a == buf + 3);
    CHECK(*a == 33);
    CHECK(*--a == 22);
    CHECK(a == buf + 2);
    CHECK(*a == 22);
    CHECK(*a-- == 22);
    CHECK(a == buf + 1);
    CHECK(*a == 11);
    CHECK(*b == 0);
    CHECK(*c == 11);
    a -= 1;
    a += 7;
#ifdef UPX_VERSION_HEX
    CHECK(get_le32(a) == ne32_to_le32(77));
#endif
    a++;
#ifdef UPX_VERSION_HEX
    CHECK_THROWS(get_le32(a));
#endif
    CHECK_THROWS(raw_bytes(a, 1));
    CHECK_THROWS(a++);
    CHECK_THROWS(++a);
    CHECK_THROWS(a += 1);
    CHECK(a == buf + 8);
    a = buf;
    CHECK_THROWS(a--);
    CHECK_THROWS(--a);
    CHECK_THROWS(a -= 1);
    CHECK_THROWS(a += 9);
    CHECK(a == buf);
    a += 8;
    CHECK(a == buf + 8);
}

/*************************************************************************
// xspan global overloads
**************************************************************************/

#ifdef UPX_VERSION_HEX

namespace {
template <class T>
static noinline void check_bele_a(const T &mb, size_t i) {
    if (i < 2) {
        CHECK_THROWS(get_ne16(mb));
        CHECK_THROWS(get_be16(mb));
        CHECK_THROWS(get_le16(mb));
        CHECK_THROWS(set_ne16(mb, 0));
        CHECK_THROWS(set_be16(mb, 0));
        CHECK_THROWS(set_le16(mb, 0));
    } else {
        CHECK_NOTHROW(get_ne16(mb));
        CHECK_NOTHROW(get_be16(mb));
        CHECK_NOTHROW(get_le16(mb));
        CHECK_NOTHROW(set_ne16(mb, 0));
        CHECK_NOTHROW(set_be16(mb, 0));
        CHECK_NOTHROW(set_le16(mb, 0));
    }
    if (i < 3) {
        CHECK_THROWS(get_ne24(mb));
        CHECK_THROWS(get_be24(mb));
        CHECK_THROWS(get_le24(mb));
        CHECK_THROWS(set_ne24(mb, 0));
        CHECK_THROWS(set_be24(mb, 0));
        CHECK_THROWS(set_le24(mb, 0));
    } else {
        CHECK_NOTHROW(get_ne24(mb));
        CHECK_NOTHROW(get_be24(mb));
        CHECK_NOTHROW(get_le24(mb));
        CHECK_NOTHROW(set_ne24(mb, 0));
        CHECK_NOTHROW(set_be24(mb, 0));
        CHECK_NOTHROW(set_le24(mb, 0));
    }
    if (i < 4) {
        CHECK_THROWS(get_ne32(mb));
        CHECK_THROWS(get_be32(mb));
        CHECK_THROWS(get_le32(mb));
        CHECK_THROWS(set_ne32(mb, 0));
        CHECK_THROWS(set_be32(mb, 0));
        CHECK_THROWS(set_le32(mb, 0));
    } else {
        CHECK_NOTHROW(get_ne32(mb));
        CHECK_NOTHROW(get_be32(mb));
        CHECK_NOTHROW(get_le32(mb));
        CHECK_NOTHROW(set_ne32(mb, 0));
        CHECK_NOTHROW(set_be32(mb, 0));
        CHECK_NOTHROW(set_le32(mb, 0));
    }
    if (i < 8) {
        CHECK_THROWS(get_ne64(mb));
        CHECK_THROWS(get_be64(mb));
        CHECK_THROWS(get_le64(mb));
        CHECK_THROWS(set_ne64(mb, 0));
        CHECK_THROWS(set_be64(mb, 0));
        CHECK_THROWS(set_le64(mb, 0));
    } else {
        CHECK_NOTHROW(get_ne64(mb));
        CHECK_NOTHROW(get_be64(mb));
        CHECK_NOTHROW(get_le64(mb));
        CHECK_NOTHROW(set_ne64(mb, 0));
        CHECK_NOTHROW(set_be64(mb, 0));
        CHECK_NOTHROW(set_le64(mb, 0));
    }
    (void) mb;
}
template <class T>
static noinline void check_bele_c(const T &mb, size_t i) {
    if (i < 2) {
        CHECK_THROWS(get_ne16(mb));
        CHECK_THROWS(get_be16(mb));
        CHECK_THROWS(get_le16(mb));
    } else {
        CHECK_NOTHROW(get_ne16(mb));
        CHECK_NOTHROW(get_be16(mb));
        CHECK_NOTHROW(get_le16(mb));
    }
    if (i < 3) {
        CHECK_THROWS(get_ne24(mb));
        CHECK_THROWS(get_be24(mb));
        CHECK_THROWS(get_le24(mb));
    } else {
        CHECK_NOTHROW(get_ne24(mb));
        CHECK_NOTHROW(get_be24(mb));
        CHECK_NOTHROW(get_le24(mb));
    }
    if (i < 4) {
        CHECK_THROWS(get_ne32(mb));
        CHECK_THROWS(get_be32(mb));
        CHECK_THROWS(get_le32(mb));
    } else {
        CHECK_NOTHROW(get_ne32(mb));
        CHECK_NOTHROW(get_be32(mb));
        CHECK_NOTHROW(get_le32(mb));
    }
    if (i < 8) {
        CHECK_THROWS(get_ne64(mb));
        CHECK_THROWS(get_be64(mb));
        CHECK_THROWS(get_le64(mb));
    } else {
        CHECK_NOTHROW(get_ne64(mb));
        CHECK_NOTHROW(get_be64(mb));
        CHECK_NOTHROW(get_le64(mb));
    }
    (void) mb;
}
} // namespace

#endif // UPX_VERSION_HEX

TEST_CASE("xspan global overloads") {
    byte buf_a[16] = {};
    const byte buf_c[16] = {};

    for (size_t i = 0; i < 16; i++) {
        XSPAN_0_VAR(byte, a0, buf_a, i);
        XSPAN_P_VAR(byte, ap, buf_a, i);
        XSPAN_S_VAR(byte, as, buf_a, i);

        XSPAN_0_VAR(const byte, c0, buf_c, i);
        XSPAN_P_VAR(const byte, cp, buf_c, i);
        XSPAN_S_VAR(const byte, cs, buf_c, i);

#ifdef UPX_VERSION_HEX
        check_bele_a(a0, i);
        check_bele_a(ap, i);
        check_bele_a(as, i);

        check_bele_c(c0, i);
        check_bele_c(cp, i);
        check_bele_c(cs, i);

        CHECK_THROWS(upx_adler32(a0, i + 1));
        CHECK_THROWS(upx_adler32(ap, i + 1));
        CHECK_THROWS(upx_adler32(as, i + 1));
        CHECK_THROWS(upx_adler32(c0, i + 1));
        CHECK_THROWS(upx_adler32(cp, i + 1));
        CHECK_THROWS(upx_adler32(cs, i + 1));

        if (i == 0) {
            CHECK_THROWS(upx_safe_strlen(a0));
            CHECK_THROWS(upx_safe_strlen(ap));
            CHECK_THROWS(upx_safe_strlen(as));
            CHECK_THROWS(upx_safe_strlen(c0));
            CHECK_THROWS(upx_safe_strlen(cp));
            CHECK_THROWS(upx_safe_strlen(cs));
        }
#endif

        CHECK_THROWS(memchr(a0, 0, i + 1)); // NOLINT(bugprone-unused-return-value)
        CHECK_THROWS(memchr(ap, 0, i + 1)); // NOLINT(bugprone-unused-return-value)
        CHECK_THROWS(memchr(as, 0, i + 1)); // NOLINT(bugprone-unused-return-value)
        CHECK_THROWS(memchr(c0, 0, i + 1)); // NOLINT(bugprone-unused-return-value)
        CHECK_THROWS(memchr(cp, 0, i + 1)); // NOLINT(bugprone-unused-return-value)
        CHECK_THROWS(memchr(cs, 0, i + 1)); // NOLINT(bugprone-unused-return-value)

        CHECK(memcmp(a0, a0, i) == 0);
        CHECK(memcmp(ap, a0, i) == 0);
        CHECK(memcmp(as, a0, i) == 0);
        CHECK_THROWS(memcmp(a0, a0, i + 1)); // NOLINT(bugprone-unused-return-value)
        CHECK_THROWS(memcmp(ap, a0, i + 1)); // NOLINT(bugprone-unused-return-value)
        CHECK_THROWS(memcmp(as, a0, i + 1)); // NOLINT(bugprone-unused-return-value)

        CHECK_NOTHROW(memcpy(a0, c0, i));
        CHECK_NOTHROW(memcpy(ap, cp, i));
        CHECK_NOTHROW(memcpy(as, cs, i));
        CHECK_THROWS(memcpy(a0, c0, i + 1));
        CHECK_THROWS(memcpy(ap, cp, i + 1));
        CHECK_THROWS(memcpy(as, cs, i + 1));

        CHECK_NOTHROW(memmove(a0, c0, i));
        CHECK_NOTHROW(memmove(ap, cp, i));
        CHECK_NOTHROW(memmove(as, cs, i));
        CHECK_THROWS(memmove(a0, c0, i + 1));
        CHECK_THROWS(memmove(ap, cp, i + 1));
        CHECK_THROWS(memmove(as, cs, i + 1));

        CHECK_NOTHROW(memset(a0, 255, i));
        CHECK_NOTHROW(memset(ap, 255, i));
        CHECK_NOTHROW(memset(as, 255, i));
        CHECK_THROWS(memset(a0, 254, i + 1));
        CHECK_THROWS(memset(ap, 254, i + 1));
        CHECK_THROWS(memset(as, 254, i + 1));
    }
}

/*************************************************************************
// codegen
**************************************************************************/

namespace {
template <class T>
static noinline int foo(T p) {
    unsigned r = 0;
    r += *p++;
    r += *++p;
    p += 3;
    r += *p;
    r += *--p;
    r += *p--;
    r += *p;
    return r;
}

template <class T>
XSPAN_0(T)
make_span_0(T *ptr, size_t count) {
    return PtrOrSpanOrNull<T>(ptr, count);
}
template <class T>
XSPAN_P(T)
make_span_p(T *ptr, size_t count) {
    return PtrOrSpan<T>(ptr, count);
}
template <class T>
XSPAN_S(T)
make_span_s(T *ptr, size_t count) {
    return Span<T>(ptr, count);
}
} // namespace

TEST_CASE("Span codegen") {
    upx_uint8_t buf[8] = {0, 1, 2, 3, 4, 5, 6, 7};
    CHECK(foo(buf) == 0 + 2 + 5 + 4 + 4 + 3);
    CHECK(foo(make_span_0(buf, 8)) == 0 + 2 + 5 + 4 + 4 + 3);
    CHECK(foo(make_span_p(buf, 8)) == 0 + 2 + 5 + 4 + 4 + 3);
    CHECK(foo(make_span_s(buf, 8)) == 0 + 2 + 5 + 4 + 4 + 3);
    CHECK(foo(XSPAN_0_MAKE(upx_uint8_t, buf, 8)) == 0 + 2 + 5 + 4 + 4 + 3);
    CHECK(foo(XSPAN_P_MAKE(upx_uint8_t, buf, 8)) == 0 + 2 + 5 + 4 + 4 + 3);
    CHECK(foo(XSPAN_S_MAKE(upx_uint8_t, buf, 8)) == 0 + 2 + 5 + 4 + 4 + 3);
    UNUSED(buf);
}

#endif // WITH_XSPAN >= 2

/*************************************************************************
// misc
**************************************************************************/

namespace {
template <class T>
struct PointerTraits {
    typedef typename std::add_lvalue_reference<T>::type reference;
    typedef
        typename std::add_lvalue_reference<typename std::add_const<T>::type>::type const_reference;
    typedef typename std::add_pointer<T>::type pointer;
    typedef typename std::add_pointer<typename std::add_const<T>::type>::type const_pointer;
};
} // namespace

#if __cplusplus >= 201103L

TEST_CASE("decltype integral constants") {
    static_assert((std::is_same<decltype(0), int>::value), "");
    static_assert((std::is_same<decltype(0u), unsigned>::value), "");
    static_assert((std::is_same<decltype(0l), long>::value), "");
    static_assert((std::is_same<decltype(0ul), unsigned long>::value), "");
    static_assert((std::is_same<decltype(0ll), long long>::value), "");
    static_assert((std::is_same<decltype(0ull), unsigned long long>::value), "");
    static_assert((std::is_same<decltype((char) 0), char>::value), "");
    static_assert((std::is_same<decltype((short) 0), short>::value), "");
    static_assert((std::is_same<decltype((int) 0), int>::value), "");
    static_assert((std::is_same<decltype((long) 0), long>::value), "");
    static_assert((std::is_same<decltype((long long) 0), long long>::value), "");
    static_assert((std::is_same<decltype(char(0)), char>::value), "");
    static_assert((std::is_same<decltype(short(0)), short>::value), "");
    static_assert((std::is_same<decltype(int(0)), int>::value), "");
    static_assert((std::is_same<decltype(long(0)), long>::value), "");
    //// static_assert((std::is_same<decltype(long long(0)), long long>::value), "");
    using my_llong = long long;
    static_assert((std::is_same<decltype(my_llong(0)), long long>::value), "");
}

TEST_CASE("decltype pointer") {
    int dummy = 0;
    int *p = &dummy;
    const int *c = &dummy;
    static_assert((std::is_same<decltype(p - p), std::ptrdiff_t>::value), "");
    static_assert((std::is_same<decltype(c - c), std::ptrdiff_t>::value), "");
    static_assert((std::is_same<decltype(p - c), std::ptrdiff_t>::value), "");
    static_assert((std::is_same<decltype(c - p), std::ptrdiff_t>::value), "");
    typedef PointerTraits<int> TInt;
    typedef PointerTraits<const int> TConstInt;
    static_assert((std::is_same<int *, TInt::pointer>::value), "");
    static_assert((std::is_same<const int *, TInt::const_pointer>::value), "");
    static_assert((std::is_same<const int *, TConstInt::pointer>::value), "");
    static_assert((std::is_same<const int *, TConstInt::const_pointer>::value), "");
    //
    static_assert((std::is_same<decltype(p), TInt::pointer>::value), "");
    static_assert((std::is_same<decltype(c), TInt::const_pointer>::value), "");
    static_assert((std::is_same<decltype(c), TConstInt::pointer>::value), "");
    static_assert((std::is_same<decltype(p + 1), TInt::pointer>::value), "");
    static_assert((std::is_same<decltype(c + 1), TInt::const_pointer>::value), "");
    static_assert((std::is_same<decltype(c + 1), TConstInt::pointer>::value), "");
    static_assert((std::is_same<decltype(c + 1), TConstInt::const_pointer>::value), "");
    static_assert((std::is_same<decltype(c + 1), const int *>::value), "");
    // dereference
    static_assert((std::is_same<decltype(*p), TInt::reference>::value), "");
    static_assert((std::is_same<decltype(*c), TInt::const_reference>::value), "");
#if 0
    // this works, but avoid clang warnings:
    //   "Expression with side effects has no effect in an unevaluated context"
    static_assert((std::is_same<decltype(*p++), TInt::reference>::value), "");
    static_assert((std::is_same<decltype(*++p), TInt::reference>::value), "");
    static_assert((std::is_same<decltype(*c++), TInt::const_reference>::value), "");
    static_assert((std::is_same<decltype(*c++), TConstInt::reference>::value), "");
    static_assert((std::is_same<decltype(*c++), TConstInt::const_reference>::value), "");
    static_assert((std::is_same<decltype(*++c), TInt::const_reference>::value), "");
    static_assert((std::is_same<decltype(*++c), TConstInt::reference>::value), "");
    static_assert((std::is_same<decltype(*++c), TConstInt::const_reference>::value), "");
#endif
    // array access
    static_assert((std::is_same<decltype(p[0]), TInt::reference>::value), "");
    static_assert((std::is_same<decltype(c[0]), TInt::const_reference>::value), "");
    static_assert((std::is_same<decltype(c[0]), TConstInt::reference>::value), "");
    static_assert((std::is_same<decltype(c[0]), TConstInt::const_reference>::value), "");
    UNUSED(p);
    UNUSED(c);
}

#endif // __cplusplus >= 201103L

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