1 /*
2     Copyright (c) 2005-2020 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #include <common/test.h>
18 #include <common/utils.h>
19 #include "common/utils_report.h"
20 #include <common/spin_barrier.h>
21 #include <common/state_trackable.h>
22 #include <common/container_move_support.h>
23 #include <common/containers_common.h>
24 #include <common/initializer_list_support.h>
25 #include <common/vector_types.h>
26 #include <tbb/concurrent_hash_map.h>
27 #include <tbb/global_control.h>
28 #include <tbb/parallel_for.h>
29 
30 //! \file conformance_concurrent_hash_map.cpp
31 //! \brief Test for [containers.concurrent_hash_map containers.tbb_hash_compare] specification
32 
33 /** Has tightly controlled interface so that we can verify
34     that concurrent_hash_map uses only the required interface. */
35 class MyException : public std::bad_alloc {
36 public:
37     virtual const char *what() const throw() override { return "out of items limit"; }
38     virtual ~MyException() throw() {}
39 };
40 
41 /** Has tightly controlled interface so that we can verify
42     that concurrent_hash_map uses only the required interface. */
43 class MyKey {
44 private:
45     int key;
46     friend class MyHashCompare;
47     friend class YourHashCompare;
48 public:
49     MyKey() = default;
50     MyKey( const MyKey& ) = default;
51     void operator=( const MyKey&  ) = delete;
52     static MyKey make( int i ) {
53         MyKey result;
54         result.key = i;
55         return result;
56     }
57 
58     int value_of() const { return key; }
59 };
60 
61 std::atomic<long> MyDataCount;
62 long MyDataCountLimit = 0;
63 
64 class MyData {
65 protected:
66     friend class MyData2;
67     int data;
68     enum state_t {
69         LIVE=0x1234,
70         DEAD=0x5678
71     } my_state;
72     void operator=( const MyData& );    // Deny access
73 public:
74     MyData(int i = 0) {
75         my_state = LIVE;
76         data = i;
77         if (MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) {
78             TBB_TEST_THROW(MyException{});
79         }
80         ++MyDataCount;
81     }
82 
83     MyData( const MyData& other ) {
84         CHECK(other.my_state==LIVE);
85         my_state = LIVE;
86         data = other.data;
87         if(MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) {
88             TBB_TEST_THROW(MyException{});
89         }
90         ++MyDataCount;
91     }
92 
93     ~MyData() {
94         --MyDataCount;
95         my_state = DEAD;
96     }
97 
98     static MyData make( int i ) {
99         MyData result;
100         result.data = i;
101         return result;
102     }
103 
104     int value_of() const {
105         CHECK(my_state==LIVE);
106         return data;
107     }
108 
109     void set_value( int i ) {
110         CHECK(my_state==LIVE);
111         data = i;
112     }
113 
114     bool operator==( const MyData& other ) const {
115         CHECK(other.my_state==LIVE);
116         CHECK(my_state==LIVE);
117         return data == other.data;
118     }
119 };
120 
121 class MyData2 : public MyData {
122 public:
123     MyData2( ) {}
124 
125     MyData2( const MyData2& other ) : MyData() {
126         CHECK(other.my_state==LIVE);
127         CHECK(my_state==LIVE);
128         data = other.data;
129     }
130 
131     MyData2( const MyData& other ) {
132         CHECK(other.my_state==LIVE);
133         CHECK(my_state==LIVE);
134         data = other.data;
135     }
136 
137     void operator=( const MyData& other ) {
138         CHECK(other.my_state==LIVE);
139         CHECK(my_state==LIVE);
140         data = other.data;
141     }
142 
143     void operator=( const MyData2& other ) {
144         CHECK(other.my_state==LIVE);
145         CHECK(my_state==LIVE);
146         data = other.data;
147     }
148 
149     bool operator==( const MyData2& other ) const {
150         CHECK(other.my_state==LIVE);
151         CHECK(my_state==LIVE);
152         return data == other.data;
153     }
154 };
155 
156 class MyHashCompare {
157 public:
158     bool equal( const MyKey& j, const MyKey& k ) const {
159         return j.key==k.key;
160     }
161 
162     unsigned long hash( const MyKey& k ) const {
163         return k.key;
164     }
165 };
166 
167 class YourHashCompare {
168 public:
169     bool equal( const MyKey& j, const MyKey& k ) const {
170         return j.key==k.key;
171     }
172 
173     unsigned long hash( const MyKey& ) const {
174         return 1;
175     }
176 };
177 
178 using test_allocator_type = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData>>>;
179 using test_table_type = tbb::concurrent_hash_map<MyKey, MyData, MyHashCompare, test_allocator_type>;
180 using other_test_table_type = tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare>;
181 
182 template <template <typename...> class ContainerType>
183 void test_member_types() {
184     using container_type = ContainerType<int, int>;
185     static_assert(std::is_same<typename container_type::allocator_type, tbb::tbb_allocator<std::pair<const int, int>>>::value,
186                   "Incorrect default template allocator");
187 
188     static_assert(std::is_same<typename container_type::key_type, int>::value,
189                   "Incorrect container key_type member type");
190     static_assert(std::is_same<typename container_type::value_type, std::pair<const int, int>>::value,
191                   "Incorrect container value_type member type");
192 
193     static_assert(std::is_unsigned<typename container_type::size_type>::value,
194                   "Incorrect container size_type member type");
195     static_assert(std::is_signed<typename container_type::difference_type>::value,
196                   "Incorrect container difference_type member type");
197 
198     using value_type = typename container_type::value_type;
199     static_assert(std::is_same<typename container_type::reference, value_type&>::value,
200                   "Incorrect container reference member type");
201     static_assert(std::is_same<typename container_type::const_reference, const value_type&>::value,
202                   "Incorrect container const_reference member type");
203     using allocator_type = typename container_type::allocator_type;
204     static_assert(std::is_same<typename container_type::pointer, typename std::allocator_traits<allocator_type>::pointer>::value,
205                   "Incorrect container pointer member type");
206     static_assert(std::is_same<typename container_type::const_pointer, typename std::allocator_traits<allocator_type>::const_pointer>::value,
207                   "Incorrect container const_pointer member type");
208 
209     static_assert(utils::is_forward_iterator<typename container_type::iterator>::value,
210                   "Incorrect container iterator member type");
211     static_assert(!std::is_const<typename container_type::iterator::value_type>::value,
212                   "Incorrect container iterator member type");
213     static_assert(utils::is_forward_iterator<typename container_type::const_iterator>::value,
214                   "Incorrect container const_iterator member type");
215     static_assert(std::is_const<typename container_type::const_iterator::value_type>::value,
216                   "Incorrect container iterator member type");
217 }
218 
219 template<typename test_table_type>
220 void FillTable( test_table_type& x, int n ) {
221     for( int i=1; i<=n; ++i ) {
222         MyKey key( MyKey::make(-i) ); // hash values must not be specified in direct order
223         typename test_table_type::accessor a;
224         bool b = x.insert(a,key);
225         CHECK(b);
226         a->second.set_value( i*i );
227     }
228 }
229 
230 template<typename test_table_type>
231 static void CheckTable( const test_table_type& x, int n ) {
232     REQUIRE_MESSAGE( x.size()==size_t(n), "table is different size than expected" );
233     CHECK(x.empty()==(n==0));
234     CHECK(x.size()<=x.max_size());
235     for( int i=1; i<=n; ++i ) {
236         MyKey key( MyKey::make(-i) );
237         typename test_table_type::const_accessor a;
238         bool b = x.find(a,key);
239         CHECK(b);
240         CHECK(a->second.value_of()==i*i);
241     }
242     int count = 0;
243     int key_sum = 0;
244     for( typename test_table_type::const_iterator i(x.begin()); i!=x.end(); ++i ) {
245         ++count;
246         key_sum += -i->first.value_of();
247     }
248     CHECK(count==n);
249     CHECK(key_sum==n*(n+1)/2);
250 }
251 
252 void TestCopy() {
253     INFO("testing copy\n");
254     test_table_type t1;
255     for( int i=0; i<10000; i=(i<100 ? i+1 : i*3) ) {
256         MyDataCount = 0;
257 
258         FillTable(t1,i);
259         // Do not call CheckTable(t1,i) before copying, it enforces rehashing
260 
261         test_table_type t2(t1);
262         // Check that copy constructor did not mangle source table.
263         CheckTable(t1,i);
264         swap(t1, t2);
265         CheckTable(t1,i);
266         CHECK(!(t1 != t2));
267 
268         // Clear original table
269         t2.clear();
270         swap(t2, t1);
271         CheckTable(t1,0);
272 
273         // Verify that copy of t1 is correct, even after t1 is cleared.
274         CheckTable(t2,i);
275         t2.clear();
276         t1.swap( t2 );
277         CheckTable(t1,0);
278         CheckTable(t2,0);
279         REQUIRE_MESSAGE( MyDataCount==0, "data leak?" );
280     }
281 }
282 
283 void TestRehash() {
284     INFO("testing rehashing\n");
285     test_table_type w;
286     w.insert( std::make_pair(MyKey::make(-5), MyData()) );
287     w.rehash(); // without this, assertion will fail
288     test_table_type::iterator it = w.begin();
289     int i = 0; // check for non-rehashed buckets
290     for( ; it != w.end(); i++ )
291         w.count( (it++)->first );
292     CHECK(i == 1);
293     for( i=0; i<1000; i=(i<29 ? i+1 : i*2) ) {
294         for( int j=std::max(256+i, i*2); j<10000; j*=3 ) {
295             test_table_type v;
296             FillTable( v, i );
297             CHECK(int(v.size()) == i);
298             CHECK(int(v.bucket_count()) <= j);
299             v.rehash( j );
300             CHECK(int(v.bucket_count()) >= j);
301             CheckTable( v, i );
302         }
303     }
304 }
305 
306 void TestAssignment() {
307     INFO("testing assignment\n");
308     tbb::concurrent_hash_map<int, int> test_map({{1, 2}, {2, 4}});
309     test_map.operator=(test_map); // suppress self assign warning
310     CHECK(!test_map.empty());
311 
312     for( int i=0; i<1000; i=(i<30 ? i+1 : i*5) ) {
313         for( int j=0; j<1000; j=(j<30 ? j+1 : j*7) ) {
314             test_table_type t1;
315             test_table_type t2;
316             FillTable(t1,i);
317             FillTable(t2,j);
318             CHECK((t1 == t2) == (i == j));
319             CheckTable(t2,j);
320 
321             test_table_type& tref = t2=t1;
322             CHECK(&tref==&t2);
323             CHECK(t1 == t2);
324             CheckTable(t1,i);
325             CheckTable(t2,i);
326 
327             t1.clear();
328             CheckTable(t1,0);
329             CheckTable(t2,i);
330             REQUIRE_MESSAGE( MyDataCount==i, "data leak?" );
331 
332             t2.clear();
333             CheckTable(t1,0);
334             CheckTable(t2,0);
335             REQUIRE_MESSAGE( MyDataCount==0, "data leak?" );
336         }
337     }
338 }
339 
340 template<typename Iterator, typename T>
341 void TestIteratorTraits() {
342     T x;
343     typename Iterator::reference xr = x;
344     typename Iterator::pointer xp = &x;
345     CHECK(&xr==xp);
346 }
347 
348 template<typename Iterator1, typename Iterator2>
349 void TestIteratorAssignment( Iterator2 j ) {
350     Iterator1 i(j), k;
351     CHECK(i==j);
352     CHECK(!(i!=j));
353     k = j;
354     CHECK(k==j);
355     CHECK(!(k!=j));
356 }
357 
358 template<typename Range1, typename Range2>
359 void TestRangeAssignment( Range2 r2 ) {
360     Range1 r1(r2); r1 = r2;
361 }
362 
363 void TestIteratorsAndRanges() {
364     INFO("testing iterators compliance\n");
365     TestIteratorTraits<test_table_type::iterator,test_table_type::value_type>();
366     TestIteratorTraits<test_table_type::const_iterator,const test_table_type::value_type>();
367 
368     test_table_type v;
369     CHECK(v.range().grainsize() == 1);
370     test_table_type const &u = v;
371 
372     TestIteratorAssignment<test_table_type::const_iterator>( u.begin() );
373     TestIteratorAssignment<test_table_type::const_iterator>( v.begin() );
374     TestIteratorAssignment<test_table_type::iterator>( v.begin() );
375     // doesn't compile as expected: TestIteratorAssignment<typename V::iterator>( u.begin() );
376 
377     // check for non-existing
378     CHECK(v.equal_range(MyKey::make(-1)) == std::make_pair(v.end(), v.end()));
379     CHECK(u.equal_range(MyKey::make(-1)) == std::make_pair(u.end(), u.end()));
380 
381     INFO("testing ranges compliance\n");
382     TestRangeAssignment<test_table_type::const_range_type>( u.range() );
383     TestRangeAssignment<test_table_type::range_type>( v.range() );
384     // doesn't compile as expected: TestRangeAssignment<typename V::range_type>( u.range() );
385 
386     INFO("testing construction and insertion from iterators range\n");
387     FillTable( v, 1000 );
388     other_test_table_type t(v.begin(), v.end());
389     v.rehash();
390     CheckTable(t, 1000);
391     t.insert(v.begin(), v.end()); // do nothing
392     CheckTable(t, 1000);
393     t.clear();
394     t.insert(v.begin(), v.end()); // restore
395     CheckTable(t, 1000);
396 
397     INFO("testing comparison\n");
398     using test_allocator_type2 = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData2>>>;
399     using YourTable1 = tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare, test_allocator_type2>;
400     using YourTable2 = tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare>;
401     YourTable1 t1;
402     FillTable( t1, 10 );
403     CheckTable(t1, 10 );
404     YourTable2 t2(t1.begin(), t1.end());
405     MyKey key( MyKey::make(-5) ); MyData2 data;
406     CHECK(t2.erase(key));
407     YourTable2::accessor a;
408     CHECK(t2.insert(a, key));
409     data.set_value(0);   a->second = data;
410     CHECK(t1 != t2);
411     data.set_value(5*5); a->second = data;
412     CHECK(t1 == t2);
413 }
414 
415 struct test_insert {
416     template<typename container_type, typename element_type>
417     static void test( std::initializer_list<element_type> il, container_type const& expected ) {
418         container_type vd;
419         vd.insert( il );
420         REQUIRE_MESSAGE( vd == expected, "inserting with an initializer list failed" );
421     }
422 };
423 
424 void TestInitList(){
425     using namespace initializer_list_support_tests;
426     INFO("testing initializer_list methods \n");
427 
428     using ch_map_type = tbb::concurrent_hash_map<int,int>;
429     std::initializer_list<ch_map_type::value_type> pairs_il = {{1,1},{2,2},{3,3},{4,4},{5,5}};
430 
431     test_initializer_list_support_without_assign<ch_map_type, test_insert>( pairs_il );
432     test_initializer_list_support_without_assign<ch_map_type, test_insert>( {} );
433 }
434 
435 template <typename base_alloc_type>
436 class only_node_counting_allocator : public StaticSharedCountingAllocator<base_alloc_type> {
437     using base_type = StaticSharedCountingAllocator<base_alloc_type>;
438     using base_traits = tbb::detail::allocator_traits<base_alloc_type>;
439 public:
440     template<typename U>
441     struct rebind {
442         using other = only_node_counting_allocator<typename base_traits::template rebind_alloc<U>>;
443     };
444 
445     only_node_counting_allocator() : base_type() {}
446     only_node_counting_allocator(const only_node_counting_allocator& a) : base_type(a) {}
447 
448     template<typename U>
449     only_node_counting_allocator(const only_node_counting_allocator<U>& a) : base_type(a) {}
450 
451     typename base_type::value_type* allocate(const std::size_t n) {
452         if ( n > 1) {
453             return base_alloc_type::allocate(n);
454         } else {
455             return base_type::allocate(n);
456         }
457     }
458 };
459 
460 #if TBB_USE_EXCEPTIONS
461 void TestExceptions() {
462     using allocator_type = only_node_counting_allocator<tbb::tbb_allocator<std::pair<const MyKey, MyData2>>>;
463     using throwing_table = tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare, allocator_type>;
464     enum methods {
465         zero_method = 0,
466         ctor_copy, op_assign, op_insert,
467         all_methods
468     };
469 
470     INFO("testing exception-safety guarantees\n");
471     throwing_table src;
472     FillTable( src, 1000 );
473     CHECK(MyDataCount==1000);
474 
475     try {
476         for(int t = 0; t < 2; t++) // exception type
477         for(int m = zero_method+1; m < all_methods; m++)
478         {
479             allocator_type a;
480             allocator_type::init_counters();
481             if(t) MyDataCountLimit = 101;
482             else a.set_limits(101);
483             throwing_table victim(a);
484             MyDataCount = 0;
485 
486             try {
487                 switch(m) {
488                 case ctor_copy: {
489                         throwing_table acopy(src, a);
490                     } break;
491                 case op_assign: {
492                         victim = src;
493                     } break;
494                 case op_insert: {
495                         // Insertion in cpp11 don't make copy constructions
496                         // during the insertion, so we need to decrement limit
497                         // to throw an exception in the right place and to prevent
498                         // successful insertion of one unexpected item
499                         if (MyDataCountLimit)
500                             --MyDataCountLimit;
501                         FillTable( victim, 1000 );
502                     } break;
503                 default:;
504                 }
505                 REQUIRE_MESSAGE(false, "should throw an exception");
506             } catch(std::bad_alloc &e) {
507                 MyDataCountLimit = 0;
508                 size_t size = victim.size();
509                 switch(m) {
510                 case op_assign:
511                     REQUIRE_MESSAGE( MyDataCount==100, "data leak?" );
512                     CHECK(size>=100);
513                     utils_fallthrough;
514                 case ctor_copy:
515                     CheckTable(src, 1000);
516                     break;
517                 case op_insert:
518                     CHECK(size==size_t(100-t));
519                     REQUIRE_MESSAGE( MyDataCount==100-t, "data leak?" );
520                     CheckTable(victim, 100-t);
521                     break;
522 
523                 default:; // nothing to check here
524                 }
525                 INFO("Exception "<< m << " : " << e.what() << "- ok ()");
526             }
527             catch ( ... ) {
528                 REQUIRE_MESSAGE( false, "Unrecognized exception" );
529             }
530         }
531     } catch(...) {
532         REQUIRE_MESSAGE(false, "unexpected exception");
533     }
534     src.clear(); MyDataCount = 0;
535     allocator_type::max_items = 0;
536 }
537 #endif
538 
539 struct default_container_traits {
540     template <typename container_type, typename iterator_type>
541     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end){
542         container_type* ptr = reinterpret_cast<container_type*>(&storage);
543         new (ptr) container_type(begin, end);
544         return *ptr;
545     }
546 
547     template <typename container_type, typename iterator_type, typename allocator_type>
548     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end, allocator_type const& a){
549         container_type* ptr = reinterpret_cast<container_type*>(&storage);
550         new (ptr) container_type(begin, end, a);
551         return *ptr;
552     }
553 };
554 
555 struct hash_map_traits : default_container_traits {
556     enum{ expected_number_of_items_to_allocate_for_steal_move = 0 };
557 
558     template<typename T>
559     struct hash_compare {
560         bool equal( const T& lhs, const T& rhs ) const {
561             return lhs==rhs;
562         }
563         size_t hash( const T& k ) const {
564             return my_hash_func(k);
565         }
566         std::hash<T> my_hash_func;
567     };
568 
569     template <typename T, typename Allocator>
570     using container_type = tbb::concurrent_hash_map<T, T, hash_compare<T>, Allocator>;
571 
572     template <typename T>
573     using container_value_type = std::pair<const T, T>;
574 
575     template<typename element_type, typename allocator_type>
576     struct apply {
577         using type = tbb::concurrent_hash_map<element_type, element_type, hash_compare<element_type>, allocator_type>;
578     };
579 
580     using init_iterator_type = move_support_tests::FooPairIterator;
581     template <typename hash_map_type, typename iterator>
582     static bool equal(hash_map_type const& c, iterator begin, iterator end){
583         bool equal_sizes = ( static_cast<size_t>(std::distance(begin, end)) == c.size() );
584         if (!equal_sizes)
585             return false;
586 
587         for (iterator it = begin; it != end; ++it ){
588             if (c.count( (*it).first) == 0){
589                 return false;
590             }
591         }
592         return true;
593     }
594 };
595 
596 using DataStateTrackedTable = tbb::concurrent_hash_map<MyKey, move_support_tests::Foo, MyHashCompare>;
597 
598 struct RvalueInsert {
599     static void apply( DataStateTrackedTable& table, int i ) {
600         DataStateTrackedTable::accessor a;
601         int next = i + 1;
602         REQUIRE_MESSAGE((table.insert( a, std::make_pair(MyKey::make(i), move_support_tests::Foo(next)))),
603             "already present while should not ?" );
604         CHECK((*a).second == next);
605         CHECK((*a).second.state == StateTrackableBase::MoveInitialized);
606     }
607 };
608 
609 struct Emplace {
610     template <typename Accessor>
611     static void apply_impl( DataStateTrackedTable& table, int i) {
612         Accessor a;
613         REQUIRE_MESSAGE((table.emplace( a, MyKey::make(i), (i + 1))),
614                 "already present while should not ?" );
615         CHECK((*a).second == i + 1);
616         CHECK((*a).second.state == StateTrackableBase::DirectInitialized);
617     }
618 
619     static void apply( DataStateTrackedTable& table, int i ) {
620         // TODO: investigate ability to rewrite apply methods with use apply_imp method.
621         if (i % 2) {
622             apply_impl<DataStateTrackedTable::accessor>(table, i);
623         } else {
624             apply_impl<DataStateTrackedTable::const_accessor>(table, i);
625         }
626     }
627 };
628 
629 template<typename Op, typename test_table_type>
630 class TableOperation {
631     test_table_type& my_table;
632 public:
633     void operator()( const tbb::blocked_range<int>& range ) const {
634         for( int i=range.begin(); i!=range.end(); ++i )
635             Op::apply(my_table,i);
636     }
637     TableOperation( test_table_type& table ) : my_table(table) {}
638 };
639 
640 bool UseKey( size_t i ) {
641     return (i&3)!=3;
642 }
643 
644 struct Insert {
645     static void apply( test_table_type& table, int i ) {
646         if( UseKey(i) ) {
647             if( i&4 ) {
648                 test_table_type::accessor a;
649                 table.insert( a, MyKey::make(i) );
650                 if( i&1 )
651                     (*a).second.set_value(i*i);
652                 else
653                     a->second.set_value(i*i);
654             } else
655                 if( i&1 ) {
656                     test_table_type::accessor a;
657                     table.insert( a, std::make_pair(MyKey::make(i), MyData(i*i)) );
658                     CHECK((*a).second.value_of()==i*i);
659                 } else {
660                     test_table_type::const_accessor ca;
661                     table.insert( ca, std::make_pair(MyKey::make(i), MyData(i*i)) );
662                     CHECK(ca->second.value_of()==i*i);
663                 }
664         }
665     }
666 };
667 
668 struct Find {
669     static void apply( test_table_type& table, int i ) {
670         test_table_type::accessor a;
671         const test_table_type::accessor& ca = a;
672         bool b = table.find( a, MyKey::make(i) );
673         CHECK(b==!a.empty());
674         if( b ) {
675             if( !UseKey(i) )
676                 REPORT("Line %d: unexpected key %d present\n",__LINE__,i);
677             CHECK(ca->second.value_of()==i*i);
678             CHECK((*ca).second.value_of()==i*i);
679             if( i&1 )
680                 ca->second.set_value( ~ca->second.value_of() );
681             else
682                 (*ca).second.set_value( ~ca->second.value_of() );
683         } else {
684             if( UseKey(i) )
685                 REPORT("Line %d: key %d missing\n",__LINE__,i);
686         }
687     }
688 };
689 
690 struct FindConst {
691     static void apply( const test_table_type& table, int i ) {
692         test_table_type::const_accessor a;
693         const test_table_type::const_accessor& ca = a;
694         bool b = table.find( a, MyKey::make(i) );
695         CHECK(b==(table.count(MyKey::make(i))>0));
696         CHECK(b==!a.empty());
697         CHECK(b==UseKey(i));
698         if( b ) {
699             CHECK(ca->second.value_of()==~(i*i));
700             CHECK((*ca).second.value_of()==~(i*i));
701         }
702     }
703 };
704 
705 struct InsertInitList {
706     static void apply( test_table_type& table, int i ) {
707         if ( UseKey( i ) ) {
708             // TODO: investigate why the following sequence causes an additional allocation sometimes:
709             // table.insert( test_table_type::value_type( MyKey::make( i ), i*i ) );
710             // table.insert( test_table_type::value_type( MyKey::make( i ), i*i+1 ) );
711             std::initializer_list<test_table_type::value_type> il = {
712                 test_table_type::value_type( MyKey::make( i ), i*i )
713                 /*, test_table_type::value_type( MyKey::make( i ), i*i+1 ) */
714                                                                     };
715             table.insert( il );
716         }
717     }
718 };
719 
720 template<typename Op, typename TableType>
721 void DoConcurrentOperations( TableType& table, int n, const char* what, std::size_t nthread ) {
722     INFO("testing " << what << " with " << nthread << " threads");
723     tbb::parallel_for(tbb::blocked_range<int>(0, n ,100), TableOperation<Op, TableType>(table));
724 }
725 
726 //! Test traversing the table with an iterator.
727 void TraverseTable( test_table_type& table, size_t n, size_t expected_size ) {
728     INFO("testing traversal\n");
729     size_t actual_size = table.size();
730     CHECK(actual_size==expected_size);
731     size_t count = 0;
732     bool* array = new bool[n];
733     memset( array, 0, n*sizeof(bool) );
734     const test_table_type& const_table = table;
735     test_table_type::const_iterator ci = const_table.begin();
736     for( test_table_type::iterator i = table.begin(); i!=table.end(); ++i ) {
737         // Check iterator
738         int k = i->first.value_of();
739         CHECK(UseKey(k));
740         CHECK((*i).first.value_of()==k);
741         REQUIRE_MESSAGE((0<=k && size_t(k)<n), "out of bounds key" );
742         REQUIRE_MESSAGE( !array[k], "duplicate key" );
743         array[k] = true;
744         ++count;
745 
746         // Check lower/upper bounds
747         std::pair<test_table_type::iterator, test_table_type::iterator> er = table.equal_range(i->first);
748         std::pair<test_table_type::const_iterator, test_table_type::const_iterator> cer = const_table.equal_range(i->first);
749         CHECK((cer.first == er.first && cer.second == er.second));
750         CHECK(cer.first == i);
751         CHECK(std::distance(cer.first, cer.second) == 1);
752 
753         // Check const_iterator
754         test_table_type::const_iterator cic = ci++;
755         CHECK(cic->first.value_of()==k);
756         CHECK((*cic).first.value_of()==k);
757     }
758     CHECK(ci==const_table.end());
759     delete[] array;
760     if (count != expected_size) {
761         INFO("Line " << __LINE__ << ": count=" << count << " but should be " << expected_size);
762     }
763 }
764 
765 std::atomic<int> EraseCount;
766 
767 struct Erase {
768     static void apply( test_table_type& table, int i ) {
769         bool b;
770         if(i&4) {
771             if(i&8) {
772                 test_table_type::const_accessor a;
773                 b = table.find( a, MyKey::make(i) ) && table.erase( a );
774             } else {
775                 test_table_type::accessor a;
776                 b = table.find( a, MyKey::make(i) ) && table.erase( a );
777             }
778         } else
779             b = table.erase( MyKey::make(i) );
780         if( b ) ++EraseCount;
781         CHECK(table.count(MyKey::make(i)) == 0);
782     }
783 };
784 
785 using YourTable = tbb::concurrent_hash_map<MyKey, MyData, YourHashCompare>;
786 static const int IE_SIZE = 2;
787 std::atomic<YourTable::size_type> InsertEraseCount[IE_SIZE];
788 
789 struct InsertErase  {
790     static void apply( YourTable& table, int i ) {
791         if ( i%3 ) {
792             int key = i%IE_SIZE;
793             if ( table.insert( std::make_pair(MyKey::make(key), MyData2()) ) )
794                 ++InsertEraseCount[key];
795         } else {
796             int key = i%IE_SIZE;
797             if( i&1 ) {
798                 YourTable::accessor res;
799                 if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
800                     --InsertEraseCount[key];
801             } else {
802                 YourTable::const_accessor res;
803                 if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
804                     --InsertEraseCount[key];
805             }
806         }
807     }
808 };
809 
810 struct InnerInsert {
811     static void apply( YourTable& table, int i ) {
812         YourTable::accessor a1, a2;
813         if(i&1) std::this_thread::yield();
814         table.insert( a1, MyKey::make(1) );
815         std::this_thread::yield();
816         table.insert( a2, MyKey::make(1 + (1<<30)) ); // the same chain
817         table.erase( a2 ); // if erase by key it would lead to deadlock for single thread
818     }
819 };
820 
821 struct FakeExclusive {
822     utils::SpinBarrier& barrier;
823     YourTable& table;
824     FakeExclusive(utils::SpinBarrier& b, YourTable&t) : barrier(b), table(t) {}
825     void operator()( std::size_t i ) const {
826         if(i) {
827             YourTable::const_accessor real_ca;
828             // const accessor on non-const table acquired as reader (shared)
829             CHECK(table.find(real_ca,MyKey::make(1)));
830             barrier.wait(); // item can be erased
831             std::this_thread::sleep_for(std::chrono::milliseconds(10)); // let it enter the erase
832             real_ca->second.value_of(); // check the state while holding accessor
833         } else {
834             YourTable::accessor fake_ca;
835             const YourTable &const_table = table;
836             // non-const accessor on const table acquired as reader (shared)
837             CHECK(const_table.find(fake_ca,MyKey::make(1)));
838             barrier.wait(); // readers acquired
839             // can mistakenly remove the item while other readers still refers to it
840             table.erase( fake_ca );
841         }
842     }
843 };
844 
845 using AtomicByte = std::atomic<unsigned char>;
846 
847 template<typename RangeType>
848 struct ParallelTraverseBody {
849     const size_t n;
850     AtomicByte* const array;
851     ParallelTraverseBody( AtomicByte array_[], size_t n_ ) :
852         n(n_),
853         array(array_)
854     {}
855     void operator()( const RangeType& range ) const {
856         for( typename RangeType::iterator i = range.begin(); i!=range.end(); ++i ) {
857             int k = i->first.value_of();
858             CHECK((0<=k && size_t(k)<n));
859             ++array[k];
860         }
861     }
862 };
863 
864 void Check( AtomicByte array[], size_t n, size_t expected_size ) {
865     if( expected_size )
866         for( size_t k=0; k<n; ++k ) {
867             if( array[k] != int(UseKey(k)) ) {
868                 REPORT("array[%d]=%d != %d=UseKey(%d)\n",
869                        int(k), int(array[k]), int(UseKey(k)), int(k));
870                 CHECK(false);
871             }
872         }
873 }
874 
875 //! Test traversing the table with a parallel range
876 void ParallelTraverseTable( test_table_type& table, size_t n, size_t expected_size ) {
877     INFO("testing parallel traversal\n");
878     CHECK(table.size()==expected_size);
879     AtomicByte* array = new AtomicByte[n];
880 
881     memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) );
882     test_table_type::range_type r = table.range(10);
883     tbb::parallel_for( r, ParallelTraverseBody<test_table_type::range_type>( array, n ));
884     Check( array, n, expected_size );
885 
886     const test_table_type& const_table = table;
887     memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) );
888     test_table_type::const_range_type cr = const_table.range(10);
889     tbb::parallel_for( cr, ParallelTraverseBody<test_table_type::const_range_type>( array, n ));
890     Check( array, n, expected_size );
891 
892     delete[] array;
893 }
894 
895 void TestInsertFindErase( std::size_t nthread ) {
896     int n=250000;
897 
898     // compute m = number of unique keys
899     int m = 0;
900     for( int i=0; i<n; ++i )
901         m += UseKey(i);
902     {
903         test_allocator_type alloc;
904         test_allocator_type::init_counters();
905         CHECK(MyDataCount==0);
906         test_table_type table(alloc);
907         TraverseTable(table,n,0);
908         ParallelTraverseTable(table,n,0);
909 
910         int expected_allocs = 0, expected_frees = 100;
911         for ( int i = 0; i < 2; ++i ) {
912             if ( i==0 )
913                 DoConcurrentOperations<InsertInitList, test_table_type>( table, n, "insert(std::initializer_list)", nthread );
914             else
915                 DoConcurrentOperations<Insert, test_table_type>( table, n, "insert", nthread );
916             CHECK(MyDataCount == m);
917             TraverseTable( table, n, m );
918             ParallelTraverseTable( table, n, m );
919             expected_allocs += m;
920 
921             DoConcurrentOperations<Find, test_table_type>( table, n, "find", nthread );
922             CHECK(MyDataCount == m);
923 
924             DoConcurrentOperations<FindConst, test_table_type>( table, n, "find(const)", nthread );
925             CHECK(MyDataCount == m);
926 
927             EraseCount = 0;
928             DoConcurrentOperations<Erase, test_table_type>( table, n, "erase", nthread );
929             CHECK(EraseCount == m);
930             CHECK(MyDataCount == 0);
931             TraverseTable( table, n, 0 );
932             expected_frees += m;
933 
934             table.clear();
935         }
936 
937         if( nthread > 1 ) {
938             YourTable ie_table;
939             for( int i=0; i<IE_SIZE; ++i )
940                 InsertEraseCount[i] = 0;
941             DoConcurrentOperations<InsertErase,YourTable>(ie_table,n/2,"insert_erase",nthread);
942             for( int i=0; i<IE_SIZE; ++i )
943                 CHECK(InsertEraseCount[i]==ie_table.count(MyKey::make(i)));
944 
945             DoConcurrentOperations<InnerInsert, YourTable>(ie_table,2000,"inner insert",nthread);
946             utils::SpinBarrier barrier(nthread);
947             INFO("testing erase on fake exclusive accessor\n");
948             utils::NativeParallelFor( nthread, FakeExclusive(barrier, ie_table));
949         }
950     }
951     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
952     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
953     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
954 }
955 
956 std::atomic<int> Counter;
957 
958 class AddToTable {
959     test_table_type& my_table;
960     const std::size_t my_nthread;
961     const int my_m;
962 public:
963     AddToTable( test_table_type& table, std::size_t nthread, int m ) : my_table(table), my_nthread(nthread), my_m(m) {}
964     void operator()( std::size_t ) const {
965         for( int i=0; i<my_m; ++i ) {
966             // Busy wait to synchronize threads
967             int j = 0;
968             while( Counter<i ) {
969                 if( ++j==1000000 ) {
970                     // If Counter<i after a million iterations, then we almost surely have
971                     // more logical threads than physical threads, and should yield in
972                     // order to let suspended logical threads make progress.
973                     j = 0;
974                     std::this_thread::yield();
975                 }
976             }
977             // Now all threads attempt to simultaneously insert a key.
978             int k;
979             {
980                 test_table_type::accessor a;
981                 MyKey key = MyKey::make(i);
982                 if( my_table.insert( a, key ) )
983                     a->second.set_value( 1 );
984                 else
985                     a->second.set_value( a->second.value_of()+1 );
986                 k = a->second.value_of();
987             }
988             if( std::size_t(k) == my_nthread )
989                 Counter=i+1;
990         }
991     }
992 };
993 
994 class RemoveFromTable {
995     test_table_type& my_table;
996     const int my_m;
997 public:
998     RemoveFromTable( test_table_type& table, int m ) : my_table(table), my_m(m) {}
999     void operator()(std::size_t) const {
1000         for( int i=0; i<my_m; ++i ) {
1001             bool b;
1002             if(i&4) {
1003                 if(i&8) {
1004                     test_table_type::const_accessor a;
1005                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1006                 } else {
1007                     test_table_type::accessor a;
1008                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1009                 }
1010             } else
1011                 b = my_table.erase( MyKey::make(i) );
1012             if( b ) ++EraseCount;
1013         }
1014     }
1015 };
1016 
1017 void TestConcurrency( std::size_t nthread ) {
1018     INFO("testing multiple insertions/deletions of same key with " << nthread << " threads");
1019     test_allocator_type::init_counters();
1020     {
1021         CHECK( MyDataCount==0);
1022         test_table_type table;
1023         const int m = 1000;
1024         Counter = 0;
1025         tbb::tick_count t0 = tbb::tick_count::now();
1026         utils::NativeParallelFor( nthread, AddToTable(table,nthread,m) );
1027         REQUIRE_MESSAGE( MyDataCount==m, "memory leak detected" );
1028 
1029         EraseCount = 0;
1030         t0 = tbb::tick_count::now();
1031         utils::NativeParallelFor( nthread, RemoveFromTable(table,m) );
1032         REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1033         REQUIRE_MESSAGE(EraseCount==m, "return value of erase() is broken");
1034 
1035     }
1036     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
1037     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
1038     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
1039     REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1040 }
1041 
1042 template<typename Key>
1043 struct non_default_constructible_hash_compare : tbb::detail::d1::tbb_hash_compare<Key> {
1044     non_default_constructible_hash_compare() {
1045         REQUIRE_MESSAGE(false, "Hash compare object must not default construct during the construction of hash_map with compare argument");
1046     }
1047 
1048     non_default_constructible_hash_compare(int) {}
1049 };
1050 
1051 void TestHashCompareConstructors() {
1052     using key_type = int;
1053     using map_type = tbb::concurrent_hash_map<key_type, key_type, non_default_constructible_hash_compare<key_type>>;
1054 
1055     non_default_constructible_hash_compare<key_type> compare(0);
1056     map_type::allocator_type allocator;
1057 
1058     map_type map1(compare);
1059     map_type map2(compare, allocator);
1060 
1061     map_type map3(1, compare);
1062     map_type map4(1, compare, allocator);
1063 
1064     std::vector<map_type::value_type> reference_vector;
1065     map_type map5(reference_vector.begin(), reference_vector.end(), compare);
1066     map_type map6(reference_vector.begin(), reference_vector.end(), compare, allocator);
1067 
1068     map_type map7({}, compare);
1069     map_type map8({}, compare, allocator);
1070 }
1071 
1072 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1073 template <typename T>
1074 struct debug_hash_compare : public tbb::detail::d1::tbb_hash_compare<T> {};
1075 
1076 template <template <typename...> typename TMap>
1077 void TestDeductionGuides() {
1078     using Key = int;
1079     using Value = std::string;
1080 
1081     using ComplexType = std::pair<Key, Value>;
1082     using ComplexTypeConst = std::pair<const Key, Value>;
1083 
1084     using DefaultCompare = tbb::detail::d1::tbb_hash_compare<Key>;
1085     using Compare = debug_hash_compare<Key>;
1086     using DefaultAllocator = tbb::tbb_allocator<ComplexTypeConst>;
1087     using Allocator = std::allocator<ComplexTypeConst>;
1088 
1089     std::vector<ComplexType> v;
1090     auto l = { ComplexTypeConst(1, "one"), ComplexTypeConst(2, "two") };
1091     Compare compare;
1092     Allocator allocator;
1093 
1094     // check TMap(InputIterator, InputIterator)
1095     TMap m1(v.begin(), v.end());
1096     static_assert(std::is_same<decltype(m1), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1097 
1098     // check TMap(InputIterator, InputIterator, HashCompare)
1099     TMap m2(v.begin(), v.end(), compare);
1100     static_assert(std::is_same<decltype(m2), TMap<Key, Value, Compare>>::value);
1101 
1102     // check TMap(InputIterator, InputIterator, HashCompare, Allocator)
1103     TMap m3(v.begin(), v.end(), compare, allocator);
1104     static_assert(std::is_same<decltype(m3), TMap<Key, Value, Compare, Allocator>>::value);
1105 
1106     // check TMap(InputIterator, InputIterator, Allocator)
1107     TMap m4(v.begin(), v.end(), allocator);
1108     static_assert(std::is_same<decltype(m4), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1109 
1110     // check TMap(std::initializer_list)
1111     TMap m5(l);
1112     static_assert(std::is_same<decltype(m5), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1113 
1114     // check TMap(std::initializer_list, HashCompare)
1115     TMap m6(l, compare);
1116     static_assert(std::is_same<decltype(m6), TMap<Key, Value, Compare, DefaultAllocator>>::value);
1117 
1118     // check TMap(std::initializer_list, HashCompare, Allocator)
1119     TMap m7(l, compare, allocator);
1120     static_assert(std::is_same<decltype(m7), TMap<Key, Value, Compare, Allocator>>::value);
1121 
1122     // check TMap(std::initializer_list, Allocator)
1123     TMap m8(l, allocator);
1124     static_assert(std::is_same<decltype(m8), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1125 
1126     // check TMap(TMap &)
1127     TMap m9(m1);
1128     static_assert(std::is_same<decltype(m9), decltype(m1)>::value);
1129 
1130     // check TMap(TMap &, Allocator)
1131     TMap m10(m4, allocator);
1132     static_assert(std::is_same<decltype(m10), decltype(m4)>::value);
1133 
1134     // check TMap(TMap &&)
1135     TMap m11(std::move(m1));
1136     static_assert(std::is_same<decltype(m11), decltype(m1)>::value);
1137 
1138     // check TMap(TMap &&, Allocator)
1139     TMap m12(std::move(m4), allocator);
1140     static_assert(std::is_same<decltype(m12), decltype(m4)>::value);
1141 }
1142 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1143 
1144 //! Test consruction with hash_compare
1145 //! \brief \ref interface \ref requirement
1146 TEST_CASE("testing consruction with hash_compare") {
1147     TestHashCompareConstructors();
1148 }
1149 
1150 //! Test concurrent_hash_map member types
1151 //! \brief \ref interface \ref requirement
1152 TEST_CASE("test types"){
1153     test_member_types<tbb::concurrent_hash_map>();
1154 }
1155 
1156 //! Test swap and clear operations
1157 //! \brief \ref interface \ref requirement
1158 TEST_CASE("test copy operations") {
1159     TestCopy();
1160 }
1161 
1162 //! Test rehash operation
1163 //! \brief \ref interface \ref requirement
1164 TEST_CASE("test rehash operation") {
1165     TestRehash();
1166 }
1167 
1168 //! Test assignment operation
1169 //! \brief \ref interface \ref requirement
1170 TEST_CASE("test assignment operation") {
1171     TestAssignment();
1172 }
1173 
1174 //! Test iterators and ranges
1175 //! \brief \ref interface \ref requirement
1176 TEST_CASE("test iterators and ranges") {
1177     TestIteratorsAndRanges();
1178 }
1179 
1180 //! Test work with initializer_list
1181 //! \brief \ref interface \ref requirement
1182 TEST_CASE("test work with initializer_list") {
1183     TestInitList();
1184 }
1185 
1186 #if TBB_USE_EXCEPTIONS
1187 //! Test exception safety
1188 //! \brief \ref requirement
1189 TEST_CASE("test exception safety") {
1190     TestExceptions();
1191 }
1192 
1193 //! Test exceptions safety guarantees for move constructor
1194 //! \brief \ref requirement
1195 TEST_CASE("test move support with exceptions") {
1196     move_support_tests::test_ex_move_ctor_unequal_allocator_memory_failure<hash_map_traits>();
1197     move_support_tests::test_ex_move_ctor_unequal_allocator_element_ctor_failure<hash_map_traits>();
1198 }
1199 #endif
1200 
1201 //! Test move constructor
1202 //! \brief \ref interface \ref requirement
1203 TEST_CASE("testing move constructor"){
1204     move_support_tests::test_move_constructor<hash_map_traits>();
1205 }
1206 
1207 //! Test move assign operator
1208 //! \brief \ref interface \ref requirement
1209 TEST_CASE("testing move assign operator"){
1210     move_support_tests::test_move_assignment<hash_map_traits>();
1211 }
1212 
1213 //! Test insert and empace
1214 //! \brief \ref interface \ref requirement
1215 TEST_CASE("testing concurrency"){
1216     tbb::global_control limit(tbb::global_control::max_allowed_parallelism, 1);
1217     int n=250000;
1218     {
1219         DataStateTrackedTable table;
1220         DoConcurrentOperations<RvalueInsert, DataStateTrackedTable>( table, n, "rvalue ref insert", 1 );
1221     }
1222     {
1223         DataStateTrackedTable table;
1224         DoConcurrentOperations<Emplace, DataStateTrackedTable>( table, n, "emplace", 1 );
1225     }
1226 }
1227 
1228 //! Test allocator traits
1229 //! \brief \ref requirement
1230 TEST_CASE("testing allocator traits") {
1231     test_allocator_traits_support<hash_map_traits>();
1232 }
1233 
1234 //! Test concurrent operations
1235 //! \brief \ref requirement
1236 TEST_CASE("testing concurrency"){
1237     for (std::size_t p = 1; p <= 4; ++p) {
1238         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
1239         TestInsertFindErase(p);
1240         TestConcurrency(p);
1241     }
1242 }
1243 
1244 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1245 //! Test deduction guides
1246 //! \brief \ref interface
1247 TEST_CASE("testing deduction guides") {
1248     TestDeductionGuides<tbb::concurrent_hash_map>();
1249 }
1250 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1251