Home | History | Annotate | Download | only in support
      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