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

// REQUIRES: has-unix-headers
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-hardening-mode=none
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing

// <memory>
//
// unique_ptr<T[]>
//
// T& operator[](std::size_t);

// This test ensures that we catch an out-of-bounds access in std::unique_ptr<T[]>::operator[]
// when unique_ptr has the appropriate ABI configuration.

#include <memory>
#include <cstddef>
#include <string>

#include "check_assertion.h"
#include "type_algorithms.h"
#include "test_macros.h"

struct MyDeleter {
  MyDeleter() = default;

  // required to exercise converting move-constructor
  template <class T>
  MyDeleter(std::default_delete<T> const&) {}

  // required to exercise converting move-assignment
  template <class T>
  MyDeleter& operator=(std::default_delete<T> const&) {
    return *this;
  }

  template <class T>
  void operator()(T* ptr) const {
    delete[] ptr;
  }
};

template <class WithCookie, class NoCookie>
void test() {
  LIBCPP_STATIC_ASSERT(std::__has_array_cookie<WithCookie>::value);
  LIBCPP_STATIC_ASSERT(!std::__has_array_cookie<NoCookie>::value);

  // For types with an array cookie, we can always detect OOB accesses. Note that reliance on an array
  // cookie is limited to the default deleter, since a unique_ptr with a custom deleter may not have
  // been allocated with `new T[n]`.
  {
    {
      std::unique_ptr<WithCookie[]> ptr(new WithCookie[5]);
      TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
    }
    {
      std::unique_ptr<WithCookie[]> ptr = std::make_unique<WithCookie[]>(5);
      TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
    }
#if TEST_STD_VER >= 20
    {
      std::unique_ptr<WithCookie[]> ptr = std::make_unique_for_overwrite<WithCookie[]>(5);
      TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = WithCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
    }
#endif
  }

  // For types that don't have an array cookie, things are a bit more complicated. We can detect OOB accesses
  // only when the unique_ptr is created via an API where the size is passed down to the library so that we
  // can store it inside the unique_ptr. That requires the appropriate ABI configuration to be enabled.
  //
  // Note that APIs that allow the size to be passed down to the library only support the default deleter
  // as of writing this test.
#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
  {
    {
      std::unique_ptr<NoCookie[]> ptr = std::make_unique<NoCookie[]>(5);
      TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
    }
#  if TEST_STD_VER >= 20
    {
      std::unique_ptr<NoCookie[]> ptr = std::make_unique_for_overwrite<NoCookie[]>(5);
      TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = NoCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
    }
#  endif
  }
#endif

  // Make sure that we carry the bounds information properly through conversions, assignments, etc.
  // These tests are only relevant when the ABI setting is enabled (with a stateful bounds-checker).
#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
  types::for_each(types::type_list<NoCookie, WithCookie>(), []<class T> {
    // Bounds carried through move construction
    {
      std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
      std::unique_ptr<T[]> other(std::move(ptr));
      TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
    }

    // Bounds carried through move assignment
    {
      std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
      std::unique_ptr<T[]> other;
      other = std::move(ptr);
      TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
    }

    // Bounds carried through converting move-constructor
    {
      std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
      std::unique_ptr<T[], MyDeleter> other(std::move(ptr));
      TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
    }

    // Bounds carried through converting move-assignment
    {
      std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
      std::unique_ptr<T[], MyDeleter> other;
      other = std::move(ptr);
      TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
    }
  });
#endif
}

template <std::size_t Size>
struct NoCookie {
  char padding[Size];
};

template <std::size_t Size>
struct WithCookie {
  WithCookie() = default;
  WithCookie(WithCookie const&) {}
  WithCookie& operator=(WithCookie const&) { return *this; }
  ~WithCookie() {}
  char padding[Size];
};

int main(int, char**) {
  test<WithCookie<1>, NoCookie<1>>();
  test<WithCookie<2>, NoCookie<2>>();
  test<WithCookie<3>, NoCookie<3>>();
  test<WithCookie<4>, NoCookie<4>>();
  test<WithCookie<8>, NoCookie<8>>();
  test<WithCookie<16>, NoCookie<16>>();
  test<WithCookie<32>, NoCookie<32>>();
  test<WithCookie<256>, NoCookie<256>>();
  test<std::string, int>();

  return 0;
}
