1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <fcntl.h>
6 #include <stdint.h>
7
8 #include <sys/mman.h>
9 #include <memory>
10
11 #include "base/files/scoped_file.h"
12 #include "base/functional/bind.h"
13 #include "base/functional/callback.h"
14 #include "base/functional/callback_helpers.h"
15 #include "base/memory/madv_free_discardable_memory_allocator_posix.h"
16 #include "base/memory/madv_free_discardable_memory_posix.h"
17 #include "base/memory/page_size.h"
18 #include "build/build_config.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 #define SUCCEED_IF_MADV_FREE_UNSUPPORTED() \
22 do { \
23 if (GetMadvFreeSupport() != base::MadvFreeSupport::kSupported) { \
24 SUCCEED() \
25 << "MADV_FREE is not supported (Linux 4.5+ required), vacuously " \
26 "passing test"; \
27 return; \
28 } \
29 } while (0)
30
31 namespace base {
32 std::atomic<size_t> allocator_byte_count;
33 class MadvFreeDiscardableMemoryPosixTester
34 : public MadvFreeDiscardableMemoryPosix {
35 public:
MadvFreeDiscardableMemoryPosixTester(size_t size_in_bytes)36 MadvFreeDiscardableMemoryPosixTester(size_t size_in_bytes)
37 : MadvFreeDiscardableMemoryPosix(size_in_bytes, &allocator_byte_count) {}
38
39 using MadvFreeDiscardableMemoryPosix::DiscardPage;
40 using MadvFreeDiscardableMemoryPosix::GetPageCount;
41 using MadvFreeDiscardableMemoryPosix::IsLockedForTesting;
42 using MadvFreeDiscardableMemoryPosix::IsValid;
43 using MadvFreeDiscardableMemoryPosix::SetKeepMemoryForTesting;
44 };
45
46 class MadvFreeDiscardableMemoryTest : public ::testing::Test {
47 protected:
MadvFreeDiscardableMemoryTest()48 MadvFreeDiscardableMemoryTest() {}
~MadvFreeDiscardableMemoryTest()49 ~MadvFreeDiscardableMemoryTest() override {}
50
51 const size_t kPageSize = base::GetPageSize();
52
53 std::unique_ptr<MadvFreeDiscardableMemoryPosixTester>
AllocateLockedDiscardableMemoryPagesForTest(size_t size_in_pages)54 AllocateLockedDiscardableMemoryPagesForTest(size_t size_in_pages) {
55 return std::make_unique<MadvFreeDiscardableMemoryPosixTester>(
56 size_in_pages * kPageSize);
57 }
58 };
59
60 using MadvFreeDiscardableMemoryDeathTest = MadvFreeDiscardableMemoryTest;
61
62 constexpr char kTestPattern[] =
63 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
64
TEST_F(MadvFreeDiscardableMemoryTest,AllocateAndUse)65 TEST_F(MadvFreeDiscardableMemoryTest, AllocateAndUse) {
66 SUCCEED_IF_MADV_FREE_UNSUPPORTED();
67
68 std::unique_ptr<MadvFreeDiscardableMemoryPosixTester> mem =
69 AllocateLockedDiscardableMemoryPagesForTest(1);
70
71 mem->SetKeepMemoryForTesting(true);
72
73 ASSERT_TRUE(mem->IsValid());
74 ASSERT_TRUE(mem->IsLockedForTesting());
75
76 char buffer[sizeof(kTestPattern)];
77
78 // Write test pattern to block
79 uint8_t* data = mem->data_as<uint8_t>();
80 memcpy(data, kTestPattern, sizeof(kTestPattern));
81
82 // Read test pattern from block
83 data = mem->data_as<uint8_t>();
84 memcpy(buffer, data, sizeof(kTestPattern));
85
86 EXPECT_EQ(memcmp(kTestPattern, buffer, sizeof(kTestPattern)), 0);
87
88 // Memory contents should not change after successful unlock and lock.
89 mem->Unlock();
90 ASSERT_TRUE(mem->Lock());
91
92 EXPECT_EQ(memcmp(kTestPattern, buffer, sizeof(kTestPattern)), 0);
93 }
94
TEST_F(MadvFreeDiscardableMemoryTest,LockAndUnlock)95 TEST_F(MadvFreeDiscardableMemoryTest, LockAndUnlock) {
96 SUCCEED_IF_MADV_FREE_UNSUPPORTED();
97
98 const size_t kPageCount = 10;
99 std::unique_ptr<MadvFreeDiscardableMemoryPosixTester> mem =
100 AllocateLockedDiscardableMemoryPagesForTest(kPageCount);
101
102 ASSERT_TRUE(mem->IsValid());
103 ASSERT_TRUE(mem->IsLockedForTesting());
104 memset(mem->data(), 0xE7, kPageSize * kPageCount);
105 mem->Unlock();
106 ASSERT_FALSE(mem->IsLockedForTesting());
107 bool result = mem->Lock();
108 // If Lock() succeeded, the memory region should be valid. If Lock() failed,
109 // the memory region should be invalid.
110 ASSERT_EQ(result, mem->IsValid());
111 }
112
TEST_F(MadvFreeDiscardableMemoryTest,LockShouldFailAfterDiscard)113 TEST_F(MadvFreeDiscardableMemoryTest, LockShouldFailAfterDiscard) {
114 SUCCEED_IF_MADV_FREE_UNSUPPORTED();
115
116 constexpr size_t kPageCount = 10;
117
118 std::unique_ptr<MadvFreeDiscardableMemoryPosixTester> mem =
119 AllocateLockedDiscardableMemoryPagesForTest(kPageCount);
120 uint8_t* data = mem->data_as<uint8_t>();
121
122 ASSERT_TRUE(mem->IsValid());
123 ASSERT_TRUE(mem->IsLockedForTesting());
124 // Modify block data such that at least one page is non-zero.
125 memset(data, 0xff, kPageSize * kPageCount);
126
127 mem->Unlock();
128 ASSERT_FALSE(mem->IsLockedForTesting());
129 // Forcefully discard at least one non-zero page.
130 mem->DiscardPage(5);
131
132 // Locking when a page has been discarded should fail.
133 ASSERT_FALSE(mem->Lock());
134 // Locking after memory is deallocated should fail.
135 ASSERT_FALSE(mem->Lock());
136
137 // Check that memory has been deallocated.
138 ASSERT_FALSE(mem->IsValid());
139 }
140
141 } // namespace base
142