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