1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // 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, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_allocator/first_fit_block_allocator.h"
16
17 #include "pw_allocator/block_allocator_testing.h"
18 #include "pw_allocator/buffer.h"
19 #include "pw_unit_test/framework.h"
20
21 namespace {
22
23 using ::pw::allocator::Layout;
24 using ::pw::allocator::test::Preallocation;
25 using FirstFitBlockAllocator =
26 ::pw::allocator::FirstFitBlockAllocator<uint16_t>;
27 using BlockAllocatorTest =
28 ::pw::allocator::test::BlockAllocatorTest<FirstFitBlockAllocator>;
29
30 class FirstFitBlockAllocatorTest : public BlockAllocatorTest {
31 public:
FirstFitBlockAllocatorTest()32 FirstFitBlockAllocatorTest() : BlockAllocatorTest(allocator_) {}
33
34 private:
35 FirstFitBlockAllocator allocator_;
36 };
37
TEST_F(FirstFitBlockAllocatorTest,CanAutomaticallyInit)38 TEST_F(FirstFitBlockAllocatorTest, CanAutomaticallyInit) {
39 FirstFitBlockAllocator allocator(GetBytes());
40 CanAutomaticallyInit(allocator);
41 }
42
TEST_F(FirstFitBlockAllocatorTest,CanExplicitlyInit)43 TEST_F(FirstFitBlockAllocatorTest, CanExplicitlyInit) {
44 FirstFitBlockAllocator allocator;
45 CanExplicitlyInit(allocator);
46 }
47
TEST_F(FirstFitBlockAllocatorTest,GetCapacity)48 TEST_F(FirstFitBlockAllocatorTest, GetCapacity) { GetCapacity(); }
49
TEST_F(FirstFitBlockAllocatorTest,AllocateLarge)50 TEST_F(FirstFitBlockAllocatorTest, AllocateLarge) { AllocateLarge(); }
51
TEST_F(FirstFitBlockAllocatorTest,AllocateSmall)52 TEST_F(FirstFitBlockAllocatorTest, AllocateSmall) { AllocateSmall(); }
53
TEST_F(FirstFitBlockAllocatorTest,AllocateLargeAlignment)54 TEST_F(FirstFitBlockAllocatorTest, AllocateLargeAlignment) {
55 AllocateLargeAlignment();
56 }
57
TEST_F(FirstFitBlockAllocatorTest,AllocateAlignmentFailure)58 TEST_F(FirstFitBlockAllocatorTest, AllocateAlignmentFailure) {
59 AllocateAlignmentFailure();
60 }
61
TEST_F(FirstFitBlockAllocatorTest,AllocatesFirstCompatible)62 TEST_F(FirstFitBlockAllocatorTest, AllocatesFirstCompatible) {
63 auto& allocator = GetAllocator({
64 {kSmallOuterSize, Preallocation::kIndexFree},
65 {kSmallerOuterSize, 1},
66 {kSmallOuterSize, Preallocation::kIndexFree},
67 {kSmallerOuterSize, 3},
68 {kLargeOuterSize, Preallocation::kIndexFree},
69 {Preallocation::kSizeRemaining, 5},
70 });
71
72 Store(0, allocator.Allocate(Layout(kSmallInnerSize, 1)));
73 EXPECT_EQ(NextAfter(0), Fetch(1));
74 Store(4, allocator.Allocate(Layout(kLargeInnerSize, 1)));
75 EXPECT_EQ(NextAfter(3), Fetch(4));
76 EXPECT_EQ(NextAfter(4), Fetch(5));
77 }
78
TEST_F(FirstFitBlockAllocatorTest,DeallocateNull)79 TEST_F(FirstFitBlockAllocatorTest, DeallocateNull) { DeallocateNull(); }
80
TEST_F(FirstFitBlockAllocatorTest,DeallocateShuffled)81 TEST_F(FirstFitBlockAllocatorTest, DeallocateShuffled) { DeallocateShuffled(); }
82
TEST_F(FirstFitBlockAllocatorTest,IterateOverBlocks)83 TEST_F(FirstFitBlockAllocatorTest, IterateOverBlocks) { IterateOverBlocks(); }
84
TEST_F(FirstFitBlockAllocatorTest,ResizeNull)85 TEST_F(FirstFitBlockAllocatorTest, ResizeNull) { ResizeNull(); }
86
TEST_F(FirstFitBlockAllocatorTest,ResizeLargeSame)87 TEST_F(FirstFitBlockAllocatorTest, ResizeLargeSame) { ResizeLargeSame(); }
88
TEST_F(FirstFitBlockAllocatorTest,ResizeLargeSmaller)89 TEST_F(FirstFitBlockAllocatorTest, ResizeLargeSmaller) { ResizeLargeSmaller(); }
90
TEST_F(FirstFitBlockAllocatorTest,ResizeLargeLarger)91 TEST_F(FirstFitBlockAllocatorTest, ResizeLargeLarger) { ResizeLargeLarger(); }
92
TEST_F(FirstFitBlockAllocatorTest,ResizeLargeLargerFailure)93 TEST_F(FirstFitBlockAllocatorTest, ResizeLargeLargerFailure) {
94 ResizeLargeLargerFailure();
95 }
96
TEST_F(FirstFitBlockAllocatorTest,ResizeSmallSame)97 TEST_F(FirstFitBlockAllocatorTest, ResizeSmallSame) { ResizeSmallSame(); }
98
TEST_F(FirstFitBlockAllocatorTest,ResizeSmallSmaller)99 TEST_F(FirstFitBlockAllocatorTest, ResizeSmallSmaller) { ResizeSmallSmaller(); }
100
TEST_F(FirstFitBlockAllocatorTest,ResizeSmallLarger)101 TEST_F(FirstFitBlockAllocatorTest, ResizeSmallLarger) { ResizeSmallLarger(); }
102
TEST_F(FirstFitBlockAllocatorTest,ResizeSmallLargerFailure)103 TEST_F(FirstFitBlockAllocatorTest, ResizeSmallLargerFailure) {
104 ResizeSmallLargerFailure();
105 }
106
TEST_F(FirstFitBlockAllocatorTest,CanMeasureFragmentation)107 TEST_F(FirstFitBlockAllocatorTest, CanMeasureFragmentation) {
108 CanMeasureFragmentation();
109 }
110
TEST_F(FirstFitBlockAllocatorTest,DisablePoisoning)111 TEST_F(FirstFitBlockAllocatorTest, DisablePoisoning) {
112 auto& allocator = GetAllocator();
113 constexpr Layout layout = Layout::Of<std::byte[kSmallInnerSize]>();
114
115 // Allocate 3 blocks to prevent the middle one from being merged when freed.
116 std::array<void*, 3> ptrs;
117 for (auto& ptr : ptrs) {
118 ptr = allocator.Allocate(layout);
119 ASSERT_NE(ptr, nullptr);
120 }
121
122 // Modify the contents of the block and check if it is still valid.
123 auto* bytes = std::launder(reinterpret_cast<uint8_t*>(ptrs[1]));
124 auto* block = BlockType::FromUsableSpace(bytes);
125 allocator.Deallocate(bytes);
126 EXPECT_FALSE(block->Used());
127 EXPECT_TRUE(block->IsValid());
128 bytes[0] = ~bytes[0];
129 EXPECT_TRUE(block->IsValid());
130
131 allocator.Deallocate(ptrs[0]);
132 allocator.Deallocate(ptrs[2]);
133 }
134
TEST(PoisonedFirstFitBlockAllocatorTest,PoisonEveryFreeBlock)135 TEST(PoisonedFirstFitBlockAllocatorTest, PoisonEveryFreeBlock) {
136 using PoisonedFirstFitBlockAllocator =
137 ::pw::allocator::FirstFitBlockAllocator<uintptr_t, 1>;
138 using BlockType = PoisonedFirstFitBlockAllocator::BlockType;
139
140 pw::allocator::WithBuffer<PoisonedFirstFitBlockAllocator,
141 FirstFitBlockAllocatorTest::kCapacity>
142 allocator;
143 allocator->Init(allocator.as_bytes());
144 constexpr Layout layout =
145 Layout::Of<std::byte[FirstFitBlockAllocatorTest::kSmallInnerSize]>();
146
147 // Allocate 3 blocks to prevent the middle one from being merged when freed.
148 std::array<void*, 3> ptrs;
149 for (auto& ptr : ptrs) {
150 ptr = allocator->Allocate(layout);
151 ASSERT_NE(ptr, nullptr);
152 }
153 // Modify the contents of the block and check if it is still valid.
154 auto* bytes = std::launder(reinterpret_cast<uint8_t*>(ptrs[1]));
155 auto* block = BlockType::FromUsableSpace(bytes);
156 allocator->Deallocate(bytes);
157 EXPECT_FALSE(block->Used());
158 EXPECT_TRUE(block->IsValid());
159 bytes[0] = ~bytes[0];
160 EXPECT_FALSE(block->IsValid());
161
162 allocator->Deallocate(ptrs[0]);
163 allocator->Deallocate(ptrs[2]);
164 }
165
TEST(PoisonedFirstFitBlockAllocatorTest,PoisonPeriodically)166 TEST(PoisonedFirstFitBlockAllocatorTest, PoisonPeriodically) {
167 using PoisonedFirstFitBlockAllocator =
168 ::pw::allocator::FirstFitBlockAllocator<uintptr_t, 4>;
169 using BlockType = PoisonedFirstFitBlockAllocator::BlockType;
170
171 pw::allocator::WithBuffer<PoisonedFirstFitBlockAllocator,
172 FirstFitBlockAllocatorTest::kCapacity>
173 allocator;
174 allocator->Init(allocator.as_bytes());
175 constexpr Layout layout =
176 Layout::Of<std::byte[FirstFitBlockAllocatorTest::kSmallInnerSize]>();
177
178 // Allocate 9 blocks to prevent every other from being merged when freed.
179 std::array<void*, 9> ptrs;
180 for (auto& ptr : ptrs) {
181 ptr = allocator->Allocate(layout);
182 ASSERT_NE(ptr, nullptr);
183 }
184
185 for (size_t i = 1; i < ptrs.size(); i += 2) {
186 auto* bytes = std::launder(reinterpret_cast<uint8_t*>(ptrs[i]));
187 auto* block = BlockType::FromUsableSpace(bytes);
188 allocator->Deallocate(bytes);
189 EXPECT_FALSE(block->Used());
190 EXPECT_TRUE(block->IsValid());
191 bytes[0] = ~bytes[0];
192
193 // Corruption is only detected on the fourth freed block.
194 if (i == 7) {
195 EXPECT_FALSE(block->IsValid());
196 } else {
197 EXPECT_TRUE(block->IsValid());
198 }
199 }
200
201 for (size_t i = 0; i < ptrs.size(); i += 2) {
202 allocator->Deallocate(ptrs[i]);
203 }
204 }
205
206 } // namespace
207