Eric Fiselier | 780b51d | 2016-12-28 05:53:01 +0000 | [diff] [blame] | 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(); |
Eric Fiselier | 673c5f9c | 2017-01-18 05:34:42 +0000 | [diff] [blame] | 194 | if constexpr (CT != CT_ForwardList ) { |
| 195 | // FIXME: This should work for both forward_list and string |
| 196 | SwapInvalidatesIterators(); |
Eric Fiselier | 780b51d | 2016-12-28 05:53:01 +0000 | [diff] [blame] | 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) ); |
Eric Fiselier | 673c5f9c | 2017-01-18 05:34:42 +0000 | [diff] [blame] | 363 | if (CT == CT_String) { |
| 364 | CHECK_DEBUG_THROWS(C1.erase(it2)); |
| 365 | } else |
| 366 | C1.erase(it2); |
Eric Fiselier | 780b51d | 2016-12-28 05:53:01 +0000 | [diff] [blame] | 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 |