• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Abseil Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <cstddef>
16 #include <cstdint>
17 #include <functional>
18 #include <limits>
19 #include <memory>
20 #include <ostream>
21 #include <set>
22 #include <type_traits>
23 #include <utility>
24 
25 #include "gmock/gmock.h"
26 #include "gtest/gtest.h"
27 #include "absl/base/config.h"
28 #include "absl/container/internal/raw_hash_set.h"
29 #include "absl/container/internal/tracked.h"
30 
31 namespace absl {
32 ABSL_NAMESPACE_BEGIN
33 namespace container_internal {
34 namespace {
35 using ::testing::AnyOf;
36 
37 enum AllocSpec {
38   kPropagateOnCopy = 1,
39   kPropagateOnMove = 2,
40   kPropagateOnSwap = 4,
41 };
42 
43 struct AllocState {
44   size_t num_allocs = 0;
45   std::set<void*> owned;
46 };
47 
48 template <class T,
49           int Spec = kPropagateOnCopy | kPropagateOnMove | kPropagateOnSwap>
50 class CheckedAlloc {
51  public:
52   template <class, int>
53   friend class CheckedAlloc;
54 
55   using value_type = T;
56 
CheckedAlloc()57   CheckedAlloc() {}
CheckedAlloc(size_t id)58   explicit CheckedAlloc(size_t id) : id_(id) {}
59   CheckedAlloc(const CheckedAlloc&) = default;
60   CheckedAlloc& operator=(const CheckedAlloc&) = default;
61 
62   template <class U>
CheckedAlloc(const CheckedAlloc<U,Spec> & that)63   CheckedAlloc(const CheckedAlloc<U, Spec>& that)
64       : id_(that.id_), state_(that.state_) {}
65 
66   template <class U>
67   struct rebind {
68     using other = CheckedAlloc<U, Spec>;
69   };
70 
71   using propagate_on_container_copy_assignment =
72       std::integral_constant<bool, (Spec & kPropagateOnCopy) != 0>;
73 
74   using propagate_on_container_move_assignment =
75       std::integral_constant<bool, (Spec & kPropagateOnMove) != 0>;
76 
77   using propagate_on_container_swap =
78       std::integral_constant<bool, (Spec & kPropagateOnSwap) != 0>;
79 
select_on_container_copy_construction() const80   CheckedAlloc select_on_container_copy_construction() const {
81     if (Spec & kPropagateOnCopy) return *this;
82     return {};
83   }
84 
allocate(size_t n)85   T* allocate(size_t n) {
86     T* ptr = std::allocator<T>().allocate(n);
87     track_alloc(ptr);
88     return ptr;
89   }
deallocate(T * ptr,size_t n)90   void deallocate(T* ptr, size_t n) {
91     memset(ptr, 0, n * sizeof(T));  // The freed memory must be unpoisoned.
92     track_dealloc(ptr);
93     return std::allocator<T>().deallocate(ptr, n);
94   }
95 
operator ==(const CheckedAlloc & a,const CheckedAlloc & b)96   friend bool operator==(const CheckedAlloc& a, const CheckedAlloc& b) {
97     return a.id_ == b.id_;
98   }
operator !=(const CheckedAlloc & a,const CheckedAlloc & b)99   friend bool operator!=(const CheckedAlloc& a, const CheckedAlloc& b) {
100     return !(a == b);
101   }
102 
num_allocs() const103   size_t num_allocs() const { return state_->num_allocs; }
104 
swap(CheckedAlloc & that)105   void swap(CheckedAlloc& that) {
106     using std::swap;
107     swap(id_, that.id_);
108     swap(state_, that.state_);
109   }
110 
swap(CheckedAlloc & a,CheckedAlloc & b)111   friend void swap(CheckedAlloc& a, CheckedAlloc& b) { a.swap(b); }
112 
operator <<(std::ostream & o,const CheckedAlloc & a)113   friend std::ostream& operator<<(std::ostream& o, const CheckedAlloc& a) {
114     return o << "alloc(" << a.id_ << ")";
115   }
116 
117  private:
track_alloc(void * ptr)118   void track_alloc(void* ptr) {
119     AllocState* state = state_.get();
120     ++state->num_allocs;
121     if (!state->owned.insert(ptr).second)
122       ADD_FAILURE() << *this << " got previously allocated memory: " << ptr;
123   }
track_dealloc(void * ptr)124   void track_dealloc(void* ptr) {
125     if (state_->owned.erase(ptr) != 1)
126       ADD_FAILURE() << *this
127                     << " deleting memory owned by another allocator: " << ptr;
128   }
129 
130   size_t id_ = std::numeric_limits<size_t>::max();
131 
132   std::shared_ptr<AllocState> state_ = std::make_shared<AllocState>();
133 };
134 
135 struct Identity {
operator ()absl::container_internal::__anon7bf2266e0111::Identity136   int32_t operator()(int32_t v) const { return v; }
137 };
138 
139 struct Policy {
140   using slot_type = Tracked<int32_t>;
141   using init_type = Tracked<int32_t>;
142   using key_type = int32_t;
143 
144   template <class allocator_type, class... Args>
constructabsl::container_internal::__anon7bf2266e0111::Policy145   static void construct(allocator_type* alloc, slot_type* slot,
146                         Args&&... args) {
147     std::allocator_traits<allocator_type>::construct(
148         *alloc, slot, std::forward<Args>(args)...);
149   }
150 
151   template <class allocator_type>
destroyabsl::container_internal::__anon7bf2266e0111::Policy152   static void destroy(allocator_type* alloc, slot_type* slot) {
153     std::allocator_traits<allocator_type>::destroy(*alloc, slot);
154   }
155 
156   template <class allocator_type>
transferabsl::container_internal::__anon7bf2266e0111::Policy157   static void transfer(allocator_type* alloc, slot_type* new_slot,
158                        slot_type* old_slot) {
159     construct(alloc, new_slot, std::move(*old_slot));
160     destroy(alloc, old_slot);
161   }
162 
163   template <class F>
applyabsl::container_internal::__anon7bf2266e0111::Policy164   static auto apply(F&& f, int32_t v) -> decltype(std::forward<F>(f)(v, v)) {
165     return std::forward<F>(f)(v, v);
166   }
167 
168   template <class F>
applyabsl::container_internal::__anon7bf2266e0111::Policy169   static auto apply(F&& f, const slot_type& v)
170       -> decltype(std::forward<F>(f)(v.val(), v)) {
171     return std::forward<F>(f)(v.val(), v);
172   }
173 
174   template <class F>
applyabsl::container_internal::__anon7bf2266e0111::Policy175   static auto apply(F&& f, slot_type&& v)
176       -> decltype(std::forward<F>(f)(v.val(), std::move(v))) {
177     return std::forward<F>(f)(v.val(), std::move(v));
178   }
179 
elementabsl::container_internal::__anon7bf2266e0111::Policy180   static slot_type& element(slot_type* slot) { return *slot; }
181 };
182 
183 template <int Spec>
184 struct PropagateTest : public ::testing::Test {
185   using Alloc = CheckedAlloc<Tracked<int32_t>, Spec>;
186 
187   using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, Alloc>;
188 
PropagateTestabsl::container_internal::__anon7bf2266e0111::PropagateTest189   PropagateTest() {
190     EXPECT_EQ(a1, t1.get_allocator());
191     EXPECT_NE(a2, t1.get_allocator());
192   }
193 
194   Alloc a1 = Alloc(1);
195   Table t1 = Table(0, a1);
196   Alloc a2 = Alloc(2);
197 };
198 
199 using PropagateOnAll =
200     PropagateTest<kPropagateOnCopy | kPropagateOnMove | kPropagateOnSwap>;
201 using NoPropagateOnCopy = PropagateTest<kPropagateOnMove | kPropagateOnSwap>;
202 using NoPropagateOnMove = PropagateTest<kPropagateOnCopy | kPropagateOnSwap>;
203 
TEST_F(PropagateOnAll,Empty)204 TEST_F(PropagateOnAll, Empty) { EXPECT_EQ(0, a1.num_allocs()); }
205 
TEST_F(PropagateOnAll,InsertAllocates)206 TEST_F(PropagateOnAll, InsertAllocates) {
207   auto it = t1.insert(0).first;
208   EXPECT_EQ(1, a1.num_allocs());
209   EXPECT_EQ(0, it->num_moves());
210   EXPECT_EQ(0, it->num_copies());
211 }
212 
TEST_F(PropagateOnAll,InsertDecomposes)213 TEST_F(PropagateOnAll, InsertDecomposes) {
214   auto it = t1.insert(0).first;
215   EXPECT_EQ(1, a1.num_allocs());
216   EXPECT_EQ(0, it->num_moves());
217   EXPECT_EQ(0, it->num_copies());
218 
219   EXPECT_FALSE(t1.insert(0).second);
220   EXPECT_EQ(1, a1.num_allocs());
221   EXPECT_EQ(0, it->num_moves());
222   EXPECT_EQ(0, it->num_copies());
223 }
224 
TEST_F(PropagateOnAll,RehashMoves)225 TEST_F(PropagateOnAll, RehashMoves) {
226   auto it = t1.insert(0).first;
227   EXPECT_EQ(0, it->num_moves());
228   t1.rehash(2 * t1.capacity());
229   EXPECT_EQ(2, a1.num_allocs());
230   it = t1.find(0);
231   EXPECT_EQ(1, it->num_moves());
232   EXPECT_EQ(0, it->num_copies());
233 }
234 
TEST_F(PropagateOnAll,CopyConstructor)235 TEST_F(PropagateOnAll, CopyConstructor) {
236   auto it = t1.insert(0).first;
237   Table u(t1);
238   EXPECT_EQ(2, a1.num_allocs());
239   EXPECT_EQ(0, it->num_moves());
240   EXPECT_EQ(1, it->num_copies());
241 }
242 
TEST_F(NoPropagateOnCopy,CopyConstructor)243 TEST_F(NoPropagateOnCopy, CopyConstructor) {
244   auto it = t1.insert(0).first;
245   Table u(t1);
246   EXPECT_EQ(1, a1.num_allocs());
247   EXPECT_EQ(1, u.get_allocator().num_allocs());
248   EXPECT_EQ(0, it->num_moves());
249   EXPECT_EQ(1, it->num_copies());
250 }
251 
TEST_F(PropagateOnAll,CopyConstructorWithSameAlloc)252 TEST_F(PropagateOnAll, CopyConstructorWithSameAlloc) {
253   auto it = t1.insert(0).first;
254   Table u(t1, a1);
255   EXPECT_EQ(2, a1.num_allocs());
256   EXPECT_EQ(0, it->num_moves());
257   EXPECT_EQ(1, it->num_copies());
258 }
259 
TEST_F(NoPropagateOnCopy,CopyConstructorWithSameAlloc)260 TEST_F(NoPropagateOnCopy, CopyConstructorWithSameAlloc) {
261   auto it = t1.insert(0).first;
262   Table u(t1, a1);
263   EXPECT_EQ(2, a1.num_allocs());
264   EXPECT_EQ(0, it->num_moves());
265   EXPECT_EQ(1, it->num_copies());
266 }
267 
TEST_F(PropagateOnAll,CopyConstructorWithDifferentAlloc)268 TEST_F(PropagateOnAll, CopyConstructorWithDifferentAlloc) {
269   auto it = t1.insert(0).first;
270   Table u(t1, a2);
271   EXPECT_EQ(a2, u.get_allocator());
272   EXPECT_EQ(1, a1.num_allocs());
273   EXPECT_EQ(1, a2.num_allocs());
274   EXPECT_EQ(0, it->num_moves());
275   EXPECT_EQ(1, it->num_copies());
276 }
277 
TEST_F(NoPropagateOnCopy,CopyConstructorWithDifferentAlloc)278 TEST_F(NoPropagateOnCopy, CopyConstructorWithDifferentAlloc) {
279   auto it = t1.insert(0).first;
280   Table u(t1, a2);
281   EXPECT_EQ(a2, u.get_allocator());
282   EXPECT_EQ(1, a1.num_allocs());
283   EXPECT_EQ(1, a2.num_allocs());
284   EXPECT_EQ(0, it->num_moves());
285   EXPECT_EQ(1, it->num_copies());
286 }
287 
TEST_F(PropagateOnAll,MoveConstructor)288 TEST_F(PropagateOnAll, MoveConstructor) {
289   t1.insert(0);
290   Table u(std::move(t1));
291   auto it = u.begin();
292   EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
293   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
294   EXPECT_EQ(0, it->num_copies());
295 }
296 
TEST_F(NoPropagateOnMove,MoveConstructor)297 TEST_F(NoPropagateOnMove, MoveConstructor) {
298   t1.insert(0);
299   Table u(std::move(t1));
300   auto it = u.begin();
301   EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
302   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
303   EXPECT_EQ(0, it->num_copies());
304 }
305 
TEST_F(PropagateOnAll,MoveConstructorWithSameAlloc)306 TEST_F(PropagateOnAll, MoveConstructorWithSameAlloc) {
307   t1.insert(0);
308   Table u(std::move(t1), a1);
309   auto it = u.begin();
310   EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
311   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
312   EXPECT_EQ(0, it->num_copies());
313 }
314 
TEST_F(NoPropagateOnMove,MoveConstructorWithSameAlloc)315 TEST_F(NoPropagateOnMove, MoveConstructorWithSameAlloc) {
316   t1.insert(0);
317   Table u(std::move(t1), a1);
318   auto it = u.begin();
319   EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
320   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
321   EXPECT_EQ(0, it->num_copies());
322 }
323 
TEST_F(PropagateOnAll,MoveConstructorWithDifferentAlloc)324 TEST_F(PropagateOnAll, MoveConstructorWithDifferentAlloc) {
325   auto it = t1.insert(0).first;
326   Table u(std::move(t1), a2);
327   it = u.find(0);
328   EXPECT_EQ(a2, u.get_allocator());
329   EXPECT_EQ(1, a1.num_allocs());
330   EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
331   EXPECT_THAT(it->num_moves(), AnyOf(1, 2));
332   EXPECT_EQ(0, it->num_copies());
333 }
334 
TEST_F(NoPropagateOnMove,MoveConstructorWithDifferentAlloc)335 TEST_F(NoPropagateOnMove, MoveConstructorWithDifferentAlloc) {
336   auto it = t1.insert(0).first;
337   Table u(std::move(t1), a2);
338   it = u.find(0);
339   EXPECT_EQ(a2, u.get_allocator());
340   EXPECT_EQ(1, a1.num_allocs());
341   EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
342   EXPECT_THAT(it->num_moves(), AnyOf(1, 2));
343   EXPECT_EQ(0, it->num_copies());
344 }
345 
TEST_F(PropagateOnAll,CopyAssignmentWithSameAlloc)346 TEST_F(PropagateOnAll, CopyAssignmentWithSameAlloc) {
347   auto it = t1.insert(0).first;
348   Table u(0, a1);
349   u = t1;
350   EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3));
351   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
352   EXPECT_EQ(1, it->num_copies());
353 }
354 
TEST_F(NoPropagateOnCopy,CopyAssignmentWithSameAlloc)355 TEST_F(NoPropagateOnCopy, CopyAssignmentWithSameAlloc) {
356   auto it = t1.insert(0).first;
357   Table u(0, a1);
358   u = t1;
359   EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3));
360   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
361   EXPECT_EQ(1, it->num_copies());
362 }
363 
TEST_F(PropagateOnAll,CopyAssignmentWithDifferentAlloc)364 TEST_F(PropagateOnAll, CopyAssignmentWithDifferentAlloc) {
365   auto it = t1.insert(0).first;
366   Table u(0, a2);
367   u = t1;
368   EXPECT_EQ(a1, u.get_allocator());
369   EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3));
370   EXPECT_EQ(0, a2.num_allocs());
371   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
372   EXPECT_EQ(1, it->num_copies());
373 }
374 
TEST_F(NoPropagateOnCopy,CopyAssignmentWithDifferentAlloc)375 TEST_F(NoPropagateOnCopy, CopyAssignmentWithDifferentAlloc) {
376   auto it = t1.insert(0).first;
377   Table u(0, a2);
378   u = t1;
379   EXPECT_EQ(a2, u.get_allocator());
380   EXPECT_EQ(1, a1.num_allocs());
381   EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
382   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
383   EXPECT_EQ(1, it->num_copies());
384 }
385 
TEST_F(PropagateOnAll,MoveAssignmentWithSameAlloc)386 TEST_F(PropagateOnAll, MoveAssignmentWithSameAlloc) {
387   t1.insert(0);
388   Table u(0, a1);
389   u = std::move(t1);
390   auto it = u.begin();
391   EXPECT_EQ(a1, u.get_allocator());
392   EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
393   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
394   EXPECT_EQ(0, it->num_copies());
395 }
396 
TEST_F(NoPropagateOnMove,MoveAssignmentWithSameAlloc)397 TEST_F(NoPropagateOnMove, MoveAssignmentWithSameAlloc) {
398   t1.insert(0);
399   Table u(0, a1);
400   u = std::move(t1);
401   auto it = u.begin();
402   EXPECT_EQ(a1, u.get_allocator());
403   EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
404   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
405   EXPECT_EQ(0, it->num_copies());
406 }
407 
TEST_F(PropagateOnAll,MoveAssignmentWithDifferentAlloc)408 TEST_F(PropagateOnAll, MoveAssignmentWithDifferentAlloc) {
409   t1.insert(0);
410   Table u(0, a2);
411   u = std::move(t1);
412   auto it = u.begin();
413   EXPECT_EQ(a1, u.get_allocator());
414   EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
415   EXPECT_EQ(0, a2.num_allocs());
416   EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
417   EXPECT_EQ(0, it->num_copies());
418 }
419 
TEST_F(NoPropagateOnMove,MoveAssignmentWithDifferentAlloc)420 TEST_F(NoPropagateOnMove, MoveAssignmentWithDifferentAlloc) {
421   t1.insert(0);
422   Table u(0, a2);
423   u = std::move(t1);
424   auto it = u.find(0);
425   EXPECT_EQ(a2, u.get_allocator());
426   EXPECT_EQ(1, a1.num_allocs());
427   EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
428   EXPECT_THAT(it->num_moves(), AnyOf(1, 2));
429   EXPECT_EQ(0, it->num_copies());
430 }
431 
TEST_F(PropagateOnAll,Swap)432 TEST_F(PropagateOnAll, Swap) {
433   auto it = t1.insert(0).first;
434   Table u(0, a2);
435   u.swap(t1);
436   EXPECT_EQ(a1, u.get_allocator());
437   EXPECT_EQ(a2, t1.get_allocator());
438   EXPECT_EQ(1, a1.num_allocs());
439   EXPECT_EQ(0, a2.num_allocs());
440   EXPECT_EQ(0, it->num_moves());
441   EXPECT_EQ(0, it->num_copies());
442 }
443 
444 // This allocator is similar to std::pmr::polymorphic_allocator.
445 // Note the disabled assignment.
446 template <class T>
447 class PAlloc {
448   template <class>
449   friend class PAlloc;
450 
451  public:
452   // types
453   using value_type = T;
454 
455   PAlloc() noexcept = default;
PAlloc(size_t id)456   explicit PAlloc(size_t id) noexcept : id_(id) {}
457   PAlloc(const PAlloc&) noexcept = default;
458   PAlloc& operator=(const PAlloc&) noexcept = delete;
459 
460   template <class U>
PAlloc(const PAlloc<U> & that)461   PAlloc(const PAlloc<U>& that) noexcept : id_(that.id_) {}  // NOLINT
462 
463   template <class U>
464   struct rebind {
465     using other = PAlloc<U>;
466   };
467 
select_on_container_copy_construction() const468   constexpr PAlloc select_on_container_copy_construction() const { return {}; }
469 
470   // public member functions
allocate(size_t)471   T* allocate(size_t) { return new T; }
deallocate(T * p,size_t)472   void deallocate(T* p, size_t) { delete p; }
473 
operator ==(const PAlloc & a,const PAlloc & b)474   friend bool operator==(const PAlloc& a, const PAlloc& b) {
475     return a.id_ == b.id_;
476   }
operator !=(const PAlloc & a,const PAlloc & b)477   friend bool operator!=(const PAlloc& a, const PAlloc& b) { return !(a == b); }
478 
479  private:
480   size_t id_ = std::numeric_limits<size_t>::max();
481 };
482 
TEST(NoPropagateDeletedAssignment,CopyConstruct)483 TEST(NoPropagateDeletedAssignment, CopyConstruct) {
484   using PA = PAlloc<char>;
485   using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
486 
487   Table t1(PA{1}), t2(t1);
488   EXPECT_EQ(t1.get_allocator(), PA(1));
489   EXPECT_EQ(t2.get_allocator(), PA());
490 }
491 
TEST(NoPropagateDeletedAssignment,CopyAssignment)492 TEST(NoPropagateDeletedAssignment, CopyAssignment) {
493   using PA = PAlloc<char>;
494   using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
495 
496   Table t1(PA{1}), t2(PA{2});
497   t1 = t2;
498   EXPECT_EQ(t1.get_allocator(), PA(1));
499   EXPECT_EQ(t2.get_allocator(), PA(2));
500 }
501 
TEST(NoPropagateDeletedAssignment,MoveAssignment)502 TEST(NoPropagateDeletedAssignment, MoveAssignment) {
503   using PA = PAlloc<char>;
504   using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
505 
506   Table t1(PA{1}), t2(PA{2});
507   t1 = std::move(t2);
508   EXPECT_EQ(t1.get_allocator(), PA(1));
509 }
510 
511 }  // namespace
512 }  // namespace container_internal
513 ABSL_NAMESPACE_END
514 }  // namespace absl
515