//===----------------------------------------------------------------------===// // // 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_ALLOCATOR_H #define TEST_ALLOCATOR_H #include #include #include #include #include #include #include #include #include "test_macros.h" template TEST_CONSTEXPR_CXX20 inline typename std::allocator_traits::size_type alloc_max_size(Alloc const& a) { typedef std::allocator_traits AT; return AT::max_size(a); } struct test_allocator_statistics { int time_to_throw = 0; int throw_after = INT_MAX; int count = 0; // the number of active instances int alloc_count = 0; // the number of allocations not deallocating int allocated_size = 0; // the size of allocated elements int construct_count = 0; // the number of times that ::construct was called int destroy_count = 0; // the number of times that ::destroy was called int copied = 0; int moved = 0; int converted = 0; TEST_CONSTEXPR_CXX14 void clear() { assert(count == 0 && "clearing leaking allocator data?"); count = 0; time_to_throw = 0; alloc_count = 0; allocated_size = 0; construct_count = 0; destroy_count = 0; throw_after = INT_MAX; clear_ctor_counters(); } TEST_CONSTEXPR_CXX14 void clear_ctor_counters() { copied = 0; moved = 0; converted = 0; } }; struct test_alloc_base { TEST_CONSTEXPR static const int destructed_value = -1; TEST_CONSTEXPR static const int moved_value = INT_MAX; }; template class test_allocator { int data_ = 0; // participates in equality int id_ = 0; // unique identifier, doesn't participate in equality test_allocator_statistics* stats_ = nullptr; template friend class test_allocator; public: typedef unsigned size_type; typedef int difference_type; typedef T value_type; typedef value_type* pointer; typedef const value_type* const_pointer; typedef typename std::add_lvalue_reference::type reference; typedef typename std::add_lvalue_reference::type const_reference; template struct rebind { typedef test_allocator other; }; TEST_CONSTEXPR test_allocator() TEST_NOEXCEPT = default; TEST_CONSTEXPR_CXX14 explicit test_allocator(test_allocator_statistics* stats) TEST_NOEXCEPT : stats_(stats) { if (stats_ != nullptr) ++stats_->count; } TEST_CONSTEXPR explicit test_allocator(int data) TEST_NOEXCEPT : data_(data) {} TEST_CONSTEXPR_CXX14 explicit test_allocator(int data, test_allocator_statistics* stats) TEST_NOEXCEPT : data_(data), stats_(stats) { if (stats != nullptr) ++stats_->count; } TEST_CONSTEXPR explicit test_allocator(int data, int id) TEST_NOEXCEPT : data_(data), id_(id) {} TEST_CONSTEXPR_CXX14 explicit test_allocator(int data, int id, test_allocator_statistics* stats) TEST_NOEXCEPT : data_(data), id_(id), stats_(stats) { if (stats_ != nullptr) ++stats_->count; } TEST_CONSTEXPR_CXX14 test_allocator(const test_allocator& a) TEST_NOEXCEPT : data_(a.data_), id_(a.id_), stats_(a.stats_) { assert(a.data_ != test_alloc_base::destructed_value && a.id_ != test_alloc_base::destructed_value && "copying from destroyed allocator"); if (stats_ != nullptr) { ++stats_->count; ++stats_->copied; } } TEST_CONSTEXPR_CXX14 test_allocator(test_allocator&& a) TEST_NOEXCEPT : data_(a.data_), id_(a.id_), stats_(a.stats_) { if (stats_ != nullptr) { ++stats_->count; ++stats_->moved; } assert(a.data_ != test_alloc_base::destructed_value && a.id_ != test_alloc_base::destructed_value && "moving from destroyed allocator"); a.data_ = test_alloc_base::moved_value; a.id_ = test_alloc_base::moved_value; } template TEST_CONSTEXPR_CXX14 test_allocator(const test_allocator& a) TEST_NOEXCEPT : data_(a.data_), id_(a.id_), stats_(a.stats_) { if (stats_ != nullptr) { ++stats_->count; ++stats_->converted; } } TEST_CONSTEXPR_CXX20 ~test_allocator() TEST_NOEXCEPT { assert(data_ != test_alloc_base::destructed_value); assert(id_ != test_alloc_base::destructed_value); if (stats_ != nullptr) --stats_->count; data_ = test_alloc_base::destructed_value; id_ = test_alloc_base::destructed_value; } TEST_CONSTEXPR pointer address(reference x) const { return &x; } TEST_CONSTEXPR const_pointer address(const_reference x) const { return &x; } TEST_CONSTEXPR_CXX14 pointer allocate(size_type n, const void* = nullptr) { assert(data_ != test_alloc_base::destructed_value); if (stats_ != nullptr) { if (stats_->time_to_throw >= stats_->throw_after) TEST_THROW(std::bad_alloc()); ++stats_->time_to_throw; ++stats_->alloc_count; stats_->allocated_size += n; } return std::allocator().allocate(n); } TEST_CONSTEXPR_CXX14 void deallocate(pointer p, size_type s) { assert(data_ != test_alloc_base::destructed_value); if (stats_ != nullptr) { --stats_->alloc_count; stats_->allocated_size -= s; } std::allocator().deallocate(p, s); } TEST_CONSTEXPR size_type max_size() const TEST_NOEXCEPT { return UINT_MAX / sizeof(T); } template TEST_CONSTEXPR_CXX20 void construct(pointer p, U&& val) { if (stats_ != nullptr) ++stats_->construct_count; #if TEST_STD_VER > 17 std::construct_at(std::to_address(p), std::forward(val)); #else ::new (static_cast(p)) T(std::forward(val)); #endif } TEST_CONSTEXPR_CXX14 void destroy(pointer p) { if (stats_ != nullptr) ++stats_->destroy_count; p->~T(); } TEST_CONSTEXPR friend bool operator==(const test_allocator& x, const test_allocator& y) { return x.data_ == y.data_; } TEST_CONSTEXPR friend bool operator!=(const test_allocator& x, const test_allocator& y) { return !(x == y); } TEST_CONSTEXPR int get_data() const { return data_; } TEST_CONSTEXPR int get_id() const { return id_; } }; template <> class test_allocator { int data_ = 0; int id_ = 0; test_allocator_statistics* stats_ = nullptr; template friend class test_allocator; public: typedef unsigned size_type; typedef int difference_type; typedef void value_type; typedef value_type* pointer; typedef const value_type* const_pointer; template struct rebind { typedef test_allocator other; }; TEST_CONSTEXPR test_allocator() TEST_NOEXCEPT = default; TEST_CONSTEXPR_CXX14 explicit test_allocator(test_allocator_statistics* stats) TEST_NOEXCEPT : stats_(stats) {} TEST_CONSTEXPR explicit test_allocator(int data) TEST_NOEXCEPT : data_(data) {} TEST_CONSTEXPR explicit test_allocator(int data, test_allocator_statistics* stats) TEST_NOEXCEPT : data_(data), stats_(stats) {} TEST_CONSTEXPR explicit test_allocator(int data, int id) : data_(data), id_(id) {} TEST_CONSTEXPR_CXX14 explicit test_allocator(int data, int id, test_allocator_statistics* stats) TEST_NOEXCEPT : data_(data), id_(id), stats_(stats) {} TEST_CONSTEXPR_CXX14 explicit test_allocator(const test_allocator& a) TEST_NOEXCEPT : data_(a.data_), id_(a.id_), stats_(a.stats_) {} template TEST_CONSTEXPR_CXX14 test_allocator(const test_allocator& a) TEST_NOEXCEPT : data_(a.data_), id_(a.id_), stats_(a.stats_) {} TEST_CONSTEXPR_CXX20 ~test_allocator() TEST_NOEXCEPT { data_ = test_alloc_base::destructed_value; id_ = test_alloc_base::destructed_value; } TEST_CONSTEXPR int get_id() const { return id_; } TEST_CONSTEXPR int get_data() const { return data_; } TEST_CONSTEXPR friend bool operator==(const test_allocator& x, const test_allocator& y) { return x.data_ == y.data_; } TEST_CONSTEXPR friend bool operator!=(const test_allocator& x, const test_allocator& y) { return !(x == y); } }; template class other_allocator { int data_ = -1; template friend class other_allocator; public: typedef T value_type; TEST_CONSTEXPR_CXX14 other_allocator() {} TEST_CONSTEXPR_CXX14 explicit other_allocator(int i) : data_(i) {} template TEST_CONSTEXPR_CXX14 other_allocator(const other_allocator& a) : data_(a.data_) {} TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) { return std::allocator().allocate(n); } TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t s) { std::allocator().deallocate(p, s); } TEST_CONSTEXPR_CXX14 other_allocator select_on_container_copy_construction() const { return other_allocator(-2); } TEST_CONSTEXPR_CXX14 friend bool operator==(const other_allocator& x, const other_allocator& y) { return x.data_ == y.data_; } TEST_CONSTEXPR_CXX14 friend bool operator!=(const other_allocator& x, const other_allocator& y) { return !(x == y); } TEST_CONSTEXPR int get_data() const { return data_; } typedef std::true_type propagate_on_container_copy_assignment; typedef std::true_type propagate_on_container_move_assignment; typedef std::true_type propagate_on_container_swap; #if TEST_STD_VER < 11 std::size_t max_size() const { return UINT_MAX / sizeof(T); } #endif }; struct Ctor_Tag {}; template class TaggingAllocator; struct Tag_X { // All constructors must be passed the Tag type. // DefaultInsertable into vector>, TEST_CONSTEXPR Tag_X(Ctor_Tag) {} // CopyInsertable into vector>, TEST_CONSTEXPR Tag_X(Ctor_Tag, const Tag_X&) {} // MoveInsertable into vector>, and TEST_CONSTEXPR Tag_X(Ctor_Tag, Tag_X&&) {} // EmplaceConstructible into vector> from args. template TEST_CONSTEXPR Tag_X(Ctor_Tag, Args&&...) {} // not DefaultConstructible, CopyConstructible or MoveConstructible. Tag_X() = delete; Tag_X(const Tag_X&) = delete; Tag_X(Tag_X&&) = delete; // CopyAssignable. TEST_CONSTEXPR_CXX14 Tag_X& operator=(const Tag_X&) { return *this; }; // MoveAssignable. TEST_CONSTEXPR_CXX14 Tag_X& operator=(Tag_X&&) { return *this; }; private: ~Tag_X() = default; // Erasable from vector>. friend class TaggingAllocator; }; template class TaggingAllocator { public: using value_type = T; TaggingAllocator() = default; template TEST_CONSTEXPR TaggingAllocator(const TaggingAllocator&) {} template TEST_CONSTEXPR_CXX20 void construct(Tag_X* p, Args&&... args) { #if TEST_STD_VER > 17 std::construct_at(p, Ctor_Tag{}, std::forward(args)...); #else ::new (static_cast(p)) Tag_X(Ctor_Tag(), std::forward(args)...); #endif } template TEST_CONSTEXPR_CXX20 void destroy(U* p) { p->~U(); } TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) { return std::allocator().allocate(n); } TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator().deallocate(p, n); } }; template struct limited_alloc_handle { std::size_t outstanding_ = 0; void* last_alloc_ = nullptr; template TEST_CONSTEXPR_CXX20 T* allocate(std::size_t N) { if (N + outstanding_ > MaxAllocs) TEST_THROW(std::bad_alloc()); auto alloc = std::allocator().allocate(N); last_alloc_ = alloc; outstanding_ += N; return alloc; } template TEST_CONSTEXPR_CXX20 void deallocate(T* ptr, std::size_t N) { if (ptr == last_alloc_) { last_alloc_ = nullptr; assert(outstanding_ >= N); outstanding_ -= N; } std::allocator().deallocate(ptr, N); } }; namespace detail { template class thread_unsafe_shared_ptr { public: thread_unsafe_shared_ptr() = default; TEST_CONSTEXPR_CXX14 thread_unsafe_shared_ptr(const thread_unsafe_shared_ptr& other) : block(other.block) { ++block->ref_count; } TEST_CONSTEXPR_CXX20 ~thread_unsafe_shared_ptr() { --block->ref_count; if (block->ref_count != 0) return; typedef std::allocator_traits > allocator_traits; std::allocator alloc; allocator_traits::destroy(alloc, block); allocator_traits::deallocate(alloc, block, 1); } TEST_CONSTEXPR const T& operator*() const { return block->content; } TEST_CONSTEXPR const T* operator->() const { return &block->content; } TEST_CONSTEXPR_CXX14 T& operator*() { return block->content; } TEST_CONSTEXPR_CXX14 T* operator->() { return &block->content; } TEST_CONSTEXPR_CXX14 T* get() { return &block->content; } TEST_CONSTEXPR const T* get() const { return &block->content; } private: struct control_block { template TEST_CONSTEXPR control_block(Args... args) : content(std::forward(args)...) {} std::size_t ref_count = 1; T content; }; control_block* block = nullptr; template friend TEST_CONSTEXPR_CXX20 thread_unsafe_shared_ptr make_thread_unsafe_shared(Args...); }; template TEST_CONSTEXPR_CXX20 thread_unsafe_shared_ptr make_thread_unsafe_shared(Args... args) { typedef typename thread_unsafe_shared_ptr::control_block control_block_type; typedef std::allocator_traits > allocator_traits; thread_unsafe_shared_ptr ptr; std::allocator alloc; ptr.block = allocator_traits::allocate(alloc, 1); allocator_traits::construct(alloc, ptr.block, std::forward(args)...); return ptr; } } // namespace detail template class limited_allocator { template friend class limited_allocator; typedef limited_alloc_handle BuffT; detail::thread_unsafe_shared_ptr handle_; public: typedef T value_type; typedef value_type* pointer; typedef const value_type* const_pointer; typedef value_type& reference; typedef const value_type& const_reference; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; template struct rebind { typedef limited_allocator other; }; TEST_CONSTEXPR_CXX20 limited_allocator() : handle_(detail::make_thread_unsafe_shared()) {} limited_allocator(limited_allocator const&) = default; template TEST_CONSTEXPR explicit limited_allocator(limited_allocator const& other) : handle_(other.handle_) {} limited_allocator& operator=(const limited_allocator&) = delete; TEST_CONSTEXPR_CXX20 pointer allocate(size_type n) { return handle_->template allocate(n); } TEST_CONSTEXPR_CXX20 void deallocate(pointer p, size_type n) { handle_->template deallocate(p, n); } TEST_CONSTEXPR size_type max_size() const { return N; } TEST_CONSTEXPR BuffT* getHandle() const { return handle_.get(); } }; template TEST_CONSTEXPR inline bool operator==(limited_allocator const& LHS, limited_allocator const& RHS) { return LHS.getHandle() == RHS.getHandle(); } template TEST_CONSTEXPR inline bool operator!=(limited_allocator const& LHS, limited_allocator const& RHS) { return !(LHS == RHS); } // Track the "provenance" of this allocator instance: how many times was // select_on_container_copy_construction called in order to produce it? // template struct SocccAllocator { using value_type = T; int count_ = 0; explicit SocccAllocator(int i) : count_(i) {} template SocccAllocator(const SocccAllocator& a) : count_(a.count_) {} T* allocate(std::size_t n) { return std::allocator().allocate(n); } void deallocate(T* p, std::size_t n) { std::allocator().deallocate(p, n); } SocccAllocator select_on_container_copy_construction() const { return SocccAllocator(count_ + 1); } bool operator==(const SocccAllocator&) const { return true; } using propagate_on_container_copy_assignment = std::false_type; using propagate_on_container_move_assignment = std::false_type; using propagate_on_container_swap = std::false_type; }; #endif // TEST_ALLOCATOR_H