//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03

// Aligned allocation is required by std::experimental::pmr, but it was not provided
// before macosx10.13 and as a result we get linker errors when deploying to older than
// macosx10.13.
// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{9|10|11|12}}

// <memory_resource>

// template <class T> class polymorphic_allocator

// template <class U1, class U2, class ...Args1, class ...Args2>
// void polymorphic_allocator<T>::construct(pair<U1, U2>*, piecewise_construct_t
//                                          tuple<Args1...>, tuple<Args2...>)

// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DISABLE_DEPRECATION_WARNINGS

#include <experimental/memory_resource>
#include <tuple>
#include <type_traits>
#include <utility>
#include <cassert>
#include <cstdlib>

#include "test_macros.h"

namespace ex = std::experimental::pmr;

template <class T>
struct EvilAlloc {
    explicit EvilAlloc() : inner_(ex::null_memory_resource()) {}

    EvilAlloc(ex::polymorphic_allocator<T> & a) : inner_(a) {}
    EvilAlloc(ex::polymorphic_allocator<T> && a) : inner_(a) {}
    EvilAlloc(ex::polymorphic_allocator<T> const & a) = delete;
    EvilAlloc(ex::polymorphic_allocator<T> const && a) = delete;

    using value_type = T;
    template <class U> EvilAlloc(EvilAlloc<U> const & rhs) : inner_(rhs.inner_) {}

    ex::polymorphic_allocator<T> inner_;
};

struct WidgetV0 {
    WidgetV0(int v) : value_(v) {}

    bool holds(int v, const ex::polymorphic_allocator<char>&) const {
        return value_ == v;
    }
private:
    int value_;
};

struct WidgetV1 {
    using allocator_type = EvilAlloc<char>;

    WidgetV1(int v) : value_(v), alloc_() {}
    WidgetV1(std::allocator_arg_t, EvilAlloc<char> a, int v) : value_(v), alloc_(a) {}

    bool holds(int v, const ex::polymorphic_allocator<char>& a) const {
        return value_ == v && alloc_.inner_ == a;
    }
private:
    int value_;
    EvilAlloc<char> alloc_;
};

struct WidgetV2 {
    using allocator_type = EvilAlloc<char>;

    WidgetV2(int v) : value_(v), alloc_() {}
    WidgetV2(int v, EvilAlloc<char> a) : value_(v), alloc_(a) {}

    bool holds(int v, ex::polymorphic_allocator<char> a) const {
        return value_ == v && alloc_.inner_ == a;
    }
private:
    int value_;
    EvilAlloc<char> alloc_;
};

struct WidgetV3 {
    using allocator_type = EvilAlloc<char>;

    WidgetV3(int v) : value_(v), alloc_() {}
    WidgetV3(std::allocator_arg_t, EvilAlloc<char> a, int v) : value_(v), alloc_(a) {}
    WidgetV3(int v, EvilAlloc<char> a) : value_(v), alloc_(a) {}

    bool holds(int v, ex::polymorphic_allocator<char> a) const {
        return value_ == v && alloc_.inner_ == a;
    }
private:
    int value_;
    EvilAlloc<char> alloc_;
};

static_assert(std::uses_allocator<WidgetV1, EvilAlloc<char>>::value, "");
static_assert(std::uses_allocator<WidgetV2, EvilAlloc<char>>::value, "");
static_assert(std::uses_allocator<WidgetV3, EvilAlloc<char>>::value, "");
static_assert(std::uses_allocator<WidgetV1, ex::polymorphic_allocator<char>>::value, "");
static_assert(std::uses_allocator<WidgetV2, ex::polymorphic_allocator<char>>::value, "");
static_assert(std::uses_allocator<WidgetV3, ex::polymorphic_allocator<char>>::value, "");

template<class W1, class W2>
void test_evil()
{
    using PMA = ex::polymorphic_allocator<char>;
    PMA pma(ex::new_delete_resource());
    {
        using Pair = std::pair<W1, W2>;
        void *where = std::malloc(sizeof (Pair));
        Pair *p = (Pair *)where;
        pma.construct(p, std::piecewise_construct, std::make_tuple(42), std::make_tuple(42));
        assert(p->first.holds(42, pma));
        assert(p->second.holds(42, pma));
        pma.destroy(p);
        std::free(where);
    }
}

int main(int, char**)
{
    test_evil<WidgetV0, WidgetV0>();
    test_evil<WidgetV0, WidgetV1>();
    test_evil<WidgetV0, WidgetV2>();
    test_evil<WidgetV0, WidgetV3>();
    test_evil<WidgetV1, WidgetV0>();
    test_evil<WidgetV1, WidgetV1>();
    test_evil<WidgetV1, WidgetV2>();
    test_evil<WidgetV1, WidgetV3>();
    test_evil<WidgetV2, WidgetV0>();
    test_evil<WidgetV2, WidgetV1>();
    test_evil<WidgetV2, WidgetV2>();
    test_evil<WidgetV2, WidgetV3>();
    test_evil<WidgetV3, WidgetV0>();
    test_evil<WidgetV3, WidgetV1>();
    test_evil<WidgetV3, WidgetV2>();
    test_evil<WidgetV3, WidgetV3>();

  return 0;
}
