// Copyright 2017 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/containers/vector_buffer.h" #include "base/compiler_specific.h" #include "base/memory/raw_ptr.h" #include "base/test/bind.h" #include "base/test/copy_only_int.h" #include "base/test/move_only_int.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace base::internal { namespace { class TRIVIAL_ABI TrivialAbiWithCountingOperations { public: TrivialAbiWithCountingOperations(int* destruction_counter, int* move_counter) : destruction_counter_(destruction_counter), move_counter_(move_counter) {} ~TrivialAbiWithCountingOperations() { ++*destruction_counter_; } // Copy construction and assignment should not be used. TrivialAbiWithCountingOperations(const TrivialAbiWithCountingOperations&) = delete; TrivialAbiWithCountingOperations& operator=( const TrivialAbiWithCountingOperations&) = delete; // Count how many times the move constructor is used. TrivialAbiWithCountingOperations(TrivialAbiWithCountingOperations&& rhs) : destruction_counter_(rhs.destruction_counter_), move_counter_(rhs.move_counter_) { ++*move_counter_; } // Move assignment should not be used. TrivialAbiWithCountingOperations& operator=( TrivialAbiWithCountingOperations&&) = delete; private: raw_ptr destruction_counter_; raw_ptr move_counter_; }; } // namespace TEST(VectorBuffer, DeletePOD) { constexpr int size = 10; VectorBuffer buffer(size); for (int i = 0; i < size; i++) buffer[i] = i + 1; VectorBuffer::DestructRange(buffer.as_span()); // Delete should do nothing. for (int i = 0; i < size; i++) EXPECT_EQ(i + 1, buffer[i]); } TEST(VectorBuffer, DeleteMoveOnly) { constexpr int size = 10; VectorBuffer buffer(size); for (int i = 0; i < size; i++) { // SAFETY: `i < size`, and `size` is the buffer's allocation size, so // `begin() + i` is inside the buffer. new (UNSAFE_BUFFERS(buffer.begin() + i)) MoveOnlyInt(i + 1); } std::vector destroyed_instances; auto scoped_callback_cleanup = MoveOnlyInt::SetScopedDestructionCallback(BindLambdaForTesting( [&](int value) { destroyed_instances.push_back(value); })); VectorBuffer::DestructRange(buffer.as_span()); EXPECT_THAT(destroyed_instances, ::testing::ElementsAre(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); } TEST(VectorBuffer, PODMove) { constexpr int size = 10; VectorBuffer dest(size); VectorBuffer original(size); for (int i = 0; i < size; i++) original[i] = i + 1; VectorBuffer::MoveConstructRange(original.as_span(), dest.as_span()); for (int i = 0; i < size; i++) EXPECT_EQ(i + 1, dest[i]); } TEST(VectorBuffer, MovableMove) { constexpr int size = 10; VectorBuffer dest(size); VectorBuffer original(size); for (int i = 0; i < size; i++) { // SAFETY: `i < size`, and `size` is the buffer's allocation size, so // `begin() + i` is inside the buffer. new (UNSAFE_BUFFERS(original.begin() + i)) MoveOnlyInt(i + 1); } std::vector destroyed_instances; auto scoped_callback_cleanup = MoveOnlyInt::SetScopedDestructionCallback(BindLambdaForTesting( [&](int value) { destroyed_instances.push_back(value); })); VectorBuffer::MoveConstructRange(original.as_span(), dest.as_span()); for (int i = 0; i < size; i++) { EXPECT_EQ(i + 1, dest[i].data()); } // The original values were consumed, so when the original elements are // destroyed, the destruction callback should report 0. EXPECT_THAT(destroyed_instances, ::testing::ElementsAre(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); } TEST(VectorBuffer, CopyToMove) { constexpr int size = 10; VectorBuffer dest(size); VectorBuffer original(size); for (int i = 0; i < size; i++) { // SAFETY: `i < size`, and `size` is the buffer's allocation size, so // `begin() + i` is inside the buffer. new (UNSAFE_BUFFERS(original.begin() + i)) CopyOnlyInt(i + 1); } std::vector destroyed_instances; auto scoped_callback_cleanup = CopyOnlyInt::SetScopedDestructionCallback(BindLambdaForTesting( [&](int value) { destroyed_instances.push_back(value); })); VectorBuffer::MoveConstructRange(original.as_span(), dest.as_span()); for (int i = 0; i < size; i++) { EXPECT_EQ(i + 1, dest[i].data()); } EXPECT_THAT(destroyed_instances, ::testing::ElementsAre(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); } TEST(VectorBuffer, TrivialAbiMove) { // Currently trivial relocation doesn't work on Windows for some reason, so // the test needs to handle both cases. constexpr bool kHaveTrivialRelocation = IS_TRIVIALLY_RELOCATABLE(TrivialAbiWithCountingOperations); constexpr int size = 10; VectorBuffer dest(size); int destruction_count = 0; int move_count = 0; VectorBuffer original(size); for (int i = 0; i < size; i++) { // SAFETY: `i < size`, and `size` is the buffer's allocation size, so // `begin() + i` is inside the buffer. new (UNSAFE_BUFFERS(original.begin() + i)) TrivialAbiWithCountingOperations(&destruction_count, &move_count); } VectorBuffer::MoveConstructRange( original.as_span(), dest.as_span()); // We expect the move to have been performed via memcpy, without calling move // constructors or destructors. EXPECT_EQ(destruction_count, kHaveTrivialRelocation ? 0 : size); EXPECT_EQ(move_count, kHaveTrivialRelocation ? 0 : size); dest.DestructRange(dest.as_span()); EXPECT_EQ(destruction_count, kHaveTrivialRelocation ? size : size * 2); EXPECT_EQ(move_count, kHaveTrivialRelocation ? 0 : size); } } // namespace base::internal