diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -268,6 +268,8 @@ ------------------------------------------------- ----------------- ``__cpp_lib_semaphore`` ``201907L`` ------------------------------------------------- ----------------- + ``__cpp_lib_shared_ptr_arrays`` ``201707L`` + ------------------------------------------------- ----------------- ``__cpp_lib_shift`` ``201806L`` ------------------------------------------------- ----------------- ``__cpp_lib_smart_ptr_for_overwrite`` *unimplemented* diff --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst --- a/libcxx/docs/ReleaseNotes.rst +++ b/libcxx/docs/ReleaseNotes.rst @@ -42,6 +42,8 @@ - Implemented P1165R1 (Make stateful allocator propagation more consistent for ``operator+(basic_string)``) +- Implemented P0674R1 (Support arrays in make_shared and allocate_shared) + - `pop_heap` now uses an algorithm known as "bottom-up heapsort" or "heapsort with bounce" to reduce the number of comparisons, and rearranges elements using move-assignment instead of `swap`. diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -1,6 +1,6 @@ "Paper #","Group","Paper Name","Meeting","Status","First released version" "`P0463R1 `__","LWG","Endian just Endian","Toronto","|Complete|","7.0" -"`P0674R1 `__","LWG","Extending make_shared to Support Arrays","Toronto","","" +"`P0674R1 `__","LWG","Extending make_shared to Support Arrays","Toronto","|Complete|","15.0" "","","","","","" "`P0020R6 `__","LWG","Floating Point Atomic","Albuquerque","","" "`P0053R7 `__","LWG","C++ Synchronized Buffered Ostream","Albuquerque","","" diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h --- a/libcxx/include/__memory/shared_ptr.h +++ b/libcxx/include/__memory/shared_ptr.h @@ -15,12 +15,15 @@ #include <__functional/binary_function.h> #include <__functional/operations.h> #include <__functional/reference_wrapper.h> +#include <__iterator/access.h> #include <__memory/addressof.h> #include <__memory/allocation_guard.h> #include <__memory/allocator.h> #include <__memory/allocator_traits.h> #include <__memory/compressed_pair.h> +#include <__memory/construct_at.h> #include <__memory/pointer_traits.h> +#include <__memory/uninitialized_algorithms.h> #include <__memory/unique_ptr.h> #include <__utility/forward.h> #include <__utility/move.h> @@ -963,6 +966,216 @@ return _VSTD::allocate_shared<_Tp>(allocator<_Tp>(), _VSTD::forward<_Args>(__args)...); } +#if _LIBCPP_STD_VER > 17 + +template +struct __sp_aligned_storage { + alignas(_Alignment) char __storage[_Alignment]; +}; + +template +struct __unbounded_array_control_block; + +template +struct __unbounded_array_control_block<_Tp[], _Alloc> : __shared_weak_count +{ + _LIBCPP_HIDE_FROM_ABI constexpr + _Tp* __get_data() noexcept { return __data_; } + + _LIBCPP_HIDE_FROM_ABI + explicit __unbounded_array_control_block(_Alloc const& __alloc, size_t __count, _Tp const& __arg) + : __alloc_(__alloc), __count_(__count) + { + std::__uninitialized_allocator_fill_n(__alloc_, std::begin(__data_), __count_, __arg); + } + + _LIBCPP_HIDE_FROM_ABI + explicit __unbounded_array_control_block(_Alloc const& __alloc, size_t __count) + : __alloc_(__alloc), __count_(__count) + { + std::__uninitialized_allocator_value_construct_n(__alloc_, std::begin(__data_), __count_); + } + + // Returns the number of bytes required to store a control block followed by the given number + // of elements of _Tp, with the whole storage being aligned to a multiple of _Tp's alignment. + _LIBCPP_HIDE_FROM_ABI + static constexpr size_t __bytes_for(size_t __elements) { + // When there's 0 elements, the control block alone is enough since it holds one element. + // Otherwise, we allocate one fewer element than requested because the control block already + // holds one. Also, we use the bitwise formula below to ensure that we allocate enough bytes + // for the whole allocation to be a multiple of _Tp's alignment. That formula is taken from [1]. + // + // [1]: https://en.wikipedia.org/wiki/Data_structure_alignment#Computing_padding + size_t __bytes = __elements == 0 ? sizeof(__unbounded_array_control_block) + : (__elements - 1) * sizeof(_Tp) + sizeof(__unbounded_array_control_block); + constexpr size_t __align = alignof(_Tp); + return (__bytes + __align - 1) & ~(__align - 1); + } + + _LIBCPP_HIDE_FROM_ABI + ~__unbounded_array_control_block() override { } // can't be `= default` because of the sometimes-non-trivial union member __data_ + +private: + void __on_zero_shared() _NOEXCEPT override { + __allocator_traits_rebind_t<_Alloc, _Tp> __value_alloc(__alloc_); + std::__allocator_destroy_multidimensional(__value_alloc, __data_, __data_ + __count_); + } + + void __on_zero_shared_weak() _NOEXCEPT override { + using _AlignedStorage = __sp_aligned_storage; + using _StorageAlloc = __allocator_traits_rebind_t<_Alloc, _AlignedStorage>; + using _PointerTraits = pointer_traits::pointer>; + + _StorageAlloc __tmp(__alloc_); + __alloc_.~_Alloc(); + size_t __size = __unbounded_array_control_block::__bytes_for(__count_); + _AlignedStorage* __storage = reinterpret_cast<_AlignedStorage*>(this); + allocator_traits<_StorageAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*__storage), __size); + } + + _LIBCPP_NO_UNIQUE_ADDRESS _Alloc __alloc_; + size_t __count_; + union { + _Tp __data_[1]; + }; +}; + +template +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Array> __allocate_shared_unbounded_array(const _Alloc& __a, size_t __n, _Arg&& ...__arg) +{ + static_assert(is_unbounded_array_v<_Array>); + // We compute the number of bytes necessary to hold the control block and the + // array elements. Then, we allocate an array of properly-aligned dummy structs + // large enough to hold the control block and array. This allows shifting the + // burden of aligning memory properly from us to the allocator. + using _ControlBlock = __unbounded_array_control_block<_Array, _Alloc>; + using _AlignedStorage = __sp_aligned_storage; + using _StorageAlloc = __allocator_traits_rebind_t<_Alloc, _AlignedStorage>; + __allocation_guard<_StorageAlloc> __guard(__a, _ControlBlock::__bytes_for(__n) / sizeof(_AlignedStorage)); + _ControlBlock* __control_block = reinterpret_cast<_ControlBlock*>(std::addressof(*__guard.__get())); + std::construct_at(__control_block, __a, __n, std::forward<_Arg>(__arg)...); + __guard.__release_ptr(); + return shared_ptr<_Array>::__create_with_control_block(__control_block->__get_data(), __control_block); +} + +template +struct __bounded_array_control_block; + +template +struct __bounded_array_control_block<_Tp[_Count], _Alloc> + : __shared_weak_count +{ + _LIBCPP_HIDE_FROM_ABI constexpr + _Tp* __get_data() noexcept { return __data_; } + + _LIBCPP_HIDE_FROM_ABI + explicit __bounded_array_control_block(_Alloc const& __alloc, _Tp const& __arg) : __alloc_(__alloc) { + std::__uninitialized_allocator_fill_n(__alloc_, std::addressof(__data_[0]), _Count, __arg); + } + + _LIBCPP_HIDE_FROM_ABI + explicit __bounded_array_control_block(_Alloc const& __alloc) : __alloc_(__alloc) { + std::__uninitialized_allocator_value_construct_n(__alloc_, std::addressof(__data_[0]), _Count); + } + + _LIBCPP_HIDE_FROM_ABI + ~__bounded_array_control_block() override { } // can't be `= default` because of the sometimes-non-trivial union member __data_ + +private: + void __on_zero_shared() _NOEXCEPT override { + __allocator_traits_rebind_t<_Alloc, _Tp> __value_alloc(__alloc_); + std::__allocator_destroy_multidimensional(__value_alloc, __data_, __data_ + _Count); + } + + void __on_zero_shared_weak() _NOEXCEPT override { + using _ControlBlockAlloc = __allocator_traits_rebind_t<_Alloc, __bounded_array_control_block>; + using _PointerTraits = pointer_traits::pointer>; + + _ControlBlockAlloc __tmp(__alloc_); + __alloc_.~_Alloc(); + allocator_traits<_ControlBlockAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*this), sizeof(*this)); + } + + _LIBCPP_NO_UNIQUE_ADDRESS _Alloc __alloc_; + union { + _Tp __data_[_Count]; + }; +}; + +template +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Array> __allocate_shared_bounded_array(const _Alloc& __a, _Arg&& ...__arg) +{ + static_assert(is_bounded_array_v<_Array>); + using _ControlBlock = __bounded_array_control_block<_Array, _Alloc>; + using _ControlBlockAlloc = __allocator_traits_rebind_t<_Alloc, _ControlBlock>; + + __allocation_guard<_ControlBlockAlloc> __guard(__a, 1); + _ControlBlock* __control_block = reinterpret_cast<_ControlBlock*>(std::addressof(*__guard.__get())); + std::construct_at(__control_block, __a, std::forward<_Arg>(__arg)...); + __guard.__release_ptr(); + return shared_ptr<_Array>::__create_with_control_block(__control_block->__get_data(), __control_block); +} + +template::value>> +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Tp> allocate_shared(const _Alloc& __a) +{ + return std::__allocate_shared_bounded_array<_Tp>(__a); +} + +template::value>> +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Tp> allocate_shared(const _Alloc& __a, const remove_extent_t<_Tp>& __u) +{ + return std::__allocate_shared_bounded_array<_Tp>(__a, __u); +} + +template::value>> +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Tp> allocate_shared(const _Alloc& __a, size_t __n) +{ + return std::__allocate_shared_unbounded_array<_Tp>(__a, __n); +} + +template::value>> +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Tp> allocate_shared(const _Alloc& __a, size_t __n, const remove_extent_t<_Tp>& __u) +{ + return std::__allocate_shared_unbounded_array<_Tp>(__a, __n, __u); +} + +template::value>> +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Tp> make_shared() +{ + return std::__allocate_shared_bounded_array<_Tp>(allocator<_Tp>()); +} + +template::value>> +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Tp> make_shared(const remove_extent_t<_Tp>& __u) +{ + return std::__allocate_shared_bounded_array<_Tp>(allocator<_Tp>(), __u); +} + +template::value>> +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Tp> make_shared(size_t __n) +{ + return std::__allocate_shared_unbounded_array<_Tp>(allocator<_Tp>(), __n); +} + +template::value>> +_LIBCPP_HIDE_FROM_ABI +shared_ptr<_Tp> make_shared(size_t __n, const remove_extent_t<_Tp>& __u) +{ + return std::__allocate_shared_unbounded_array<_Tp>(allocator<_Tp>(), __n, __u); +} + +#endif // _LIBCPP_STD_VER > 17 + template inline _LIBCPP_INLINE_VISIBILITY bool diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h --- a/libcxx/include/__memory/uninitialized_algorithms.h +++ b/libcxx/include/__memory/uninitialized_algorithms.h @@ -13,10 +13,13 @@ #include <__config> #include <__iterator/iterator_traits.h> #include <__memory/addressof.h> +#include <__memory/allocator_traits.h> #include <__memory/construct_at.h> #include <__memory/voidify.h> #include <__utility/move.h> #include <__utility/pair.h> +#include <__utility/transaction.h> +#include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -346,6 +349,153 @@ #endif // _LIBCPP_STD_VER > 14 +#if _LIBCPP_STD_VER > 17 + +// Destroys every element in the range [first, last) FROM RIGHT TO LEFT using allocator +// destruction. If elements are themselves C-style arrays, they are recursively destroyed +// in the same manner. +// +// This function assumes that destructors do not throw, and that the allocator is bound to +// the correct type. +template::value +>> +_LIBCPP_HIDE_FROM_ABI +constexpr void __allocator_destroy_multidimensional(_Alloc& __alloc, _BidirIter __first, _BidirIter __last) noexcept { + using _ValueType = typename iterator_traits<_BidirIter>::value_type; + static_assert(is_same_v::value_type, _ValueType>, + "The allocator should already be rebound to the correct type"); + + if (__first == __last) + return; + + if constexpr (is_array_v<_ValueType>) { + static_assert(!is_unbounded_array_v<_ValueType>, + "arrays of unbounded arrays don't exist, but if they did we would mess up here"); + + using _Element = remove_extent_t<_ValueType>; + __allocator_traits_rebind_t<_Alloc, _Element> __elem_alloc(__alloc); + do { + --__last; + decltype(auto) __array = *__last; + std::__allocator_destroy_multidimensional(__elem_alloc, __array, __array + extent_v<_ValueType>); + } while (__last != __first); + } else { + do { + --__last; + allocator_traits<_Alloc>::destroy(__alloc, std::addressof(*__last)); + } while (__last != __first); + } +} + +// Constructs the object at the given location using the allocator's construct method. +// +// If the object being constructed is an array, each element of the array is allocator-constructed, +// recursively. If an exception is thrown during the construction of an array, the initialized +// elements are destroyed in reverse order of initialization using allocator destruction. +// +// This function assumes that the allocator is bound to the correct type. +template +_LIBCPP_HIDE_FROM_ABI +constexpr void __allocator_construct_at(_Alloc& __alloc, _Tp* __loc) { + static_assert(is_same_v::value_type, _Tp>, + "The allocator should already be rebound to the correct type"); + + if constexpr (is_array_v<_Tp>) { + using _Element = remove_extent_t<_Tp>; + __allocator_traits_rebind_t<_Alloc, _Element> __elem_alloc(__alloc); + size_t __i = 0; + _Tp& __array = *__loc; + + // If an exception is thrown, destroy what we have constructed so far in reverse order. + __transaction __guard([&]() { std::__allocator_destroy_multidimensional(__elem_alloc, __array, __array + __i); }); + for (; __i != extent_v<_Tp>; ++__i) { + std::__allocator_construct_at(__elem_alloc, std::addressof(__array[__i])); + } + __guard.__complete(); + } else { + allocator_traits<_Alloc>::construct(__alloc, __loc); + } +} + +// Constructs the object at the given location using the allocator's construct method, passing along +// the provided argument. +// +// If the object being constructed is an array, the argument is also assumed to be an array. Each +// each element of the array being constructed is allocator-constructed from the corresponding +// element of the argument array. If an exception is thrown during the construction of an array, +// the initialized elements are destroyed in reverse order of initialization using allocator +// destruction. +// +// This function assumes that the allocator is bound to the correct type. +template +_LIBCPP_HIDE_FROM_ABI +constexpr void __allocator_construct_at(_Alloc& __alloc, _Tp* __loc, _Arg const& __arg) { + static_assert(is_same_v::value_type, _Tp>, + "The allocator should already be rebound to the correct type"); + + if constexpr (is_array_v<_Tp>) { + static_assert(is_array_v<_Arg>, + "Provided non-array initialization argument to __allocator_construct_at when " + "trying to construct an array."); + + using _Element = remove_extent_t<_Tp>; + __allocator_traits_rebind_t<_Alloc, _Element> __elem_alloc(__alloc); + size_t __i = 0; + _Tp& __array = *__loc; + + // If an exception is thrown, destroy what we have constructed so far in reverse order. + __transaction __guard([&]() { std::__allocator_destroy_multidimensional(__elem_alloc, __array, __array + __i); }); + for (; __i != extent_v<_Tp>; ++__i) { + std::__allocator_construct_at(__elem_alloc, std::addressof(__array[__i]), __arg[__i]); + } + __guard.__complete(); + } else { + allocator_traits<_Alloc>::construct(__alloc, __loc, __arg); + } +} + +// Given a range starting at it and containing n elements, initializes each element in the +// range from left to right using the construct method of the allocator (rebound to the +// correct type). +// +// If an exception is thrown, the initialized elements are destroyed in reverse order of +// initialization using allocator_traits destruction. If the elements in the range are C-style +// arrays, they are initialized element-wise using allocator construction, and recursively so. +template::difference_type> +_LIBCPP_HIDE_FROM_ABI +constexpr void __uninitialized_allocator_fill_n(_Alloc& __alloc, _BidirIter __it, _Size __n, _Tp const& __value) { + using _ValueType = typename iterator_traits<_BidirIter>::value_type; + __allocator_traits_rebind_t<_Alloc, _ValueType> __value_alloc(__alloc); + _BidirIter __begin = __it; + + // If an exception is thrown, destroy what we have constructed so far in reverse order. + __transaction __guard([&]() { std::__allocator_destroy_multidimensional(__value_alloc, __begin, __it); }); + for (; __n != 0; --__n, ++__it) { + std::__allocator_construct_at(__value_alloc, std::addressof(*__it), __value); + } + __guard.__complete(); +} + +// Same as __uninitialized_allocator_fill_n, but doesn't pass any initialization argument +// to the allocator's construct method, which results in value initialization. +template::difference_type> +_LIBCPP_HIDE_FROM_ABI +constexpr void __uninitialized_allocator_value_construct_n(_Alloc& __alloc, _BidirIter __it, _Size __n) { + using _ValueType = typename iterator_traits<_BidirIter>::value_type; + __allocator_traits_rebind_t<_Alloc, _ValueType> __value_alloc(__alloc); + _BidirIter __begin = __it; + + // If an exception is thrown, destroy what we have constructed so far in reverse order. + __transaction __guard([&]() { std::__allocator_destroy_multidimensional(__value_alloc, __begin, __it); }); + for (; __n != 0; --__n, ++__it) { + std::__allocator_construct_at(__value_alloc, std::addressof(*__it)); + } + __guard.__complete(); +} + +#endif // _LIBCPP_STD_VER > 17 + _LIBCPP_END_NAMESPACE_STD #endif // _LIBCPP___MEMORY_UNINITIALIZED_ALGORITHMS_H diff --git a/libcxx/include/memory b/libcxx/include/memory --- a/libcxx/include/memory +++ b/libcxx/include/memory @@ -661,9 +661,29 @@ template D* get_deleter(shared_ptr const& p) noexcept; template - shared_ptr make_shared(Args&&... args); + shared_ptr make_shared(Args&&... args); // T is not an array template - shared_ptr allocate_shared(const A& a, Args&&... args); + shared_ptr allocate_shared(const A& a, Args&&... args); // T is not an array + +template + shared_ptr make_shared(size_t N); // T is U[] (since C++20) +template + shared_ptr allocate_shared(const A& a, size_t N); // T is U[] (since C++20) + +template + shared_ptr make_shared(); // T is U[N] (since C++20) +template + shared_ptr allocate_shared(const A& a); // T is U[N] (since C++20) + +template + shared_ptr make_shared(size_t N, const remove_extent_t& u); // T is U[] (since C++20) +template + shared_ptr allocate_shared(const A& a, size_t N, const remove_extent_t& u); // T is U[] (since C++20) + +template shared_ptr + make_shared(const remove_extent_t& u); // T is U[N] (since C++20) +template + shared_ptr allocate_shared(const A& a, const remove_extent_t& u); // T is U[N] (since C++20) template class weak_ptr diff --git a/libcxx/include/version b/libcxx/include/version --- a/libcxx/include/version +++ b/libcxx/include/version @@ -154,7 +154,8 @@ __cpp_lib_scoped_lock 201703L __cpp_lib_semaphore 201907L __cpp_lib_shared_mutex 201505L -__cpp_lib_shared_ptr_arrays 201611L +__cpp_lib_shared_ptr_arrays 201707L + 201611L // C++17 __cpp_lib_shared_ptr_weak_type 201606L __cpp_lib_shared_timed_mutex 201402L __cpp_lib_shift 201806L @@ -353,6 +354,8 @@ # if !defined(_LIBCPP_HAS_NO_THREADS) && !defined(_LIBCPP_AVAILABILITY_DISABLE_FTM___cpp_lib_semaphore) # define __cpp_lib_semaphore 201907L # endif +# undef __cpp_lib_shared_ptr_arrays +# define __cpp_lib_shared_ptr_arrays 201707L # define __cpp_lib_shift 201806L // # define __cpp_lib_smart_ptr_for_overwrite 202002L // # define __cpp_lib_source_location 201907L diff --git a/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.zero_size.compile.fail.cpp b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.zero_size.compile.fail.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.zero_size.compile.fail.cpp @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// Make sure that std::allocate_shared(...) fails at compile-time. +// While Clang and GCC appear to support T[0] as a language extension, that support is +// unreliable (for example T[0] doesn't match a T[N] partial specialization on Clang as +// of writing this). So instead, we make sure that this doesn't work at all with our +// implementation. + +#include + +void f() { + auto p = std::allocate_shared(std::allocator()); + (void)p; +} diff --git a/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.zero_size.compile.fail.cpp b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.zero_size.compile.fail.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.zero_size.compile.fail.cpp @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// Make sure that std::make_shared(...) fails at compile-time. +// While Clang and GCC appear to support T[0] as a language extension, that support is +// unreliable (for example T[0] doesn't match a T[N] partial specialization on Clang as +// of writing this). So instead, we make sure that this doesn't work at all with our +// implementation. + +#include + +void f() { + auto p = std::make_shared(); + (void)p; +} diff --git a/libcxx/test/std/containers/sequences/array/size_and_alignment.pass.cpp b/libcxx/test/std/containers/sequences/array/size_and_alignment.pass.cpp --- a/libcxx/test/std/containers/sequences/array/size_and_alignment.pass.cpp +++ b/libcxx/test/std/containers/sequences/array/size_and_alignment.pass.cpp @@ -13,11 +13,6 @@ // Test the size and alignment matches that of an array of a given type. -// Ignore error about requesting a large alignment not being ABI compatible with older AIX systems. -#if defined(_AIX) -# pragma clang diagnostic ignored "-Waix-compat" -#endif - #include #include #include @@ -25,6 +20,9 @@ #include "test_macros.h" +// Ignore error about requesting a large alignment not being ABI compatible with older AIX systems. +TEST_CLANG_DIAGNOSTIC_IGNORED("-Waix-compat") + template struct MyArray { T elems[Size]; diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.pass.cpp --- a/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.pass.cpp @@ -29,6 +29,7 @@ __cpp_lib_ranges 201811L [C++20] __cpp_lib_raw_memory_algorithms 201606L [C++17] __cpp_lib_shared_ptr_arrays 201611L [C++17] + 201707L [C++20] __cpp_lib_shared_ptr_weak_type 201606L [C++17] __cpp_lib_smart_ptr_for_overwrite 202002L [C++20] __cpp_lib_to_address 201711L [C++20] @@ -374,8 +375,8 @@ # ifndef __cpp_lib_shared_ptr_arrays # error "__cpp_lib_shared_ptr_arrays should be defined in c++20" # endif -# if __cpp_lib_shared_ptr_arrays != 201611L -# error "__cpp_lib_shared_ptr_arrays should have the value 201611L in c++20" +# if __cpp_lib_shared_ptr_arrays != 201707L +# error "__cpp_lib_shared_ptr_arrays should have the value 201707L in c++20" # endif # ifndef __cpp_lib_shared_ptr_weak_type @@ -525,8 +526,8 @@ # ifndef __cpp_lib_shared_ptr_arrays # error "__cpp_lib_shared_ptr_arrays should be defined in c++2b" # endif -# if __cpp_lib_shared_ptr_arrays != 201611L -# error "__cpp_lib_shared_ptr_arrays should have the value 201611L in c++2b" +# if __cpp_lib_shared_ptr_arrays != 201707L +# error "__cpp_lib_shared_ptr_arrays should have the value 201707L in c++2b" # endif # ifndef __cpp_lib_shared_ptr_weak_type diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp --- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp @@ -141,6 +141,7 @@ __cpp_lib_semaphore 201907L [C++20] __cpp_lib_shared_mutex 201505L [C++17] __cpp_lib_shared_ptr_arrays 201611L [C++17] + 201707L [C++20] __cpp_lib_shared_ptr_weak_type 201606L [C++17] __cpp_lib_shared_timed_mutex 201402L [C++14] __cpp_lib_shift 201806L [C++20] @@ -3347,8 +3348,8 @@ # ifndef __cpp_lib_shared_ptr_arrays # error "__cpp_lib_shared_ptr_arrays should be defined in c++20" # endif -# if __cpp_lib_shared_ptr_arrays != 201611L -# error "__cpp_lib_shared_ptr_arrays should have the value 201611L in c++20" +# if __cpp_lib_shared_ptr_arrays != 201707L +# error "__cpp_lib_shared_ptr_arrays should have the value 201707L in c++20" # endif # ifndef __cpp_lib_shared_ptr_weak_type @@ -4720,8 +4721,8 @@ # ifndef __cpp_lib_shared_ptr_arrays # error "__cpp_lib_shared_ptr_arrays should be defined in c++2b" # endif -# if __cpp_lib_shared_ptr_arrays != 201611L -# error "__cpp_lib_shared_ptr_arrays should have the value 201611L in c++2b" +# if __cpp_lib_shared_ptr_arrays != 201707L +# error "__cpp_lib_shared_ptr_arrays should have the value 201707L in c++2b" # endif # ifndef __cpp_lib_shared_ptr_weak_type diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.bounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.bounded.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.bounded.pass.cpp @@ -0,0 +1,418 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// Aligned deallocation isn't provided before macOS 10.14, and some tests for overaligned types +// below require that feature. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13}} + +// + +// shared_ptr + +// template +// shared_ptr allocate_shared(const A& a); // T is U[N] +// +// template +// shared_ptr allocate_shared(const A& a, const remove_extent_t& u); // T is U[N] + +#include +#include +#include // std::uintptr_t +#include +#include + +#include "min_allocator.h" +#include "operator_hijacker.h" +#include "types.h" + +template +concept CanAllocateShared = requires(Args&& ...args) { + { std::allocate_shared(std::forward(args)...) } -> std::same_as>; +}; + +int main(int, char**) { + // Make sure we initialize elements correctly + { + // Without passing an initial value + { + using Array = int[8]; + std::shared_ptr ptr = std::allocate_shared(std::allocator()); + for (unsigned i = 0; i < 8; ++i) { + assert(ptr[i] == 0); + } + } + { + using Array = int[8][3]; + std::shared_ptr ptr = std::allocate_shared(std::allocator()); + 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 ptr = std::allocate_shared(std::allocator()); + 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 ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(std::allocator()); + assert(DestroyInReverseOrder::alive() == 8); + } + assert(DestroyInReverseOrder::alive() == 0); + } + { + using Array = DestroyInReverseOrder[8][3]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::allocate_shared(std::allocator()); + assert(DestroyInReverseOrder::alive() == 8 * 3); + } + assert(DestroyInReverseOrder::alive() == 0); + } + { + using Array = DestroyInReverseOrder[8][3][2]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::allocate_shared(std::allocator()); + 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 ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(std::allocator()); + assert(CountCopies::copies() == 0); + } + { + using Array = CountCopies[8][3]; + CountCopies::reset(); + std::shared_ptr ptr = std::allocate_shared(std::allocator());; + assert(CountCopies::copies() == 0); + } + { + using Array = CountCopies[8][3][2]; + CountCopies::reset(); + std::shared_ptr ptr = std::allocate_shared(std::allocator());; + assert(CountCopies::copies() == 0); + } + + // Passing an initial value + { + using Array = CountCopies[8]; + int copies = 0; + CountCopies init(&copies); + std::shared_ptr ptr = std::allocate_shared(std::allocator(), init); + assert(copies == 8); + } + { + using Array = CountCopies[8][3]; + int copies = 0; + CountCopies init[3] = {&copies, &copies, &copies}; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(std::allocator(), 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 = [] { + { + using Array = T[8]; + std::shared_ptr ptr = std::allocate_shared(std::allocator()); + for (int i = 0; i < 8; ++i) { + T* p = std::addressof(ptr[i]); + assert(reinterpret_cast(p) % alignof(T) == 0); + } + } + { + using Array = T[8][3]; + std::shared_ptr ptr = std::allocate_shared(std::allocator()); + for (int i = 0; i < 8; ++i) { + for (int j = 0; j < 3; ++j) { + T* p = std::addressof(ptr[i][j]); + assert(reinterpret_cast(p) % alignof(T) == 0); + } + } + } + { + using Array = T[8][3][2]; + std::shared_ptr ptr = std::allocate_shared(std::allocator()); + 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(p) % alignof(T) == 0); + } + } + } + } + }; + + struct Empty { }; + check_alignment.operator()(); + check_alignment.operator()(); + check_alignment.operator()(); + + // test non corner cases as well while we're at it + struct Foo { int i; char c; }; + check_alignment.operator()(); + check_alignment.operator()(); + } + + // 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 ptr = std::allocate_shared(std::allocator()); + 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 ptr = std::allocate_shared(std::allocator()); + 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 ptr = std::allocate_shared(std::allocator()); + 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 ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(std::allocator(), 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 ptr = std::allocate_shared(min_allocator()); + 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 ptr = std::allocate_shared(min_allocator(), 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 ptr = std::allocate_shared(std::allocator()); + (void)ptr; + } + + // Make sure std::allocate_shared handles badly-behaved types properly + { + using Array = operator_hijacker[3]; + std::shared_ptr p1 = std::allocate_shared(std::allocator()); + std::shared_ptr p2 = std::allocate_shared(std::allocator(), operator_hijacker()); + assert(p1 != nullptr); + assert(p2 != nullptr); + } + + // Check that we SFINAE-away for invalid arguments + { + struct T { }; + static_assert( CanAllocateShared>); + static_assert( CanAllocateShared, T>); + static_assert(!CanAllocateShared, T, int>); // too many arguments + static_assert(!CanAllocateShared, int>); // T is not constructible from int + } + + return 0; +} diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp @@ -0,0 +1,458 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// Aligned deallocation isn't provided before macOS 10.14, and some tests for overaligned types +// below require that feature. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13}} + +// + +// shared_ptr + +// template +// shared_ptr allocate_shared(const A& a, size_t N); // T is U[] +// +// template +// shared_ptr allocate_shared(const A& a, size_t N, const remove_extent_t& u); // T is U[] + +#include +#include +#include // std::uintptr_t +#include +#include + +#include "min_allocator.h" +#include "operator_hijacker.h" +#include "test_macros.h" +#include "types.h" + +// Ignore error about requesting a large alignment not being ABI compatible with older AIX systems. +TEST_CLANG_DIAGNOSTIC_IGNORED("-Waix-compat") + +template +concept CanAllocateShared = requires(Args&& ...args) { + { std::allocate_shared(std::forward(args)...) } -> std::same_as>; +}; + +int main(int, char**) { + // Check behavior for a zero-sized array + { + // Without passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 0); + assert(ptr != nullptr); + } + + // Passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 0, 42); + assert(ptr != nullptr); + } + } + + // Check behavior for a 1-sized array + { + // Without passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 1); + assert(ptr != nullptr); + assert(ptr[0] == 0); + } + + // Passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 1, 42); + assert(ptr != nullptr); + assert(ptr[0] == 42); + } + } + + // Make sure we initialize elements correctly + { + // Without passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + for (unsigned i = 0; i < 8; ++i) { + assert(ptr[i] == 0); + } + } + { + using Array = int[][3]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + 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[][3][2]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + 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[]; + int init = 42; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, init); + for (unsigned i = 0; i < 8; ++i) { + assert(ptr[i] == init); + } + } + { + using Array = int[][3]; + int init[3] = {42, 43, 44}; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, 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[][3][2]; + int init[3][2] = {{31, 32}, {41, 42}, {51, 52}}; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, 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[]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + assert(DestroyInReverseOrder::alive() == 8); + } + assert(DestroyInReverseOrder::alive() == 0); + } + { + using Array = DestroyInReverseOrder[][3]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + assert(DestroyInReverseOrder::alive() == 8 * 3); + } + assert(DestroyInReverseOrder::alive() == 0); + } + { + using Array = DestroyInReverseOrder[][3][2]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + assert(DestroyInReverseOrder::alive() == 8 * 3 * 2); + } + assert(DestroyInReverseOrder::alive() == 0); + } + + // Passing an initial value + { + using Array = DestroyInReverseOrder[]; + int count = 0; + DestroyInReverseOrder init(&count); + int init_count = 1; + { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, init); + assert(count == 8 + init_count); + } + assert(count == init_count); + } + { + using Array = DestroyInReverseOrder[][3]; + int count = 0; + DestroyInReverseOrder init[3] = {&count, &count, &count}; + int init_count = 3; + { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, init); + assert(count == 8 * 3 + init_count); + } + assert(count == init_count); + } + { + using Array = DestroyInReverseOrder[][3][2]; + int count = 0; + DestroyInReverseOrder init[3][2] = {{&count, &count}, {&count, &count}, {&count, &count}}; + int init_count = 3 * 2; + { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, 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[]; + CountCopies::reset(); + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + assert(CountCopies::copies() == 0); + } + { + using Array = CountCopies[][3]; + CountCopies::reset(); + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + assert(CountCopies::copies() == 0); + } + { + using Array = CountCopies[][3][2]; + CountCopies::reset(); + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + assert(CountCopies::copies() == 0); + } + + // Passing an initial value + { + using Array = CountCopies[]; + int copies = 0; + CountCopies init(&copies); + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, init); + assert(copies == 8); + } + { + using Array = CountCopies[][3]; + int copies = 0; + CountCopies init[3] = {&copies, &copies, &copies}; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, init); + assert(copies == 8 * 3); + } + { + using Array = CountCopies[][3][2]; + int copies = 0; + CountCopies init[3][2] = {{&copies, &copies}, {&copies, &copies}, {&copies, &copies}}; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, 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 = [] { + { + using Array = T[]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + for (int i = 0; i < 8; ++i) { + T* p = std::addressof(ptr[i]); + assert(reinterpret_cast(p) % alignof(T) == 0); + } + } + { + using Array = T[][3]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + for (int i = 0; i < 8; ++i) { + for (int j = 0; j < 3; ++j) { + T* p = std::addressof(ptr[i][j]); + assert(reinterpret_cast(p) % alignof(T) == 0); + } + } + } + { + using Array = T[][3][2]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + 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(p) % alignof(T) == 0); + } + } + } + } + }; + + struct Empty { }; + check_alignment.operator()(); + check_alignment.operator()(); + check_alignment.operator()(); + + // test non corner cases as well while we're at it + struct Foo { int i; char c; }; + check_alignment.operator()(); + check_alignment.operator()(); + } + + // 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[]; + for (int i = 0; i < 8; ++i) { + ThrowOnConstruction::throw_after(i); + DestroyInReverseOrder::reset(); + try { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 0); + } + } + } + { + using Array = Sentinel[][3]; + for (int i = 0; i < 8 * 3; ++i) { + ThrowOnConstruction::throw_after(i); + DestroyInReverseOrder::reset(); + try { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 0); + } + } + } + { + using Array = Sentinel[][3][2]; + for (int i = 0; i < 8 * 3 * 2; ++i) { + ThrowOnConstruction::throw_after(i); + DestroyInReverseOrder::reset(); + try { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 0); + } + } + } + + // Passing an initial value + { + using Array = Sentinel[]; + for (int i = 0; i < 8; ++i) { + DestroyInReverseOrder::reset(); + ThrowOnConstruction::reset(); + Sentinel init; + ThrowOnConstruction::throw_after(i); + try { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, init); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 1); + } + } + } + { + using Array = Sentinel[][3]; + for (int i = 0; i < 8 * 3; ++i) { + DestroyInReverseOrder::reset(); + ThrowOnConstruction::reset(); + Sentinel init[3] = {}; + ThrowOnConstruction::throw_after(i); + try { + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8, init); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 3); + } + } + } + { + using Array = Sentinel[][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 ptr = std::allocate_shared(std::allocator(), 8, 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[][3]; + std::shared_ptr ptr = std::allocate_shared(min_allocator(), 8); + 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[][3]; + int init[3] = {42, 43, 44}; + std::shared_ptr ptr = std::allocate_shared(min_allocator(), 8, 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[][3]; + std::shared_ptr ptr = std::allocate_shared(std::allocator(), 8); + (void)ptr; + } + + // Make sure std::allocate_shared handles badly-behaved types properly + { + using Array = operator_hijacker[]; + std::shared_ptr p1 = std::allocate_shared(std::allocator(), 3); + std::shared_ptr p2 = std::allocate_shared(std::allocator(), 3, operator_hijacker()); + assert(p1 != nullptr); + assert(p2 != nullptr); + } + + // Check that we SFINAE-away for invalid arguments + { + struct T { }; + static_assert( CanAllocateShared, std::size_t>); + static_assert( CanAllocateShared, std::size_t, T>); + static_assert(!CanAllocateShared, std::size_t, T, int>); // too many arguments + static_assert(!CanAllocateShared, std::size_t, int>); // T not constructible from int + } + + return 0; +} diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp --- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp @@ -11,15 +11,17 @@ // shared_ptr // template -// shared_ptr allocate_shared(const A& a, Args&&... args); +// shared_ptr allocate_shared(const A& a, Args&&... args); // T is not an array #include #include #include #include -#include "test_macros.h" -#include "test_allocator.h" + #include "min_allocator.h" +#include "operator_hijacker.h" +#include "test_allocator.h" +#include "test_macros.h" int new_count = 0; @@ -172,6 +174,14 @@ } assert(A::count == 0); + // Make sure std::allocate_shared handles badly-behaved types properly + { + std::shared_ptr p1 = std::allocate_shared(min_allocator()); + std::shared_ptr p2 = std::allocate_shared(min_allocator(), operator_hijacker()); + assert(p1 != nullptr); + assert(p2 != nullptr); + } + // Test that we don't call construct before C++20. #if TEST_STD_VER < 20 { diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp --- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp @@ -13,7 +13,7 @@ // shared_ptr // template -// shared_ptr allocate_shared(const A& a, Args&&... args); +// shared_ptr allocate_shared(const A& a, Args&&... args); // This test checks that allocator_traits::construct is used in allocate_shared // as requested in C++20 (via P0674R1). diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.bounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.bounded.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.bounded.pass.cpp @@ -0,0 +1,390 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// Aligned deallocation isn't provided before macOS 10.14, and some tests for overaligned types +// below require that feature. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13}} + +// + +// shared_ptr + +// template +// shared_ptr make_shared(); // T is U[N] +// +// template +// shared_ptr make_shared(const remove_extent_t& u); // T is U[N] + +#include +#include +#include // std::uintptr_t +#include +#include + +#include "operator_hijacker.h" +#include "types.h" + +template +concept CanMakeShared = requires(Args&& ...args) { + { std::make_shared(std::forward(args)...) } -> std::same_as>; +}; + +int main(int, char**) { + // Make sure we initialize elements correctly + { + // Without passing an initial value + { + using Array = int[8]; + std::shared_ptr ptr = std::make_shared(); + for (unsigned i = 0; i < 8; ++i) { + assert(ptr[i] == 0); + } + } + { + using Array = int[8][3]; + std::shared_ptr ptr = std::make_shared(); + 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 ptr = std::make_shared(); + 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 ptr = std::make_shared(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 ptr = std::make_shared(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 ptr = std::make_shared(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 ptr = std::make_shared(); + assert(DestroyInReverseOrder::alive() == 8); + } + assert(DestroyInReverseOrder::alive() == 0); + } + { + using Array = DestroyInReverseOrder[8][3]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::make_shared(); + assert(DestroyInReverseOrder::alive() == 8 * 3); + } + assert(DestroyInReverseOrder::alive() == 0); + } + { + using Array = DestroyInReverseOrder[8][3][2]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::make_shared(); + 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 ptr = std::make_shared(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 ptr = std::make_shared(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 ptr = std::make_shared(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 ptr = std::make_shared(); + assert(CountCopies::copies() == 0); + } + { + using Array = CountCopies[8][3]; + CountCopies::reset(); + std::shared_ptr ptr = std::make_shared(); + assert(CountCopies::copies() == 0); + } + { + using Array = CountCopies[8][3][2]; + CountCopies::reset(); + std::shared_ptr ptr = std::make_shared(); + assert(CountCopies::copies() == 0); + } + + // Passing an initial value + { + using Array = CountCopies[8]; + int copies = 0; + CountCopies init(&copies); + std::shared_ptr ptr = std::make_shared(init); + assert(copies == 8); + } + { + using Array = CountCopies[8][3]; + int copies = 0; + CountCopies init[3] = {&copies, &copies, &copies}; + std::shared_ptr ptr = std::make_shared(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 ptr = std::make_shared(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 = [] { + { + using Array = T[8]; + std::shared_ptr ptr = std::make_shared(); + for (int i = 0; i < 8; ++i) { + T* p = std::addressof(ptr[i]); + assert(reinterpret_cast(p) % alignof(T) == 0); + } + } + { + using Array = T[8][3]; + std::shared_ptr ptr = std::make_shared(); + for (int i = 0; i < 8; ++i) { + for (int j = 0; j < 3; ++j) { + T* p = std::addressof(ptr[i][j]); + assert(reinterpret_cast(p) % alignof(T) == 0); + } + } + } + { + using Array = T[8][3][2]; + std::shared_ptr ptr = std::make_shared(); + 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(p) % alignof(T) == 0); + } + } + } + } + }; + + struct Empty { }; + check_alignment.operator()(); + check_alignment.operator()(); + check_alignment.operator()(); + + // test non corner cases as well while we're at it + struct Foo { int i; char c; }; + check_alignment.operator()(); + check_alignment.operator()(); + } + + // 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 ptr = std::make_shared(); + 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 ptr = std::make_shared(); + 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 ptr = std::make_shared(); + 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 ptr = std::make_shared(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 ptr = std::make_shared(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 ptr = std::make_shared(init); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 3 * 2); + } + } + } + } +#endif // TEST_HAS_NO_EXCEPTIONS + + // Make sure the version without an initialization argument works even for non-movable types + { + using Array = NonMovable[8][3]; + std::shared_ptr ptr = std::make_shared(); + (void)ptr; + } + + // Make sure std::make_shared handles badly-behaved types properly + { + using Array = operator_hijacker[3]; + std::shared_ptr p1 = std::make_shared(); + std::shared_ptr p2 = std::make_shared(operator_hijacker()); + assert(p1 != nullptr); + assert(p2 != nullptr); + } + + // Check that we SFINAE-away for invalid arguments + { + struct T { }; + static_assert( CanMakeShared); + static_assert( CanMakeShared); + static_assert(!CanMakeShared); // too many arguments + } + + return 0; +} diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp @@ -0,0 +1,429 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// Aligned deallocation isn't provided before macOS 10.14, and some tests for overaligned types +// below require that feature. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13}} + +// + +// shared_ptr + +// template shared_ptr make_shared(size_t N); // T is U[] +// +// template +// shared_ptr make_shared(size_t N, const remove_extent_t& u); // T is U[] + +#include +#include +#include // std::uintptr_t +#include +#include + +#include "operator_hijacker.h" +#include "test_macros.h" +#include "types.h" + +// Ignore error about requesting a large alignment not being ABI compatible with older AIX systems. +TEST_CLANG_DIAGNOSTIC_IGNORED("-Waix-compat") + +template +concept CanMakeShared = requires(Args&& ...args) { + { std::make_shared(std::forward(args)...) } -> std::same_as>; +}; + +int main(int, char**) { + // Check behavior for a zero-sized array + { + // Without passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::make_shared(0); + assert(ptr != nullptr); + } + + // Passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::make_shared(0, 42); + assert(ptr != nullptr); + } + } + + // Check behavior for a 1-sized array + { + // Without passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::make_shared(1); + assert(ptr != nullptr); + assert(ptr[0] == 0); + } + + // Passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::make_shared(1, 42); + assert(ptr != nullptr); + assert(ptr[0] == 42); + } + } + + // Make sure we initialize elements correctly + { + // Without passing an initial value + { + using Array = int[]; + std::shared_ptr ptr = std::make_shared(8); + for (unsigned i = 0; i < 8; ++i) { + assert(ptr[i] == 0); + } + } + { + using Array = int[][3]; + std::shared_ptr ptr = std::make_shared(8); + 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[][3][2]; + std::shared_ptr ptr = std::make_shared(8); + 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[]; + int init = 42; + std::shared_ptr ptr = std::make_shared(8, init); + for (unsigned i = 0; i < 8; ++i) { + assert(ptr[i] == init); + } + } + { + using Array = int[][3]; + int init[3] = {42, 43, 44}; + std::shared_ptr ptr = std::make_shared(8, 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[][3][2]; + int init[3][2] = {{31, 32}, {41, 42}, {51, 52}}; + std::shared_ptr ptr = std::make_shared(8, 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[]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::make_shared(8); + assert(DestroyInReverseOrder::alive() == 8); + } + assert(DestroyInReverseOrder::alive() == 0); + } + { + using Array = DestroyInReverseOrder[][3]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::make_shared(8); + assert(DestroyInReverseOrder::alive() == 8 * 3); + } + assert(DestroyInReverseOrder::alive() == 0); + } + { + using Array = DestroyInReverseOrder[][3][2]; + DestroyInReverseOrder::reset(); + { + std::shared_ptr ptr = std::make_shared(8); + assert(DestroyInReverseOrder::alive() == 8 * 3 * 2); + } + assert(DestroyInReverseOrder::alive() == 0); + } + + // Passing an initial value + { + using Array = DestroyInReverseOrder[]; + int count = 0; + DestroyInReverseOrder init(&count); + int init_count = 1; + { + std::shared_ptr ptr = std::make_shared(8, init); + assert(count == 8 + init_count); + } + assert(count == init_count); + } + { + using Array = DestroyInReverseOrder[][3]; + int count = 0; + DestroyInReverseOrder init[3] = {&count, &count, &count}; + int init_count = 3; + { + std::shared_ptr ptr = std::make_shared(8, init); + assert(count == 8 * 3 + init_count); + } + assert(count == init_count); + } + { + using Array = DestroyInReverseOrder[][3][2]; + int count = 0; + DestroyInReverseOrder init[3][2] = {{&count, &count}, {&count, &count}, {&count, &count}}; + int init_count = 3 * 2; + { + std::shared_ptr ptr = std::make_shared(8, 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[]; + CountCopies::reset(); + std::shared_ptr ptr = std::make_shared(8); + assert(CountCopies::copies() == 0); + } + { + using Array = CountCopies[][3]; + CountCopies::reset(); + std::shared_ptr ptr = std::make_shared(8); + assert(CountCopies::copies() == 0); + } + { + using Array = CountCopies[][3][2]; + CountCopies::reset(); + std::shared_ptr ptr = std::make_shared(8); + assert(CountCopies::copies() == 0); + } + + // Passing an initial value + { + using Array = CountCopies[]; + int copies = 0; + CountCopies init(&copies); + std::shared_ptr ptr = std::make_shared(8, init); + assert(copies == 8); + } + { + using Array = CountCopies[][3]; + int copies = 0; + CountCopies init[3] = {&copies, &copies, &copies}; + std::shared_ptr ptr = std::make_shared(8, init); + assert(copies == 8 * 3); + } + { + using Array = CountCopies[][3][2]; + int copies = 0; + CountCopies init[3][2] = {{&copies, &copies}, {&copies, &copies}, {&copies, &copies}}; + std::shared_ptr ptr = std::make_shared(8, 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 = [] { + { + using Array = T[]; + std::shared_ptr ptr = std::make_shared(8); + for (int i = 0; i < 8; ++i) { + T* p = std::addressof(ptr[i]); + assert(reinterpret_cast(p) % alignof(T) == 0); + } + } + { + using Array = T[][3]; + std::shared_ptr ptr = std::make_shared(8); + for (int i = 0; i < 8; ++i) { + for (int j = 0; j < 3; ++j) { + T* p = std::addressof(ptr[i][j]); + assert(reinterpret_cast(p) % alignof(T) == 0); + } + } + } + { + using Array = T[][3][2]; + std::shared_ptr ptr = std::make_shared(8); + 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(p) % alignof(T) == 0); + } + } + } + } + }; + + struct Empty { }; + check_alignment.operator()(); + check_alignment.operator()(); + check_alignment.operator()(); + + // test non corner cases as well while we're at it + struct Foo { int i; char c; }; + check_alignment.operator()(); + check_alignment.operator()(); + } + + // 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[]; + for (int i = 0; i < 8; ++i) { + ThrowOnConstruction::throw_after(i); + DestroyInReverseOrder::reset(); + try { + std::shared_ptr ptr = std::make_shared(8); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 0); + } + } + } + { + using Array = Sentinel[][3]; + for (int i = 0; i < 8 * 3; ++i) { + ThrowOnConstruction::throw_after(i); + DestroyInReverseOrder::reset(); + try { + std::shared_ptr ptr = std::make_shared(8); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 0); + } + } + } + { + using Array = Sentinel[][3][2]; + for (int i = 0; i < 8 * 3 * 2; ++i) { + ThrowOnConstruction::throw_after(i); + DestroyInReverseOrder::reset(); + try { + std::shared_ptr ptr = std::make_shared(8); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 0); + } + } + } + + // Passing an initial value + { + using Array = Sentinel[]; + for (int i = 0; i < 8; ++i) { + DestroyInReverseOrder::reset(); + ThrowOnConstruction::reset(); + Sentinel init; + ThrowOnConstruction::throw_after(i); + try { + std::shared_ptr ptr = std::make_shared(8, init); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 1); + } + } + } + { + using Array = Sentinel[][3]; + for (int i = 0; i < 8 * 3; ++i) { + DestroyInReverseOrder::reset(); + ThrowOnConstruction::reset(); + Sentinel init[3] = {}; + ThrowOnConstruction::throw_after(i); + try { + std::shared_ptr ptr = std::make_shared(8, init); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 3); + } + } + } + { + using Array = Sentinel[][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 ptr = std::make_shared(8, init); + assert(false); + } catch (ThrowOnConstruction::exception const&) { + assert(DestroyInReverseOrder::alive() == 3 * 2); + } + } + } + } +#endif // TEST_HAS_NO_EXCEPTIONS + + // Make sure the version without an initialization argument works even for non-movable types + { + using Array = NonMovable[][3]; + std::shared_ptr ptr = std::make_shared(8); + (void)ptr; + } + + // Make sure std::make_shared handles badly-behaved types properly + { + using Array = operator_hijacker[]; + std::shared_ptr p1 = std::make_shared(3); + std::shared_ptr p2 = std::make_shared(3, operator_hijacker()); + assert(p1 != nullptr); + assert(p2 != nullptr); + } + + // Check that we SFINAE-away for invalid arguments + { + struct T { }; + static_assert( CanMakeShared); + static_assert( CanMakeShared); + static_assert(!CanMakeShared); // too many arguments + } + + return 0; +} diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.pass.cpp --- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.pass.cpp +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.pass.cpp @@ -10,13 +10,15 @@ // shared_ptr -// template shared_ptr make_shared(Args&&... args); +// template +// shared_ptr make_shared(Args&&... args); // T is not an array #include #include -#include "test_macros.h" #include "count_new.h" +#include "operator_hijacker.h" +#include "test_macros.h" struct A { @@ -122,6 +124,14 @@ #endif assert(A::count == 0); + // Make sure std::make_shared handles badly-behaved types properly + { + std::shared_ptr p1 = std::make_shared(); + std::shared_ptr p2 = std::make_shared(operator_hijacker()); + assert(p1 != nullptr); + assert(p2 != nullptr); + } + test(true); test(3); test(5.0); diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.private.compile.fail.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.private.compile.fail.cpp --- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.private.compile.fail.cpp +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.private.compile.fail.cpp @@ -10,7 +10,8 @@ // shared_ptr -// template shared_ptr make_shared(Args&&... args); +// template +// shared_ptr make_shared(Args&&... args); #include #include diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/types.h b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/types.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/types.h @@ -0,0 +1,99 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_STD_UTILITIES_MEMORY_UTIL_SMARTPTR_SHARED_CREATE_TYPES_H +#define TEST_STD_UTILITIES_MEMORY_UTIL_SMARTPTR_SHARED_CREATE_TYPES_H + +#include +#include + +#include "test_macros.h" + +struct DestroyInReverseOrder { + static void reset() { global_count_ = 0; } + static int alive() { return global_count_; } + + DestroyInReverseOrder() + : DestroyInReverseOrder(&global_count_) + { } + + constexpr DestroyInReverseOrder(int* count) + : count_(count), value_(*count) + { ++*count_; } + + constexpr DestroyInReverseOrder(DestroyInReverseOrder const& other) + : count_(other.count_), value_(*other.count_) + { ++*count_; } + + constexpr int value() const { return value_; } + + // Ensure that we destroy these objects in the reverse order as they were created. + constexpr ~DestroyInReverseOrder() { + --*count_; + assert(*count_ == value_); + } + +private: + int* count_; + int value_; + static int global_count_; +}; + +int DestroyInReverseOrder::global_count_ = 0; + +struct NonMovable { + NonMovable() = default; + NonMovable(NonMovable&&) = delete; +}; + +struct CountCopies { + static void reset() { global_count_ = 0; } + static int copies() { return global_count_; } + + constexpr CountCopies() : copies_(&global_count_) { } + constexpr CountCopies(int* counter) : copies_(counter) { } + constexpr CountCopies(CountCopies const& other) : copies_(other.copies_) { ++*copies_; } + +private: + int* copies_; + static int global_count_; +}; + +int CountCopies::global_count_ = 0; + +struct alignas(alignof(std::max_align_t) * 2) OverAligned { }; + +struct MaxAligned { + std::max_align_t foo; +}; + +#ifndef TEST_HAS_NO_EXCEPTIONS +struct ThrowOnConstruction { + struct exception : std::exception { }; + + ThrowOnConstruction() { on_construct(); } + ThrowOnConstruction(ThrowOnConstruction const&) { on_construct(); } + + static void reset() { throw_after_ = -1; } + static void throw_after(int n) { throw_after_ = n; } + +private: + static int throw_after_; + void on_construct() { + if (throw_after_ == 0) + throw exception{}; + + if (throw_after_ != -1) + --throw_after_; + } +}; + +int ThrowOnConstruction::throw_after_ = -1; +#endif // TEST_HAS_NO_EXCEPTIONS + +#endif // TEST_STD_UTILITIES_MEMORY_UTIL_SMARTPTR_SHARED_CREATE_TYPES_H diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -608,7 +608,7 @@ "libcxx_guard": "!defined(_LIBCPP_HAS_NO_THREADS) && !defined(_LIBCPP_AVAILABILITY_DISABLE_FTM___cpp_lib_shared_mutex)", }, { "name": "__cpp_lib_shared_ptr_arrays", - "values": { "c++17": 201611 }, + "values": { "c++17": 201611, "c++20": 201707 }, "headers": ["memory"], }, { "name": "__cpp_lib_shared_ptr_weak_type",