//===----------------------------------------------------------------------===//
//
// 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, c++11, c++14, c++17
// <memory>
// shared_ptr
// template<class T, class A>
// shared_ptr<T> allocate_shared(const A& a); // T is U[N]
//
// template<class T, class A>
// shared_ptr<T> allocate_shared(const A& a, const remove_extent_t<T>& u); // T is U[N]
#include <cassert>
#include <concepts>
#include <cstdint> // std::uintptr_t
#include <memory>
#include <utility>
#include "min_allocator.h"
#include "operator_hijacker.h"
#include "types.h"
template <class T, class ...Args>
concept CanAllocateShared = requires(Args&& ...args) {
{ std::allocate_shared<T>(std::forward<Args>(args)...) } -> std::same_as<std::shared_ptr<T>>;
};
int main(int, char**) {
// Make sure we initialize elements correctly
{
// Without passing an initial value
{
using Array = int[8];
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
for (unsigned i = 0; i < 8; ++i) {
assert(ptr[i] == 0);
}
}
{
using Array = int[8][3];
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
for (unsigned i = 0; i < 8; ++i) {
assert(ptr[i][0] == 0);
assert(ptr[i][1] == 0);
assert(ptr[i][2] == 0);
}
}
{
using Array = int[8][3][2];
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
for (unsigned i = 0; i < 8; ++i) {
assert(ptr[i][0][0] == 0);
assert(ptr[i][0][1] == 0);
assert(ptr[i][1][0] == 0);
assert(ptr[i][1][1] == 0);
assert(ptr[i][2][0] == 0);
assert(ptr[i][2][1] == 0);
}
}
// Passing an initial value
{
using Array = int[8];
int init = 42;
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
for (unsigned i = 0; i < 8; ++i) {
assert(ptr[i] == init);
}
}
{
using Array = int[8][3];
int init[3] = {42, 43, 44};
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
for (unsigned i = 0; i < 8; ++i) {
assert(ptr[i][0] == 42);
assert(ptr[i][1] == 43);
assert(ptr[i][2] == 44);
}
}
{
using Array = int[8][3][2];
int init[3][2] = {{31, 32}, {41, 42}, {51, 52}};
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
for (unsigned i = 0; i < 8; ++i) {
assert(ptr[i][0][0] == 31);
assert(ptr[i][0][1] == 32);
assert(ptr[i][1][0] == 41);
assert(ptr[i][1][1] == 42);
assert(ptr[i][2][0] == 51);
assert(ptr[i][2][1] == 52);
}
}
}
// Make sure array elements are destroyed in reverse order
{
// Without passing an initial value
{
using Array = DestroyInReverseOrder[8];
DestroyInReverseOrder::reset();
{
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
assert(DestroyInReverseOrder::alive() == 8);
}
assert(DestroyInReverseOrder::alive() == 0);
}
{
using Array = DestroyInReverseOrder[8][3];
DestroyInReverseOrder::reset();
{
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
assert(DestroyInReverseOrder::alive() == 8 * 3);
}
assert(DestroyInReverseOrder::alive() == 0);
}
{
using Array = DestroyInReverseOrder[8][3][2];
DestroyInReverseOrder::reset();
{
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
assert(DestroyInReverseOrder::alive() == 8 * 3 * 2);
}
assert(DestroyInReverseOrder::alive() == 0);
}
// Passing an initial value
{
using Array = DestroyInReverseOrder[8];
int count = 0;
DestroyInReverseOrder init(&count);
int init_count = 1;
{
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
assert(count == 8 + init_count);
}
assert(count == init_count);
}
{
using Array = DestroyInReverseOrder[8][3];
int count = 0;
DestroyInReverseOrder init[3] = {&count, &count, &count};
int init_count = 3;
{
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
assert(count == 8 * 3 + init_count);
}
assert(count == init_count);
}
{
using Array = DestroyInReverseOrder[8][3][2];
int count = 0;
DestroyInReverseOrder init[3][2] = {{&count, &count}, {&count, &count}, {&count, &count}};
int init_count = 3 * 2;
{
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
assert(count == 8 * 3 * 2 + init_count);
}
assert(count == init_count);
}
}
// Count the number of copies being made
{
// Without passing an initial value
{
using Array = CountCopies[8];
CountCopies::reset();
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
assert(CountCopies::copies() == 0);
}
{
using Array = CountCopies[8][3];
CountCopies::reset();
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());;
assert(CountCopies::copies() == 0);
}
{
using Array = CountCopies[8][3][2];
CountCopies::reset();
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());;
assert(CountCopies::copies() == 0);
}
// Passing an initial value
{
using Array = CountCopies[8];
int copies = 0;
CountCopies init(&copies);
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
assert(copies == 8);
}
{
using Array = CountCopies[8][3];
int copies = 0;
CountCopies init[3] = {&copies, &copies, &copies};
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
assert(copies == 8 * 3);
}
{
using Array = CountCopies[8][3][2];
int copies = 0;
CountCopies init[3][2] = {{&copies, &copies}, {&copies, &copies}, {&copies, &copies}};
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
assert(copies == 8 * 3 * 2);
}
}
// Make sure array elements are aligned properly when the array contains an overaligned type.
//
// Here, we don't need to test both the with-initial-value and without-initial-value code paths,
// since we're just checking the alignment and both are going to use the same code path unless
// the implementation is completely crazy.
{
auto check_alignment = []<class T> {
{
using Array = T[8];
std::shared_ptr ptr = std::allocate_shared<Array>(std::allocator<Array>());
for (int i = 0; i < 8; ++i) {
T* p = std::addressof(ptr[i]);
assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
}
}
{
using Array = T[8][3];
std::shared_ptr ptr = std::allocate_shared<Array>(std::allocator<Array>());
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 3; ++j) {
T* p = std::addressof(ptr[i][j]);
assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
}
}
}
{
using Array = T[8][3][2];
std::shared_ptr ptr = std::allocate_shared<Array>(std::allocator<Array>());
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 3; ++j) {
for (int k = 0; k < 2; ++k) {
T* p = std::addressof(ptr[i][j][k]);
assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
}
}
}
}
};
struct Empty { };
check_alignment.operator()<Empty>();
check_alignment.operator()<OverAligned>();
check_alignment.operator()<MaxAligned>();
// test non corner cases as well while we're at it
struct Foo { int i; char c; };
check_alignment.operator()<int>();
check_alignment.operator()<Foo>();
}
// Make sure that we destroy all the elements constructed so far when an exception
// is thrown. Also make sure that we do it in reverse order of construction.
#ifndef TEST_HAS_NO_EXCEPTIONS
{
struct Sentinel : ThrowOnConstruction, DestroyInReverseOrder { };
// Without passing an initial value
{
using Array = Sentinel[8];
for (int i = 0; i < 8; ++i) {
ThrowOnConstruction::throw_after(i);
DestroyInReverseOrder::reset();
try {
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
assert(false);
} catch (ThrowOnConstruction::exception const&) {
assert(DestroyInReverseOrder::alive() == 0);
}
}
}
{
using Array = Sentinel[8][3];
for (int i = 0; i < 8 * 3; ++i) {
ThrowOnConstruction::throw_after(i);
DestroyInReverseOrder::reset();
try {
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
assert(false);
} catch (ThrowOnConstruction::exception const&) {
assert(DestroyInReverseOrder::alive() == 0);
}
}
}
{
using Array = Sentinel[8][3][2];
for (int i = 0; i < 8 * 3 * 2; ++i) {
ThrowOnConstruction::throw_after(i);
DestroyInReverseOrder::reset();
try {
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
assert(false);
} catch (ThrowOnConstruction::exception const&) {
assert(DestroyInReverseOrder::alive() == 0);
}
}
}
// Passing an initial value
{
using Array = Sentinel[8];
for (int i = 0; i < 8; ++i) {
DestroyInReverseOrder::reset();
ThrowOnConstruction::reset();
Sentinel init;
ThrowOnConstruction::throw_after(i);
try {
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
assert(false);
} catch (ThrowOnConstruction::exception const&) {
assert(DestroyInReverseOrder::alive() == 1);
}
}
}
{
using Array = Sentinel[8][3];
for (int i = 0; i < 8 * 3; ++i) {
DestroyInReverseOrder::reset();
ThrowOnConstruction::reset();
Sentinel init[3] = {};
ThrowOnConstruction::throw_after(i);
try {
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
assert(false);
} catch (ThrowOnConstruction::exception const&) {
assert(DestroyInReverseOrder::alive() == 3);
}
}
}
{
using Array = Sentinel[8][3][2];
for (int i = 0; i < 8 * 3 * 2; ++i) {
DestroyInReverseOrder::reset();
ThrowOnConstruction::reset();
Sentinel init[3][2] = {};
ThrowOnConstruction::throw_after(i);
try {
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
assert(false);
} catch (ThrowOnConstruction::exception const&) {
assert(DestroyInReverseOrder::alive() == 3 * 2);
}
}
}
}
#endif // TEST_HAS_NO_EXCEPTIONS
// Test with another allocator that's not std::allocator
{
// Without passing an initial value
{
using Array = int[8][3];
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(min_allocator<Array>());
for (unsigned i = 0; i < 8; ++i) {
assert(ptr[i][0] == 0);
assert(ptr[i][1] == 0);
assert(ptr[i][2] == 0);
}
}
// Passing an initial value
{
using Array = int[8][3];
int init[3] = {42, 43, 44};
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(min_allocator<Array>(), init);
for (unsigned i = 0; i < 8; ++i) {
assert(ptr[i][0] == 42);
assert(ptr[i][1] == 43);
assert(ptr[i][2] == 44);
}
}
}
// Make sure the version without an initialization argument works even for non-movable types
{
using Array = NonMovable[8][3];
std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
(void)ptr;
}
// Make sure std::allocate_shared handles badly-behaved types properly
{
using Array = operator_hijacker[3];
std::shared_ptr<Array> p1 = std::allocate_shared<Array>(std::allocator<Array>());
std::shared_ptr<Array> p2 = std::allocate_shared<Array>(std::allocator<Array>(), operator_hijacker());
assert(p1 != nullptr);
assert(p2 != nullptr);
}
// Check that we SFINAE-away for invalid arguments
{
struct T { };
static_assert( CanAllocateShared<T[8], std::allocator<T[8]>>);
static_assert( CanAllocateShared<T[8], std::allocator<T[8]>, T>);
static_assert(!CanAllocateShared<T[8], std::allocator<T[8]>, T, int>); // too many arguments
static_assert(!CanAllocateShared<T[8], std::allocator<T[8]>, int>); // T is not constructible from int
}
return 0;
}