//===----------------------------------------------------------------------===// // // 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_SUPPORT_CONTAINER_DEBUG_TESTS_H #define TEST_SUPPORT_CONTAINER_DEBUG_TESTS_H #include <ciso646> #ifndef _LIBCPP_VERSION #error This header may only be used for libc++ tests" #endif #ifndef _LIBCPP_DEBUG #error _LIBCPP_DEBUG must be defined before including this header #endif #include <__debug> #include <utility> #include <cstddef> #include <cstdlib> #include <cassert> #include <string> #include <sstream> #include <iostream> #include "test_macros.h" #include "debug_mode_helper.h" #include "assert_checkpoint.h" #include "test_allocator.h" // These test make use of 'if constexpr'. #if TEST_STD_VER <= 14 #error This header may only be used in C++17 and greater #endif #ifndef __cpp_if_constexpr #error These tests require if constexpr #endif namespace IteratorDebugChecks { enum ContainerType { CT_None, CT_String, CT_Vector, CT_VectorBool, CT_List, CT_Deque, CT_ForwardList, CT_Map, CT_Set, CT_MultiMap, CT_MultiSet, CT_UnorderedMap, CT_UnorderedSet, CT_UnorderedMultiMap, CT_UnorderedMultiSet }; constexpr bool isSequential(ContainerType CT) { return CT >= CT_Vector && CT <= CT_ForwardList; } constexpr bool isAssociative(ContainerType CT) { return CT >= CT_Map && CT <= CT_MultiSet; } constexpr bool isUnordered(ContainerType CT) { return CT >= CT_UnorderedMap && CT <= CT_UnorderedMultiSet; } constexpr bool isSet(ContainerType CT) { return CT == CT_Set || CT == CT_MultiSet || CT == CT_UnorderedSet || CT == CT_UnorderedMultiSet; } constexpr bool isMap(ContainerType CT) { return CT == CT_Map || CT == CT_MultiMap || CT == CT_UnorderedMap || CT == CT_UnorderedMultiMap; } constexpr bool isMulti(ContainerType CT) { return CT == CT_MultiMap || CT == CT_MultiSet || CT == CT_UnorderedMultiMap || CT == CT_UnorderedMultiSet; } template <class Container, class ValueType = typename Container::value_type> struct ContainerDebugHelper { static_assert(std::is_constructible<ValueType, int>::value, "must be constructible from int"); static ValueType makeValueType(int val = 0, int = 0) { return ValueType(val); } }; template <class Container> struct ContainerDebugHelper<Container, char> { static char makeValueType(int = 0, int = 0) { return 'A'; } }; template <class Container, class Key, class Value> struct ContainerDebugHelper<Container, std::pair<const Key, Value> > { using ValueType = std::pair<const Key, Value>; static_assert(std::is_constructible<Key, int>::value, "must be constructible from int"); static_assert(std::is_constructible<Value, int>::value, "must be constructible from int"); static ValueType makeValueType(int key = 0, int val = 0) { return ValueType(key, val); } }; template <class Container, ContainerType CT, class Helper = ContainerDebugHelper<Container> > struct BasicContainerChecks { using value_type = typename Container::value_type; using iterator = typename Container::iterator; using const_iterator = typename Container::const_iterator; using allocator_type = typename Container::allocator_type; using traits = std::iterator_traits<iterator>; using category = typename traits::iterator_category; static_assert(std::is_same<test_allocator<value_type>, allocator_type>::value, "the container must use a test allocator"); static constexpr bool IsBiDir = std::is_convertible<category, std::bidirectional_iterator_tag>::value; public: static void run() { run_iterator_tests(); run_container_tests(); run_allocator_aware_tests(); } static void run_iterator_tests() { TestNullIterators<iterator>(); TestNullIterators<const_iterator>(); if constexpr (IsBiDir) { DecrementBegin(); } IncrementEnd(); DerefEndIterator(); } static void run_container_tests() { CopyInvalidatesIterators(); MoveInvalidatesIterators(); if constexpr (CT != CT_ForwardList) { EraseIter(); EraseIterIter(); } } static void run_allocator_aware_tests() { SwapNonEqualAllocators(); if constexpr (CT != CT_ForwardList ) { // FIXME: This should work for both forward_list and string SwapInvalidatesIterators(); } } static Container makeContainer(int size, allocator_type A = allocator_type()) { Container C(A); if constexpr (CT == CT_ForwardList) { for (int i = 0; i < size; ++i) C.insert_after(C.before_begin(), Helper::makeValueType(i)); } else { for (int i = 0; i < size; ++i) C.insert(C.end(), Helper::makeValueType(i)); assert(C.size() == static_cast<std::size_t>(size)); } return C; } static value_type makeValueType(int value) { return Helper::makeValueType(value); } private: // Iterator tests template <class Iter> static void TestNullIterators() { CHECKPOINT("testing null iterator"); Iter it; EXPECT_DEATH( ++it ); EXPECT_DEATH( it++ ); EXPECT_DEATH( *it ); if constexpr (CT != CT_VectorBool) { EXPECT_DEATH( it.operator->() ); } if constexpr (IsBiDir) { EXPECT_DEATH( --it ); EXPECT_DEATH( it-- ); } } static void DecrementBegin() { CHECKPOINT("testing decrement on begin"); Container C = makeContainer(1); iterator i = C.end(); const_iterator ci = C.cend(); --i; --ci; assert(i == C.begin()); EXPECT_DEATH( --i ); EXPECT_DEATH( i-- ); EXPECT_DEATH( --ci ); EXPECT_DEATH( ci-- ); } static void IncrementEnd() { CHECKPOINT("testing increment on end"); Container C = makeContainer(1); iterator i = C.begin(); const_iterator ci = C.begin(); ++i; ++ci; assert(i == C.end()); EXPECT_DEATH( ++i ); EXPECT_DEATH( i++ ); EXPECT_DEATH( ++ci ); EXPECT_DEATH( ci++ ); } static void DerefEndIterator() { CHECKPOINT("testing deref end iterator"); Container C = makeContainer(1); iterator i = C.begin(); const_iterator ci = C.cbegin(); (void)*i; (void)*ci; if constexpr (CT != CT_VectorBool) { i.operator->(); ci.operator->(); } ++i; ++ci; assert(i == C.end()); EXPECT_DEATH( *i ); EXPECT_DEATH( *ci ); if constexpr (CT != CT_VectorBool) { EXPECT_DEATH( i.operator->() ); EXPECT_DEATH( ci.operator->() ); } } // Container tests static void CopyInvalidatesIterators() { CHECKPOINT("copy invalidates iterators"); Container C1 = makeContainer(3); iterator i = C1.begin(); Container C2 = C1; if constexpr (CT == CT_ForwardList) { iterator i_next = i; ++i_next; (void)*i_next; EXPECT_DEATH( C2.erase_after(i) ); C1.erase_after(i); EXPECT_DEATH( *i_next ); } else { EXPECT_DEATH( C2.erase(i) ); (void)*i; C1.erase(i); EXPECT_DEATH( *i ); } } static void MoveInvalidatesIterators() { CHECKPOINT("copy move invalidates iterators"); Container C1 = makeContainer(3); iterator i = C1.begin(); Container C2 = std::move(C1); (void) *i; if constexpr (CT == CT_ForwardList) { EXPECT_DEATH( C1.erase_after(i) ); C2.erase_after(i); } else { EXPECT_DEATH( C1.erase(i) ); C2.erase(i); EXPECT_DEATH(*i); } } static void EraseIter() { CHECKPOINT("testing erase invalidation"); Container C1 = makeContainer(2); iterator it1 = C1.begin(); iterator it1_next = it1; ++it1_next; Container C2 = C1; EXPECT_DEATH( C2.erase(it1) ); // wrong container EXPECT_DEATH( C2.erase(C2.end()) ); // erase with end C1.erase(it1_next); EXPECT_DEATH( C1.erase(it1_next) ); // invalidated iterator C1.erase(it1); EXPECT_DEATH( C1.erase(it1) ); // invalidated iterator } static void EraseIterIter() { CHECKPOINT("testing erase iter iter invalidation"); Container C1 = makeContainer(2); iterator it1 = C1.begin(); iterator it1_next = it1; ++it1_next; Container C2 = C1; iterator it2 = C2.begin(); iterator it2_next = it2; ++it2_next; EXPECT_DEATH( C2.erase(it1, it1_next) ); // begin from wrong container EXPECT_DEATH( C2.erase(it1, it2_next) ); // end from wrong container EXPECT_DEATH( C2.erase(it2, it1_next) ); // both from wrong container C2.erase(it2, it2_next); } // Allocator aware tests static void SwapInvalidatesIterators() { CHECKPOINT("testing swap invalidates iterators"); Container C1 = makeContainer(3); Container C2 = makeContainer(3); iterator it1 = C1.begin(); iterator it2 = C2.begin(); swap(C1, C2); EXPECT_DEATH( C1.erase(it1) ); if (CT == CT_String) { EXPECT_DEATH(C1.erase(it2)); } else C1.erase(it2); //C2.erase(it1); EXPECT_DEATH( C1.erase(it1) ); } static void SwapNonEqualAllocators() { CHECKPOINT("testing swap with non-equal allocators"); Container C1 = makeContainer(3, allocator_type(1)); Container C2 = makeContainer(1, allocator_type(2)); Container C3 = makeContainer(2, allocator_type(2)); swap(C2, C3); EXPECT_DEATH( swap(C1, C2) ); } private: BasicContainerChecks() = delete; }; } // namespace IteratorDebugChecks #endif // TEST_SUPPORT_CONTAINER_DEBUG_TESTS_H