/*
 * Copyright (C) 2017-2018 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include <mutex>
#include <wtf/PoisonedUniquePtr.h>

namespace TestWebKitAPI {

namespace {

uintptr_t g_poisonA;
uintptr_t g_poisonB;

using PoisonA = Poison<g_poisonA>;
using PoisonB = Poison<g_poisonB>;

static void initializePoisons()
{
    static std::once_flag initializeOnceFlag;
    std::call_once(initializeOnceFlag, [] {
        // Make sure we get 2 different poison values.
        g_poisonA = makePoison();
        while (!g_poisonB || g_poisonB == g_poisonA)
            g_poisonB = makePoison();
    });
}

struct Logger {
    Logger() { }
    Logger(const char* name, int& destructCount)
        : name(name)
        , destructCount(&destructCount)
    { }

    ~Logger() { ++(*destructCount); }

    const char* name;
    int* destructCount;
};
    
template<typename T, typename... Arguments>
T* makeArray(size_t count, Arguments&&... arguments)
{
    T* result = new T[count];
    while (count--)
        new (result + count) T(std::forward<Arguments>(arguments)...);
    return result;
}

const int arraySize = 5;

} // anonymous namespace

TEST(WTF_PoisonedUniquePtrForNonTriviallyDestructibleArrays, DISABLED_Basic)
{
    initializePoisons();

    {
        PoisonedUniquePtr<PoisonA, Logger[]> empty;
        ASSERT_EQ(nullptr, empty.unpoisoned());
        ASSERT_EQ(0u, empty.bits());
    }

    {
        int aDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> ptr(a);
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(a, ptr.unpoisoned());
            ASSERT_EQ(a, &*ptr);
            for (auto i = 0; i < arraySize; ++i)
                ASSERT_EQ(a[i].name, ptr[i].name);

#if ENABLE(POISON)
            uintptr_t ptrBits;
            std::memcpy(&ptrBits, &ptr, sizeof(ptrBits));
            ASSERT_TRUE(ptrBits != bitwise_cast<uintptr_t>(a));
#if ENABLE(POISON_ASSERTS)
            ASSERT_TRUE((PoisonedUniquePtr<PoisonA, Logger[]>::isPoisoned(ptrBits)));
#endif
#endif // ENABLE(POISON)
        }
        ASSERT_EQ(arraySize, aDestructCount);
    }

    {
        int aDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> ptr = a;
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(a, ptr.unpoisoned());
            for (auto i = 0; i < arraySize; ++i)
                ASSERT_EQ(a[i].name, ptr[i].name);
        }
        ASSERT_EQ(arraySize, aDestructCount);
    }

    {
        int aDestructCount = 0;
        const char* aName = "a";
        {
            PoisonedUniquePtr<PoisonA, Logger[]> ptr = PoisonedUniquePtr<PoisonA, Logger[]>::create(arraySize, aName, aDestructCount);
            ASSERT_EQ(0, aDestructCount);
            ASSERT_TRUE(nullptr != ptr.unpoisoned());
            for (auto i = 0; i < arraySize; ++i)
                ASSERT_EQ(aName, ptr[i].name);
        }
        ASSERT_EQ(arraySize, aDestructCount);
    }

    {
        int aDestructCount = 0;
        int bDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        Logger* b = makeArray<Logger>(arraySize, "b", bDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> p1 = a;
            PoisonedUniquePtr<PoisonA, Logger[]> p2 = WTFMove(p1);
            ASSERT_EQ(aDestructCount, 0);
            ASSERT_EQ(nullptr, p1.unpoisoned());
            ASSERT_EQ(0u, p1.bits());
            ASSERT_EQ(a, p2.unpoisoned());

            PoisonedUniquePtr<PoisonA, Logger[]> p3 = b;
            PoisonedUniquePtr<PoisonB, Logger[]> p4 = WTFMove(p3);
            ASSERT_EQ(0, bDestructCount);
            ASSERT_EQ(nullptr, p3.unpoisoned());
            ASSERT_EQ(0u, p3.bits());
            ASSERT_EQ(b, p4.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
        ASSERT_EQ(arraySize, bDestructCount);
    }

    {
        int aDestructCount = 0;
        int bDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        Logger* b = makeArray<Logger>(arraySize, "b", bDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> p1 = a;
            PoisonedUniquePtr<PoisonA, Logger[]> p2(WTFMove(p1));
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(nullptr, p1.unpoisoned());
            ASSERT_EQ(0u, p1.bits());
            ASSERT_EQ(a, p2.unpoisoned());

            PoisonedUniquePtr<PoisonA, Logger[]> p3 = b;
            PoisonedUniquePtr<PoisonB, Logger[]> p4(WTFMove(p3));
            ASSERT_EQ(0, bDestructCount);
            ASSERT_EQ(nullptr, p3.unpoisoned());
            ASSERT_EQ(0u, p3.bits());
            ASSERT_EQ(b, p4.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
        ASSERT_EQ(arraySize, bDestructCount);
    }

    {
        int aDestructCount = 0;
        int bDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        Logger* b = makeArray<Logger>(arraySize, "b", bDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> p1 = a;
            PoisonedUniquePtr<PoisonA, Logger[]> p2 = WTFMove(p1);
            ASSERT_EQ(aDestructCount, 0);
            ASSERT_TRUE(!p1.unpoisoned());
            ASSERT_TRUE(!p1.bits());
            ASSERT_EQ(a, p2.unpoisoned());

            PoisonedUniquePtr<PoisonA, Logger[]> p3 = b;
            PoisonedUniquePtr<PoisonB, Logger[]> p4 = WTFMove(p3);
            ASSERT_EQ(bDestructCount, 0);
            ASSERT_TRUE(!p3.unpoisoned());
            ASSERT_TRUE(!p3.bits());
            ASSERT_EQ(b, p4.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
        ASSERT_EQ(arraySize, bDestructCount);
    }

    {
        int aDestructCount = 0;
        int bDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        Logger* b = makeArray<Logger>(arraySize, "b", bDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> p1 = a;
            PoisonedUniquePtr<PoisonA, Logger[]> p2(WTFMove(p1));
            ASSERT_EQ(aDestructCount, 0);
            ASSERT_TRUE(!p1.unpoisoned());
            ASSERT_TRUE(!p1.bits());
            ASSERT_EQ(a, p2.unpoisoned());

            PoisonedUniquePtr<PoisonA, Logger[]> p3 = b;
            PoisonedUniquePtr<PoisonB, Logger[]> p4(WTFMove(p3));
            ASSERT_EQ(bDestructCount, 0);
            ASSERT_TRUE(!p3.unpoisoned());
            ASSERT_TRUE(!p3.bits());
            ASSERT_EQ(b, p4.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
        ASSERT_EQ(arraySize, bDestructCount);
    }

    {
        int aDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> ptr(a);
            ASSERT_EQ(a, ptr.unpoisoned());
            ptr.clear();
            ASSERT_TRUE(!ptr.unpoisoned());
            ASSERT_TRUE(!ptr.bits());
            ASSERT_EQ(arraySize, aDestructCount);
        }
        ASSERT_EQ(arraySize, aDestructCount);
    }
}

TEST(WTF_PoisonedUniquePtrForNonTriviallyDestructibleArrays, DISABLED_Assignment)
{
    initializePoisons();

    {
        int aDestructCount = 0;
        int bDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        Logger* b = makeArray<Logger>(arraySize, "b", bDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> ptr(a);
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(0, bDestructCount);
            ASSERT_EQ(a, ptr.unpoisoned());
            ptr = b;
            ASSERT_EQ(arraySize, aDestructCount);
            ASSERT_EQ(0, bDestructCount);
            ASSERT_EQ(b, ptr.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
        ASSERT_EQ(arraySize, bDestructCount);
    }

    {
        int aDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> ptr(a);
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(a, ptr.unpoisoned());
            ptr = nullptr;
            ASSERT_EQ(arraySize, aDestructCount);
            ASSERT_EQ(nullptr, ptr.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
    }

    {
        int aDestructCount = 0;
        int bDestructCount = 0;
        int cDestructCount = 0;
        int dDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        Logger* b = makeArray<Logger>(arraySize, "b", bDestructCount);
        Logger* c = makeArray<Logger>(arraySize, "c", cDestructCount);
        Logger* d = makeArray<Logger>(arraySize, "d", dDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> p1(a);
            PoisonedUniquePtr<PoisonA, Logger[]> p2(b);
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(0, bDestructCount);
            ASSERT_EQ(a, p1.unpoisoned());
            ASSERT_EQ(b, p2.unpoisoned());
            p1 = WTFMove(p2);
            ASSERT_EQ(arraySize, aDestructCount);
            ASSERT_EQ(0, bDestructCount);
            ASSERT_EQ(b, p1.unpoisoned());
            ASSERT_EQ(nullptr, p2.unpoisoned());

            PoisonedUniquePtr<PoisonA, Logger[]> p3(c);
            PoisonedUniquePtr<PoisonB, Logger[]> p4(d);
            ASSERT_EQ(0, cDestructCount);
            ASSERT_EQ(0, dDestructCount);
            ASSERT_EQ(c, p3.unpoisoned());
            ASSERT_EQ(d, p4.unpoisoned());
            p3 = WTFMove(p4);
            ASSERT_EQ(arraySize, cDestructCount);
            ASSERT_EQ(0, dDestructCount);
            ASSERT_EQ(d, p3.unpoisoned());
            ASSERT_EQ(nullptr, p4.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
        ASSERT_EQ(arraySize, bDestructCount);
        ASSERT_EQ(arraySize, cDestructCount);
        ASSERT_EQ(arraySize, dDestructCount);
    }

    {
        int aDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> ptr(a);
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(a, ptr.unpoisoned());
            ptr = a;
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(a, ptr.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
    }

    {
        int aDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> ptr(a);
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(a, ptr.unpoisoned());
#if COMPILER(CLANG)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wself-move"
#endif
            ptr = WTFMove(ptr);
#if COMPILER(CLANG)
#pragma clang diagnostic pop
#endif
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(a, ptr.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
    }
}

TEST(WTF_PoisonedUniquePtrForNonTriviallyDestructibleArrays, DISABLED_Swap)
{
    initializePoisons();

    {
        int aDestructCount = 0;
        int bDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        Logger* b = makeArray<Logger>(arraySize, "b", bDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> p1 = a;
            PoisonedUniquePtr<PoisonA, Logger[]> p2;
            ASSERT_EQ(a, p1.unpoisoned());
            ASSERT_TRUE(!p2.bits());
            ASSERT_TRUE(!p2.unpoisoned());
            p2.swap(p1);
            ASSERT_EQ(aDestructCount, 0);
            ASSERT_TRUE(!p1.bits());
            ASSERT_TRUE(!p1.unpoisoned());
            ASSERT_EQ(a, p2.unpoisoned());

            PoisonedUniquePtr<PoisonA, Logger[]> p3 = b;
            PoisonedUniquePtr<PoisonB, Logger[]> p4;
            ASSERT_EQ(b, p3.unpoisoned());
            ASSERT_TRUE(!p4.bits());
            ASSERT_TRUE(!p4.unpoisoned());
            p4.swap(p3);
            ASSERT_EQ(0, bDestructCount);
            ASSERT_TRUE(!p3.bits());
            ASSERT_TRUE(!p3.unpoisoned());
            ASSERT_EQ(b, p4.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
        ASSERT_EQ(arraySize, bDestructCount);
    }

    {
        int aDestructCount = 0;
        int bDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        Logger* b = makeArray<Logger>(arraySize, "b", bDestructCount);
        {
            PoisonedUniquePtr<PoisonA, Logger[]> p1 = a;
            PoisonedUniquePtr<PoisonA, Logger[]> p2;
            ASSERT_EQ(a, p1.unpoisoned());
            ASSERT_TRUE(!p2.bits());
            ASSERT_TRUE(!p2.unpoisoned());
            swap(p1, p2);
            ASSERT_EQ(0, aDestructCount);
            ASSERT_TRUE(!p1.bits());
            ASSERT_TRUE(!p1.unpoisoned());
            ASSERT_EQ(a, p2.unpoisoned());

            PoisonedUniquePtr<PoisonA, Logger[]> p3 = b;
            PoisonedUniquePtr<PoisonB, Logger[]> p4;
            ASSERT_EQ(b, p3.unpoisoned());
            ASSERT_TRUE(!p4.bits());
            ASSERT_TRUE(!p4.unpoisoned());
            swap(p3, p4);
            ASSERT_EQ(0, bDestructCount);
            ASSERT_TRUE(!p3.bits());
            ASSERT_TRUE(!p3.unpoisoned());
            ASSERT_EQ(b, p4.unpoisoned());
        }
        ASSERT_EQ(arraySize, aDestructCount);
        ASSERT_EQ(arraySize, bDestructCount);
    }
}

static PoisonedUniquePtr<PoisonA, Logger[]> poisonedPtrFoo(Logger* array)
{
    return PoisonedUniquePtr<PoisonA, Logger[]>(array);
}

TEST(WTF_PoisonedUniquePtrForNonTriviallyDestructibleArrays, DISABLED_ReturnValue)
{
    initializePoisons();

    {
        int aDestructCount = 0;
        Logger* a = makeArray<Logger>(arraySize, "a", aDestructCount);
        {
            auto ptr = poisonedPtrFoo(a);
            ASSERT_EQ(0, aDestructCount);
            ASSERT_EQ(a, ptr.unpoisoned());
            ASSERT_EQ(a, &*ptr);
            for (auto i = 0; i < arraySize; ++i)
                ASSERT_EQ(a[i].name, ptr[i].name);
        }
        ASSERT_EQ(arraySize, aDestructCount);
    }
}

} // namespace TestWebKitAPI

