// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include "base/bind.h" #include "base/callback.h" #include "base/callback_helpers.h" #include "base/files/scoped_file.h" #include "base/memory/madv_free_discardable_memory_allocator_posix.h" #include "base/memory/madv_free_discardable_memory_posix.h" #include "base/memory/page_size.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #define SUCCEED_IF_MADV_FREE_UNSUPPORTED() \ do { \ if (GetMadvFreeSupport() != base::MadvFreeSupport::kSupported) { \ SUCCEED() \ << "MADV_FREE is not supported (Linux 4.5+ required), vacuously " \ "passing test"; \ return; \ } \ } while (0) namespace base { std::atomic allocator_byte_count; class MadvFreeDiscardableMemoryPosixTester : public MadvFreeDiscardableMemoryPosix { public: MadvFreeDiscardableMemoryPosixTester(size_t size_in_bytes) : MadvFreeDiscardableMemoryPosix(size_in_bytes, &allocator_byte_count) {} using MadvFreeDiscardableMemoryPosix::DiscardPage; using MadvFreeDiscardableMemoryPosix::GetPageCount; using MadvFreeDiscardableMemoryPosix::IsLockedForTesting; using MadvFreeDiscardableMemoryPosix::IsValid; using MadvFreeDiscardableMemoryPosix::SetKeepMemoryForTesting; }; class MadvFreeDiscardableMemoryTest : public ::testing::Test { protected: MadvFreeDiscardableMemoryTest() {} ~MadvFreeDiscardableMemoryTest() override {} const size_t kPageSize = base::GetPageSize(); std::unique_ptr AllocateLockedDiscardableMemoryPagesForTest(size_t size_in_pages) { return std::make_unique( size_in_pages * kPageSize); } }; using MadvFreeDiscardableMemoryDeathTest = MadvFreeDiscardableMemoryTest; constexpr char kTestPattern[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; TEST_F(MadvFreeDiscardableMemoryTest, AllocateAndUse) { SUCCEED_IF_MADV_FREE_UNSUPPORTED(); std::unique_ptr mem = AllocateLockedDiscardableMemoryPagesForTest(1); mem->SetKeepMemoryForTesting(true); ASSERT_TRUE(mem->IsValid()); ASSERT_TRUE(mem->IsLockedForTesting()); char buffer[sizeof(kTestPattern)]; // Write test pattern to block uint8_t* data = mem->data_as(); memcpy(data, kTestPattern, sizeof(kTestPattern)); // Read test pattern from block data = mem->data_as(); memcpy(buffer, data, sizeof(kTestPattern)); EXPECT_EQ(memcmp(kTestPattern, buffer, sizeof(kTestPattern)), 0); // Memory contents should not change after successful unlock and lock. mem->Unlock(); ASSERT_TRUE(mem->Lock()); EXPECT_EQ(memcmp(kTestPattern, buffer, sizeof(kTestPattern)), 0); } TEST_F(MadvFreeDiscardableMemoryTest, LockAndUnlock) { SUCCEED_IF_MADV_FREE_UNSUPPORTED(); const size_t kPageCount = 10; std::unique_ptr mem = AllocateLockedDiscardableMemoryPagesForTest(kPageCount); ASSERT_TRUE(mem->IsValid()); ASSERT_TRUE(mem->IsLockedForTesting()); memset(mem->data(), 0xE7, kPageSize * kPageCount); mem->Unlock(); ASSERT_FALSE(mem->IsLockedForTesting()); bool result = mem->Lock(); // If Lock() succeeded, the memory region should be valid. If Lock() failed, // the memory region should be invalid. ASSERT_EQ(result, mem->IsValid()); } TEST_F(MadvFreeDiscardableMemoryTest, LockShouldFailAfterDiscard) { SUCCEED_IF_MADV_FREE_UNSUPPORTED(); constexpr size_t kPageCount = 10; std::unique_ptr mem = AllocateLockedDiscardableMemoryPagesForTest(kPageCount); uint8_t* data = mem->data_as(); ASSERT_TRUE(mem->IsValid()); ASSERT_TRUE(mem->IsLockedForTesting()); // Modify block data such that at least one page is non-zero. memset(data, 0xff, kPageSize * kPageCount); mem->Unlock(); ASSERT_FALSE(mem->IsLockedForTesting()); // Forcefully discard at least one non-zero page. mem->DiscardPage(5); // Locking when a page has been discarded should fail. ASSERT_FALSE(mem->Lock()); // Locking after memory is deallocated should fail. ASSERT_FALSE(mem->Lock()); // Check that memory has been deallocated. ASSERT_FALSE(mem->IsValid()); } } // namespace base