1 //===----------------------------------------------------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is dual licensed under the MIT and the University of Illinois Open 6 // Source Licenses. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #ifndef TEST_SUPPORT_DEBUG_MODE_HELPER_H 11 #define TEST_SUPPORT_DEBUG_MODE_HELPER_H 12 13 #ifndef _LIBCPP_DEBUG 14 #error _LIBCPP_DEBUG must be defined before including this header 15 #endif 16 #ifndef _LIBCPP_DEBUG_USE_EXCEPTIONS 17 #error _LIBCPP_DEBUG_USE_EXCEPTIONS must be defined before including this header 18 #endif 19 20 #include <ciso646> 21 #ifndef _LIBCPP_VERSION 22 #error This header may only be used for libc++ tests" 23 #endif 24 25 #include <__debug> 26 #include <utility> 27 #include <cstddef> 28 #include <cstdlib> 29 #include <cassert> 30 31 #include "test_macros.h" 32 #include "assert_checkpoint.h" 33 #include "test_allocator.h" 34 35 // These test make use of 'if constexpr'. 36 #if TEST_STD_VER <= 14 37 #error This header may only be used in C++17 and greater 38 #endif 39 #ifdef TEST_HAS_NO_EXCEPTIONS 40 #error These tests require exceptions 41 #endif 42 43 #ifndef __cpp_if_constexpr 44 #error These tests require if constexpr 45 #endif 46 47 /// Assert that the specified expression throws a libc++ debug exception. 48 #define CHECK_DEBUG_THROWS(...) assert((CheckDebugThrows( [&]() { __VA_ARGS__; } ))) 49 50 template <class Func> 51 inline bool CheckDebugThrows(Func&& func) { 52 try { 53 func(); 54 } catch (std::__libcpp_debug_exception const&) { 55 return true; 56 } 57 return false; 58 } 59 60 namespace IteratorDebugChecks { 61 62 enum ContainerType { 63 CT_None, 64 CT_String, 65 CT_Vector, 66 CT_VectorBool, 67 CT_List, 68 CT_Deque, 69 CT_ForwardList, 70 CT_Map, 71 CT_Set, 72 CT_MultiMap, 73 CT_MultiSet, 74 CT_UnorderedMap, 75 CT_UnorderedSet, 76 CT_UnorderedMultiMap, 77 CT_UnorderedMultiSet 78 }; 79 80 constexpr bool isSequential(ContainerType CT) { 81 return CT_Vector >= CT && CT_ForwardList <= CT; 82 } 83 84 constexpr bool isAssociative(ContainerType CT) { 85 return CT_Map >= CT && CT_MultiSet <= CT; 86 } 87 88 constexpr bool isUnordered(ContainerType CT) { 89 return CT_UnorderedMap >= CT && CT_UnorderedMultiSet <= CT; 90 } 91 92 constexpr bool isSet(ContainerType CT) { 93 return CT == CT_Set 94 || CT == CT_MultiSet 95 || CT == CT_UnorderedSet 96 || CT == CT_UnorderedMultiSet; 97 } 98 99 constexpr bool isMap(ContainerType CT) { 100 return CT == CT_Map 101 || CT == CT_MultiMap 102 || CT == CT_UnorderedMap 103 || CT == CT_UnorderedMultiMap; 104 } 105 106 constexpr bool isMulti(ContainerType CT) { 107 return CT == CT_MultiMap 108 || CT == CT_MultiSet 109 || CT == CT_UnorderedMultiMap 110 || CT == CT_UnorderedMultiSet; 111 } 112 113 template <class Container, class ValueType = typename Container::value_type> 114 struct ContainerDebugHelper { 115 static_assert(std::is_constructible<ValueType, int>::value, 116 "must be constructible from int"); 117 118 static ValueType makeValueType(int val = 0, int = 0) { 119 return ValueType(val); 120 } 121 }; 122 123 template <class Container> 124 struct ContainerDebugHelper<Container, char> { 125 static char makeValueType(int = 0, int = 0) { 126 return 'A'; 127 } 128 }; 129 130 template <class Container, class Key, class Value> 131 struct ContainerDebugHelper<Container, std::pair<const Key, Value> > { 132 using ValueType = std::pair<const Key, Value>; 133 static_assert(std::is_constructible<Key, int>::value, 134 "must be constructible from int"); 135 static_assert(std::is_constructible<Value, int>::value, 136 "must be constructible from int"); 137 138 static ValueType makeValueType(int key = 0, int val = 0) { 139 return ValueType(key, val); 140 } 141 }; 142 143 template <class Container, ContainerType CT, 144 class Helper = ContainerDebugHelper<Container> > 145 struct BasicContainerChecks { 146 using value_type = typename Container::value_type; 147 using iterator = typename Container::iterator; 148 using const_iterator = typename Container::const_iterator; 149 using allocator_type = typename Container::allocator_type; 150 using traits = std::iterator_traits<iterator>; 151 using category = typename traits::iterator_category; 152 153 static_assert(std::is_same<test_allocator<value_type>, allocator_type>::value, 154 "the container must use a test allocator"); 155 156 static constexpr bool IsBiDir = 157 std::is_convertible<category, std::bidirectional_iterator_tag>::value; 158 159 public: 160 static void run() { 161 run_iterator_tests(); 162 run_container_tests(); 163 run_allocator_aware_tests(); 164 } 165 166 static void run_iterator_tests() { 167 try { 168 TestNullIterators<iterator>(); 169 TestNullIterators<const_iterator>(); 170 if constexpr (IsBiDir) { DecrementBegin(); } 171 IncrementEnd(); 172 DerefEndIterator(); 173 } catch (...) { 174 assert(false && "uncaught debug exception"); 175 } 176 } 177 178 static void run_container_tests() { 179 try { 180 CopyInvalidatesIterators(); 181 MoveInvalidatesIterators(); 182 if constexpr (CT != CT_ForwardList) { 183 EraseIter(); 184 EraseIterIter(); 185 } 186 } catch (...) { 187 assert(false && "uncaught debug exception"); 188 } 189 } 190 191 static void run_allocator_aware_tests() { 192 try { 193 SwapNonEqualAllocators(); 194 if constexpr (CT != CT_ForwardList ) { 195 // FIXME: This should work for both forward_list and string 196 SwapInvalidatesIterators(); 197 } 198 } catch (...) { 199 assert(false && "uncaught debug exception"); 200 } 201 } 202 203 static Container makeContainer(int size, allocator_type A = allocator_type()) { 204 Container C(A); 205 if constexpr (CT == CT_ForwardList) { 206 for (int i = 0; i < size; ++i) 207 C.insert_after(C.before_begin(), Helper::makeValueType(i)); 208 } else { 209 for (int i = 0; i < size; ++i) 210 C.insert(C.end(), Helper::makeValueType(i)); 211 assert(C.size() == static_cast<std::size_t>(size)); 212 } 213 return C; 214 } 215 216 static value_type makeValueType(int value) { 217 return Helper::makeValueType(value); 218 } 219 220 private: 221 // Iterator tests 222 template <class Iter> 223 static void TestNullIterators() { 224 CHECKPOINT("testing null iterator"); 225 Iter it; 226 CHECK_DEBUG_THROWS( ++it ); 227 CHECK_DEBUG_THROWS( it++ ); 228 CHECK_DEBUG_THROWS( *it ); 229 if constexpr (CT != CT_VectorBool) { 230 CHECK_DEBUG_THROWS( it.operator->() ); 231 } 232 if constexpr (IsBiDir) { 233 CHECK_DEBUG_THROWS( --it ); 234 CHECK_DEBUG_THROWS( it-- ); 235 } 236 } 237 238 static void DecrementBegin() { 239 CHECKPOINT("testing decrement on begin"); 240 Container C = makeContainer(1); 241 iterator i = C.end(); 242 const_iterator ci = C.cend(); 243 --i; 244 --ci; 245 assert(i == C.begin()); 246 CHECK_DEBUG_THROWS( --i ); 247 CHECK_DEBUG_THROWS( i-- ); 248 CHECK_DEBUG_THROWS( --ci ); 249 CHECK_DEBUG_THROWS( ci-- ); 250 } 251 252 static void IncrementEnd() { 253 CHECKPOINT("testing increment on end"); 254 Container C = makeContainer(1); 255 iterator i = C.begin(); 256 const_iterator ci = C.begin(); 257 ++i; 258 ++ci; 259 assert(i == C.end()); 260 CHECK_DEBUG_THROWS( ++i ); 261 CHECK_DEBUG_THROWS( i++ ); 262 CHECK_DEBUG_THROWS( ++ci ); 263 CHECK_DEBUG_THROWS( ci++ ); 264 } 265 266 static void DerefEndIterator() { 267 CHECKPOINT("testing deref end iterator"); 268 Container C = makeContainer(1); 269 iterator i = C.begin(); 270 const_iterator ci = C.cbegin(); 271 (void)*i; (void)*ci; 272 if constexpr (CT != CT_VectorBool) { 273 i.operator->(); 274 ci.operator->(); 275 } 276 ++i; ++ci; 277 assert(i == C.end()); 278 CHECK_DEBUG_THROWS( *i ); 279 CHECK_DEBUG_THROWS( *ci ); 280 if constexpr (CT != CT_VectorBool) { 281 CHECK_DEBUG_THROWS( i.operator->() ); 282 CHECK_DEBUG_THROWS( ci.operator->() ); 283 } 284 } 285 286 // Container tests 287 static void CopyInvalidatesIterators() { 288 CHECKPOINT("copy invalidates iterators"); 289 Container C1 = makeContainer(3); 290 iterator i = C1.begin(); 291 Container C2 = C1; 292 if constexpr (CT == CT_ForwardList) { 293 iterator i_next = i; 294 ++i_next; 295 (void)*i_next; 296 CHECK_DEBUG_THROWS( C2.erase_after(i) ); 297 C1.erase_after(i); 298 CHECK_DEBUG_THROWS( *i_next ); 299 } else { 300 CHECK_DEBUG_THROWS( C2.erase(i) ); 301 (void)*i; 302 C1.erase(i); 303 CHECK_DEBUG_THROWS( *i ); 304 } 305 } 306 307 static void MoveInvalidatesIterators() { 308 CHECKPOINT("copy move invalidates iterators"); 309 Container C1 = makeContainer(3); 310 iterator i = C1.begin(); 311 Container C2 = std::move(C1); 312 (void) *i; 313 if constexpr (CT == CT_ForwardList) { 314 CHECK_DEBUG_THROWS( C1.erase_after(i) ); 315 C2.erase_after(i); 316 } else { 317 CHECK_DEBUG_THROWS( C1.erase(i) ); 318 C2.erase(i); 319 CHECK_DEBUG_THROWS(*i); 320 } 321 } 322 323 static void EraseIter() { 324 CHECKPOINT("testing erase invalidation"); 325 Container C1 = makeContainer(2); 326 iterator it1 = C1.begin(); 327 iterator it1_next = it1; 328 ++it1_next; 329 Container C2 = C1; 330 CHECK_DEBUG_THROWS( C2.erase(it1) ); // wrong container 331 CHECK_DEBUG_THROWS( C2.erase(C2.end()) ); // erase with end 332 C1.erase(it1_next); 333 CHECK_DEBUG_THROWS( C1.erase(it1_next) ); // invalidated iterator 334 C1.erase(it1); 335 CHECK_DEBUG_THROWS( C1.erase(it1) ); // invalidated iterator 336 } 337 338 static void EraseIterIter() { 339 CHECKPOINT("testing erase iter iter invalidation"); 340 Container C1 = makeContainer(2); 341 iterator it1 = C1.begin(); 342 iterator it1_next = it1; 343 ++it1_next; 344 Container C2 = C1; 345 iterator it2 = C2.begin(); 346 iterator it2_next = it2; 347 ++it2_next; 348 CHECK_DEBUG_THROWS( C2.erase(it1, it1_next) ); // begin from wrong container 349 CHECK_DEBUG_THROWS( C2.erase(it1, it2_next) ); // end from wrong container 350 CHECK_DEBUG_THROWS( C2.erase(it2, it1_next) ); // both from wrong container 351 C2.erase(it2, it2_next); 352 } 353 354 // Allocator aware tests 355 static void SwapInvalidatesIterators() { 356 CHECKPOINT("testing swap invalidates iterators"); 357 Container C1 = makeContainer(3); 358 Container C2 = makeContainer(3); 359 iterator it1 = C1.begin(); 360 iterator it2 = C2.begin(); 361 swap(C1, C2); 362 CHECK_DEBUG_THROWS( C1.erase(it1) ); 363 if (CT == CT_String) { 364 CHECK_DEBUG_THROWS(C1.erase(it2)); 365 } else 366 C1.erase(it2); 367 //C2.erase(it1); 368 CHECK_DEBUG_THROWS( C1.erase(it1) ); 369 } 370 371 static void SwapNonEqualAllocators() { 372 CHECKPOINT("testing swap with non-equal allocators"); 373 Container C1 = makeContainer(3, allocator_type(1)); 374 Container C2 = makeContainer(1, allocator_type(2)); 375 Container C3 = makeContainer(2, allocator_type(2)); 376 swap(C2, C3); 377 CHECK_DEBUG_THROWS( swap(C1, C2) ); 378 } 379 380 private: 381 BasicContainerChecks() = delete; 382 }; 383 384 } // namespace IteratorDebugChecks 385 386 #endif // TEST_SUPPORT_DEBUG_MODE_HELPER_H 387