1//===-- release_test.cpp ----------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "tests/scudo_unit_test.h"
10
11#include "list.h"
12#include "release.h"
13#include "size_class_map.h"
14
15#include <string.h>
16
17#include <algorithm>
18#include <random>
19#include <set>
20
21TEST(ScudoReleaseTest, RegionPageMap) {
22 for (scudo::uptr I = 0; I < SCUDO_WORDSIZE; I++) {
23 // Various valid counter's max values packed into one word.
24 scudo::RegionPageMap PageMap2N(1U, 1U, 1UL << I);
25 ASSERT_TRUE(PageMap2N.isAllocated());
26 EXPECT_EQ(1U, PageMap2N.getBufferNumElements());
27 // Check the "all bit set" values too.
28 scudo::RegionPageMap PageMap2N1_1(1U, 1U, ~0UL >> I);
29 ASSERT_TRUE(PageMap2N1_1.isAllocated());
30 EXPECT_EQ(1U, PageMap2N1_1.getBufferNumElements());
31 // Verify the packing ratio, the counter is Expected to be packed into the
32 // closest power of 2 bits.
33 scudo::RegionPageMap PageMap(1U, SCUDO_WORDSIZE, 1UL << I);
34 ASSERT_TRUE(PageMap.isAllocated());
35 EXPECT_EQ(scudo::roundUpPowerOfTwo(Size: I + 1), PageMap.getBufferNumElements());
36 }
37
38 // Go through 1, 2, 4, 8, .. {32,64} bits per counter.
39 for (scudo::uptr I = 0; (SCUDO_WORDSIZE >> I) != 0; I++) {
40 // Make sure counters request one memory page for the buffer.
41 const scudo::uptr NumCounters =
42 (scudo::getPageSizeCached() / 8) * (SCUDO_WORDSIZE >> I);
43 scudo::RegionPageMap PageMap(1U, NumCounters,
44 1UL << ((1UL << I) - 1));
45 ASSERT_TRUE(PageMap.isAllocated());
46 PageMap.inc(Region: 0U, I: 0U);
47 for (scudo::uptr C = 1; C < NumCounters - 1; C++) {
48 EXPECT_EQ(0UL, PageMap.get(Region: 0U, I: C));
49 PageMap.inc(Region: 0U, I: C);
50 EXPECT_EQ(1UL, PageMap.get(Region: 0U, I: C - 1));
51 }
52 EXPECT_EQ(0UL, PageMap.get(Region: 0U, I: NumCounters - 1));
53 PageMap.inc(Region: 0U, I: NumCounters - 1);
54 if (I > 0) {
55 PageMap.incRange(Region: 0u, From: 0U, To: NumCounters - 1);
56 for (scudo::uptr C = 0; C < NumCounters; C++)
57 EXPECT_EQ(2UL, PageMap.get(Region: 0U, I: C));
58 }
59 }
60
61 // Similar to the above except that we are using incN().
62 for (scudo::uptr I = 0; (SCUDO_WORDSIZE >> I) != 0; I++) {
63 // Make sure counters request one memory page for the buffer.
64 const scudo::uptr NumCounters =
65 (scudo::getPageSizeCached() / 8) * (SCUDO_WORDSIZE >> I);
66 scudo::uptr MaxValue = 1UL << ((1UL << I) - 1);
67 if (MaxValue <= 1U)
68 continue;
69
70 scudo::RegionPageMap PageMap(1U, NumCounters, MaxValue);
71
72 scudo::uptr N = MaxValue / 2;
73 PageMap.incN(Region: 0U, I: 0, N);
74 for (scudo::uptr C = 1; C < NumCounters; C++) {
75 EXPECT_EQ(0UL, PageMap.get(Region: 0U, I: C));
76 PageMap.incN(Region: 0U, I: C, N);
77 EXPECT_EQ(N, PageMap.get(Region: 0U, I: C - 1));
78 }
79 EXPECT_EQ(N, PageMap.get(Region: 0U, I: NumCounters - 1));
80 }
81}
82
83class StringRangeRecorder {
84public:
85 std::string ReportedPages;
86
87 StringRangeRecorder()
88 : PageSizeScaledLog(scudo::getLog2(X: scudo::getPageSizeCached())) {}
89
90 void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) {
91 From >>= PageSizeScaledLog;
92 To >>= PageSizeScaledLog;
93 EXPECT_LT(From, To);
94 if (!ReportedPages.empty())
95 EXPECT_LT(LastPageReported, From);
96 ReportedPages.append(From - LastPageReported, '.');
97 ReportedPages.append(To - From, 'x');
98 LastPageReported = To;
99 }
100
101private:
102 const scudo::uptr PageSizeScaledLog;
103 scudo::uptr LastPageReported = 0;
104};
105
106TEST(ScudoReleaseTest, FreePagesRangeTracker) {
107 // 'x' denotes a page to be released, '.' denotes a page to be kept around.
108 const char *TestCases[] = {
109 "",
110 ".",
111 "x",
112 "........",
113 "xxxxxxxxxxx",
114 "..............xxxxx",
115 "xxxxxxxxxxxxxxxxxx.....",
116 "......xxxxxxxx........",
117 "xxx..........xxxxxxxxxxxxxxx",
118 "......xxxx....xxxx........",
119 "xxx..........xxxxxxxx....xxxxxxx",
120 "x.x.x.x.x.x.x.x.x.x.x.x.",
121 ".x.x.x.x.x.x.x.x.x.x.x.x",
122 ".x.x.x.x.x.x.x.x.x.x.x.x.",
123 "x.x.x.x.x.x.x.x.x.x.x.x.x",
124 };
125 typedef scudo::FreePagesRangeTracker<StringRangeRecorder> RangeTracker;
126
127 for (auto TestCase : TestCases) {
128 StringRangeRecorder Recorder;
129 RangeTracker Tracker(Recorder);
130 for (scudo::uptr I = 0; TestCase[I] != 0; I++)
131 Tracker.processNextPage(Released: TestCase[I] == 'x');
132 Tracker.finish();
133 // Strip trailing '.'-pages before comparing the results as they are not
134 // going to be reported to range_recorder anyway.
135 const char *LastX = strrchr(s: TestCase, c: 'x');
136 std::string Expected(
137 TestCase,
138 LastX == nullptr ? 0U : static_cast<size_t>(LastX - TestCase + 1));
139 EXPECT_STREQ(Expected.c_str(), Recorder.ReportedPages.c_str());
140 }
141}
142
143class ReleasedPagesRecorder {
144public:
145 ReleasedPagesRecorder() = default;
146 explicit ReleasedPagesRecorder(scudo::uptr Base) : Base(Base) {}
147 std::set<scudo::uptr> ReportedPages;
148
149 void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) {
150 const scudo::uptr PageSize = scudo::getPageSizeCached();
151 for (scudo::uptr I = From; I < To; I += PageSize)
152 ReportedPages.insert(I + getBase());
153 }
154
155 scudo::uptr getBase() const { return Base; }
156 scudo::uptr Base = 0;
157};
158
159// Simplified version of a TransferBatch.
160template <class SizeClassMap> struct FreeBatch {
161 static const scudo::u16 MaxCount = SizeClassMap::MaxNumCachedHint;
162 void clear() { Count = 0; }
163 void add(scudo::uptr P) {
164 DCHECK_LT(Count, MaxCount);
165 Batch[Count++] = P;
166 }
167 scudo::u16 getCount() const { return Count; }
168 scudo::uptr get(scudo::u16 I) const {
169 DCHECK_LE(I, Count);
170 return Batch[I];
171 }
172 FreeBatch *Next;
173
174private:
175 scudo::uptr Batch[MaxCount];
176 scudo::u16 Count;
177};
178
179template <class SizeClassMap> void testReleaseFreeMemoryToOS() {
180 typedef FreeBatch<SizeClassMap> Batch;
181 const scudo::uptr PagesCount = 1024;
182 const scudo::uptr PageSize = scudo::getPageSizeCached();
183 const scudo::uptr PageSizeLog = scudo::getLog2(X: PageSize);
184 std::mt19937 R;
185 scudo::u32 RandState = 42;
186
187 for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) {
188 const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);
189 const scudo::uptr MaxBlocks = PagesCount * PageSize / BlockSize;
190
191 // Generate the random free list.
192 std::vector<scudo::uptr> FreeArray;
193 bool InFreeRange = false;
194 scudo::uptr CurrentRangeEnd = 0;
195 for (scudo::uptr I = 0; I < MaxBlocks; I++) {
196 if (I == CurrentRangeEnd) {
197 InFreeRange = (scudo::getRandomU32(State: &RandState) & 1U) == 1;
198 CurrentRangeEnd += (scudo::getRandomU32(State: &RandState) & 0x7f) + 1;
199 }
200 if (InFreeRange)
201 FreeArray.push_back(I * BlockSize);
202 }
203 if (FreeArray.empty())
204 continue;
205 // Shuffle the array to ensure that the order is irrelevant.
206 std::shuffle(FreeArray.begin(), FreeArray.end(), R);
207
208 // Build the FreeList from the FreeArray.
209 scudo::SinglyLinkedList<Batch> FreeList;
210 FreeList.clear();
211 Batch *CurrentBatch = nullptr;
212 for (auto const &Block : FreeArray) {
213 if (!CurrentBatch) {
214 CurrentBatch = new Batch;
215 CurrentBatch->clear();
216 FreeList.push_back(CurrentBatch);
217 }
218 CurrentBatch->add(Block);
219 if (CurrentBatch->getCount() == Batch::MaxCount)
220 CurrentBatch = nullptr;
221 }
222
223 // Release the memory.
224 auto SkipRegion = [](UNUSED scudo::uptr RegionIndex) { return false; };
225 auto DecompactPtr = [](scudo::uptr P) { return P; };
226 ReleasedPagesRecorder Recorder;
227 scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U,
228 /*ReleaseSize=*/MaxBlocks * BlockSize);
229 ASSERT_FALSE(Context.hasBlockMarked());
230 Context.markFreeBlocksInRegion(FreeList, DecompactPtr, Recorder.getBase(),
231 /*RegionIndex=*/0, MaxBlocks * BlockSize,
232 /*MayContainLastBlockInRegion=*/true);
233 ASSERT_TRUE(Context.hasBlockMarked());
234 releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
235 scudo::RegionPageMap &PageMap = Context.PageMap;
236
237 // Verify that there are no released pages touched by used chunks and all
238 // ranges of free chunks big enough to contain the entire memory pages had
239 // these pages released.
240 scudo::uptr VerifiedReleasedPages = 0;
241 std::set<scudo::uptr> FreeBlocks(FreeArray.begin(), FreeArray.end());
242
243 scudo::uptr CurrentBlock = 0;
244 InFreeRange = false;
245 scudo::uptr CurrentFreeRangeStart = 0;
246 for (scudo::uptr I = 0; I < MaxBlocks; I++) {
247 const bool IsFreeBlock =
248 FreeBlocks.find(CurrentBlock) != FreeBlocks.end();
249 if (IsFreeBlock) {
250 if (!InFreeRange) {
251 InFreeRange = true;
252 CurrentFreeRangeStart = CurrentBlock;
253 }
254 } else {
255 // Verify that this used chunk does not touch any released page.
256 const scudo::uptr StartPage = CurrentBlock / PageSize;
257 const scudo::uptr EndPage = (CurrentBlock + BlockSize - 1) / PageSize;
258 for (scudo::uptr J = StartPage; J <= EndPage; J++) {
259 const bool PageReleased = Recorder.ReportedPages.find(J * PageSize) !=
260 Recorder.ReportedPages.end();
261 EXPECT_EQ(false, PageReleased);
262 EXPECT_EQ(false,
263 PageMap.isAllCounted(Region: 0, I: (J * PageSize) >> PageSizeLog));
264 }
265
266 if (InFreeRange) {
267 InFreeRange = false;
268 // Verify that all entire memory pages covered by this range of free
269 // chunks were released.
270 scudo::uptr P = scudo::roundUp(X: CurrentFreeRangeStart, Boundary: PageSize);
271 while (P + PageSize <= CurrentBlock) {
272 const bool PageReleased =
273 Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end();
274 EXPECT_EQ(true, PageReleased);
275 EXPECT_EQ(true, PageMap.isAllCounted(Region: 0, I: P >> PageSizeLog));
276 VerifiedReleasedPages++;
277 P += PageSize;
278 }
279 }
280 }
281
282 CurrentBlock += BlockSize;
283 }
284
285 if (InFreeRange) {
286 scudo::uptr P = scudo::roundUp(X: CurrentFreeRangeStart, Boundary: PageSize);
287 const scudo::uptr EndPage =
288 scudo::roundUp(X: MaxBlocks * BlockSize, Boundary: PageSize);
289 while (P + PageSize <= EndPage) {
290 const bool PageReleased =
291 Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end();
292 EXPECT_EQ(true, PageReleased);
293 EXPECT_EQ(true, PageMap.isAllCounted(Region: 0, I: P >> PageSizeLog));
294 VerifiedReleasedPages++;
295 P += PageSize;
296 }
297 }
298
299 EXPECT_EQ(Recorder.ReportedPages.size(), VerifiedReleasedPages);
300
301 while (!FreeList.empty()) {
302 CurrentBatch = FreeList.front();
303 FreeList.pop_front();
304 delete CurrentBatch;
305 }
306 }
307}
308
309template <class SizeClassMap> void testPageMapMarkRange() {
310 const scudo::uptr PageSize = scudo::getPageSizeCached();
311
312 for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) {
313 const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);
314
315 const scudo::uptr GroupNum = 2;
316 const scudo::uptr GroupSize = scudo::roundUp(X: BlockSize, Boundary: PageSize) * 2;
317 const scudo::uptr RegionSize =
318 scudo::roundUpSlow(X: GroupSize * GroupNum, Boundary: BlockSize);
319 const scudo::uptr RoundedRegionSize = scudo::roundUp(X: RegionSize, Boundary: PageSize);
320
321 std::vector<scudo::uptr> Pages(RoundedRegionSize / PageSize, 0);
322 for (scudo::uptr Block = 0; Block < RoundedRegionSize; Block += BlockSize) {
323 for (scudo::uptr Page = Block / PageSize;
324 Page <= (Block + BlockSize - 1) / PageSize &&
325 Page < RoundedRegionSize / PageSize;
326 ++Page) {
327 ASSERT_LT(Page, Pages.size());
328 ++Pages[Page];
329 }
330 }
331
332 for (scudo::uptr GroupId = 0; GroupId < GroupNum; ++GroupId) {
333 const scudo::uptr GroupBeg = GroupId * GroupSize;
334 const scudo::uptr GroupEnd = GroupBeg + GroupSize;
335
336 scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U,
337 /*ReleaseSize=*/RegionSize);
338 Context.markRangeAsAllCounted(From: GroupBeg, To: GroupEnd, /*Base=*/0U,
339 /*RegionIndex=*/0, RegionSize);
340
341 scudo::uptr FirstBlock =
342 ((GroupBeg + BlockSize - 1) / BlockSize) * BlockSize;
343
344 // All the pages before first block page are not supposed to be marked.
345 if (FirstBlock / PageSize > 0) {
346 for (scudo::uptr Page = 0; Page <= FirstBlock / PageSize - 1; ++Page)
347 EXPECT_EQ(Context.PageMap.get(/*Region=*/0, I: Page), 0U);
348 }
349
350 // Verify the pages used by the blocks in the group except that if the
351 // end of the last block is not aligned with `GroupEnd`, it'll be verified
352 // later.
353 scudo::uptr Block;
354 for (Block = FirstBlock; Block + BlockSize <= GroupEnd;
355 Block += BlockSize) {
356 for (scudo::uptr Page = Block / PageSize;
357 Page <= (Block + BlockSize - 1) / PageSize; ++Page) {
358 // First used page in the group has two cases, which are w/ and w/o
359 // block sitting across the boundary.
360 if (Page == FirstBlock / PageSize) {
361 if (FirstBlock % PageSize == 0) {
362 EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0U, I: Page));
363 } else {
364 // There's a block straddling `GroupBeg`, it's supposed to only
365 // increment the counter and we expect it should be 1 less
366 // (exclude the straddling one) than the total blocks on the page.
367 EXPECT_EQ(Context.PageMap.get(/*Region=*/0U, Page),
368 Pages[Page] - 1);
369 }
370 } else {
371 EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0, I: Page));
372 }
373 }
374 }
375
376 if (Block == GroupEnd)
377 continue;
378
379 // Examine the last block which sits across the group boundary.
380 if (Block + BlockSize == RegionSize) {
381 // This is the last block in the region, it's supposed to mark all the
382 // pages as all counted.
383 for (scudo::uptr Page = Block / PageSize;
384 Page <= (Block + BlockSize - 1) / PageSize; ++Page) {
385 EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0, I: Page));
386 }
387 } else {
388 for (scudo::uptr Page = Block / PageSize;
389 Page <= (Block + BlockSize - 1) / PageSize; ++Page) {
390 if (Page <= (GroupEnd - 1) / PageSize)
391 EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0, I: Page));
392 else
393 EXPECT_EQ(Context.PageMap.get(/*Region=*/0U, I: Page), 1U);
394 }
395 }
396
397 const scudo::uptr FirstUncountedPage =
398 scudo::roundUp(X: Block + BlockSize, Boundary: PageSize);
399 for (scudo::uptr Page = FirstUncountedPage;
400 Page <= RoundedRegionSize / PageSize; ++Page) {
401 EXPECT_EQ(Context.PageMap.get(/*Region=*/0U, I: Page), 0U);
402 }
403 } // Iterate each Group
404
405 // Release the entire region. This is to ensure the last page is counted.
406 scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U,
407 /*ReleaseSize=*/RegionSize);
408 Context.markRangeAsAllCounted(/*From=*/0U, /*To=*/RegionSize, /*Base=*/0,
409 /*RegionIndex=*/0, RegionSize);
410 for (scudo::uptr Page = 0; Page < RoundedRegionSize / PageSize; ++Page)
411 EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0, I: Page));
412 } // Iterate each size class
413}
414
415template <class SizeClassMap> void testReleasePartialRegion() {
416 typedef FreeBatch<SizeClassMap> Batch;
417 const scudo::uptr PageSize = scudo::getPageSizeCached();
418
419 for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) {
420 // In the following, we want to ensure the region includes at least 2 pages
421 // and we will release all the pages except the first one. The handling of
422 // the last block is tricky, so we always test the case that includes the
423 // last block.
424 const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);
425 const scudo::uptr ReleaseBase = scudo::roundUp(X: BlockSize, Boundary: PageSize);
426 const scudo::uptr BasePageOffset = ReleaseBase / PageSize;
427 const scudo::uptr RegionSize =
428 scudo::roundUpSlow(X: scudo::roundUp(X: BlockSize, Boundary: PageSize) + ReleaseBase,
429 Boundary: BlockSize) +
430 BlockSize;
431 const scudo::uptr RoundedRegionSize = scudo::roundUp(X: RegionSize, Boundary: PageSize);
432
433 scudo::SinglyLinkedList<Batch> FreeList;
434 FreeList.clear();
435
436 // Skip the blocks in the first page and add the remaining.
437 std::vector<scudo::uptr> Pages(RoundedRegionSize / PageSize, 0);
438 for (scudo::uptr Block = scudo::roundUpSlow(X: ReleaseBase, Boundary: BlockSize);
439 Block + BlockSize <= RoundedRegionSize; Block += BlockSize) {
440 for (scudo::uptr Page = Block / PageSize;
441 Page <= (Block + BlockSize - 1) / PageSize; ++Page) {
442 ASSERT_LT(Page, Pages.size());
443 ++Pages[Page];
444 }
445 }
446
447 // This follows the logic how we count the last page. It should be
448 // consistent with how markFreeBlocksInRegion() handles the last block.
449 if (RoundedRegionSize % BlockSize != 0)
450 ++Pages.back();
451
452 Batch *CurrentBatch = nullptr;
453 for (scudo::uptr Block = scudo::roundUpSlow(X: ReleaseBase, Boundary: BlockSize);
454 Block < RegionSize; Block += BlockSize) {
455 if (CurrentBatch == nullptr ||
456 CurrentBatch->getCount() == Batch::MaxCount) {
457 CurrentBatch = new Batch;
458 CurrentBatch->clear();
459 FreeList.push_back(CurrentBatch);
460 }
461 CurrentBatch->add(Block);
462 }
463
464 auto VerifyReleaseToOs = [&](scudo::PageReleaseContext &Context) {
465 auto SkipRegion = [](UNUSED scudo::uptr RegionIndex) { return false; };
466 ReleasedPagesRecorder Recorder(ReleaseBase);
467 releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
468 const scudo::uptr FirstBlock = scudo::roundUpSlow(X: ReleaseBase, Boundary: BlockSize);
469
470 for (scudo::uptr P = 0; P < RoundedRegionSize; P += PageSize) {
471 if (P < FirstBlock) {
472 // If FirstBlock is not aligned with page boundary, the first touched
473 // page will not be released either.
474 EXPECT_TRUE(Recorder.ReportedPages.find(P) ==
475 Recorder.ReportedPages.end());
476 } else {
477 EXPECT_TRUE(Recorder.ReportedPages.find(P) !=
478 Recorder.ReportedPages.end());
479 }
480 }
481 };
482
483 // Test marking by visiting each block.
484 {
485 auto DecompactPtr = [](scudo::uptr P) { return P; };
486 scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U,
487 /*ReleaseSize=*/RegionSize - PageSize,
488 ReleaseBase);
489 Context.markFreeBlocksInRegion(FreeList, DecompactPtr, /*Base=*/0U,
490 /*RegionIndex=*/0, RegionSize,
491 /*MayContainLastBlockInRegion=*/true);
492 for (const Batch &It : FreeList) {
493 for (scudo::u16 I = 0; I < It.getCount(); I++) {
494 scudo::uptr Block = It.get(I);
495 for (scudo::uptr Page = Block / PageSize;
496 Page <= (Block + BlockSize - 1) / PageSize; ++Page) {
497 EXPECT_EQ(Pages[Page], Context.PageMap.get(/*Region=*/0U,
498 Page - BasePageOffset));
499 }
500 }
501 }
502
503 VerifyReleaseToOs(Context);
504 }
505
506 // Test range marking.
507 {
508 scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U,
509 /*ReleaseSize=*/RegionSize - PageSize,
510 ReleaseBase);
511 Context.markRangeAsAllCounted(From: ReleaseBase, To: RegionSize, /*Base=*/0U,
512 /*RegionIndex=*/0, RegionSize);
513 for (scudo::uptr Page = ReleaseBase / PageSize;
514 Page < RoundedRegionSize / PageSize; ++Page) {
515 if (Context.PageMap.get(/*Region=*/0, Page - BasePageOffset) !=
516 Pages[Page]) {
517 EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0,
518 I: Page - BasePageOffset));
519 }
520 }
521
522 VerifyReleaseToOs(Context);
523 }
524
525 // Check the buffer size of PageMap.
526 {
527 scudo::PageReleaseContext Full(BlockSize, /*NumberOfRegions=*/1U,
528 /*ReleaseSize=*/RegionSize);
529 Full.ensurePageMapAllocated();
530 scudo::PageReleaseContext Partial(BlockSize, /*NumberOfRegions=*/1U,
531 /*ReleaseSize=*/RegionSize - PageSize,
532 ReleaseBase);
533 Partial.ensurePageMapAllocated();
534
535 EXPECT_GE(Full.PageMap.getBufferNumElements(),
536 Partial.PageMap.getBufferNumElements());
537 }
538
539 while (!FreeList.empty()) {
540 CurrentBatch = FreeList.front();
541 FreeList.pop_front();
542 delete CurrentBatch;
543 }
544 } // Iterate each size class
545}
546
547TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSDefault) {
548 testReleaseFreeMemoryToOS<scudo::DefaultSizeClassMap>();
549}
550
551TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSAndroid) {
552 testReleaseFreeMemoryToOS<scudo::AndroidSizeClassMap>();
553}
554
555TEST(ScudoReleaseTest, PageMapMarkRange) {
556 testPageMapMarkRange<scudo::DefaultSizeClassMap>();
557 testPageMapMarkRange<scudo::AndroidSizeClassMap>();
558 testPageMapMarkRange<scudo::FuchsiaSizeClassMap>();
559}
560
561TEST(ScudoReleaseTest, ReleasePartialRegion) {
562 testReleasePartialRegion<scudo::DefaultSizeClassMap>();
563 testReleasePartialRegion<scudo::AndroidSizeClassMap>();
564 testReleasePartialRegion<scudo::FuchsiaSizeClassMap>();
565}
566
567template <class SizeClassMap> void testReleaseRangeWithSingleBlock() {
568 const scudo::uptr PageSize = scudo::getPageSizeCached();
569
570 // We want to test if a memory group only contains single block that will be
571 // handled properly. The case is like:
572 //
573 // From To
574 // +----------------------+
575 // +------------+------------+
576 // | | |
577 // +------------+------------+
578 // ^
579 // RegionSize
580 //
581 // Note that `From` will be page aligned.
582 //
583 // If the second from the last block is aligned at `From`, then we expect all
584 // the pages after `From` will be marked as can-be-released. Otherwise, the
585 // pages only touched by the last blocks will be marked as can-be-released.
586 for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) {
587 const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);
588 const scudo::uptr From = scudo::roundUp(X: BlockSize, Boundary: PageSize);
589 const scudo::uptr To =
590 From % BlockSize == 0
591 ? From + BlockSize
592 : scudo::roundDownSlow(X: From + BlockSize, Boundary: BlockSize) + BlockSize;
593 const scudo::uptr RoundedRegionSize = scudo::roundUp(X: To, Boundary: PageSize);
594
595 std::vector<scudo::uptr> Pages(RoundedRegionSize / PageSize, 0);
596 for (scudo::uptr Block = (To - BlockSize); Block < RoundedRegionSize;
597 Block += BlockSize) {
598 for (scudo::uptr Page = Block / PageSize;
599 Page <= (Block + BlockSize - 1) / PageSize &&
600 Page < RoundedRegionSize / PageSize;
601 ++Page) {
602 ASSERT_LT(Page, Pages.size());
603 ++Pages[Page];
604 }
605 }
606
607 scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U,
608 /*ReleaseSize=*/To,
609 /*ReleaseBase=*/0U);
610 Context.markRangeAsAllCounted(From, To, /*Base=*/0U, /*RegionIndex=*/0,
611 /*RegionSize=*/To);
612
613 for (scudo::uptr Page = 0; Page < RoundedRegionSize; Page += PageSize) {
614 if (Context.PageMap.get(/*Region=*/0U, Page / PageSize) !=
615 Pages[Page / PageSize]) {
616 EXPECT_TRUE(
617 Context.PageMap.isAllCounted(/*Region=*/0U, I: Page / PageSize));
618 }
619 }
620 } // for each size class
621}
622
623TEST(ScudoReleaseTest, RangeReleaseRegionWithSingleBlock) {
624 testReleaseRangeWithSingleBlock<scudo::DefaultSizeClassMap>();
625 testReleaseRangeWithSingleBlock<scudo::AndroidSizeClassMap>();
626 testReleaseRangeWithSingleBlock<scudo::FuchsiaSizeClassMap>();
627}
628
629TEST(ScudoReleaseTest, BufferPool) {
630 constexpr scudo::uptr StaticBufferCount = SCUDO_WORDSIZE - 1;
631 constexpr scudo::uptr StaticBufferNumElements = 512U;
632
633 // Allocate the buffer pool on the heap because it is quite large (slightly
634 // more than StaticBufferCount * StaticBufferNumElements * sizeof(uptr)) and
635 // it may not fit in the stack on some platforms.
636 using BufferPool =
637 scudo::BufferPool<StaticBufferCount, StaticBufferNumElements>;
638 std::unique_ptr<BufferPool> Pool(new BufferPool());
639
640 std::vector<BufferPool::Buffer> Buffers;
641 for (scudo::uptr I = 0; I < StaticBufferCount; ++I) {
642 BufferPool::Buffer Buffer = Pool->getBuffer(StaticBufferNumElements);
643 EXPECT_TRUE(Pool->isStaticBufferTestOnly(Buffer));
644 Buffers.push_back(Buffer);
645 }
646
647 // The static buffer is supposed to be used up.
648 BufferPool::Buffer Buffer = Pool->getBuffer(StaticBufferNumElements);
649 EXPECT_FALSE(Pool->isStaticBufferTestOnly(Buffer));
650
651 Pool->releaseBuffer(Buffer);
652 for (auto &Buffer : Buffers)
653 Pool->releaseBuffer(Buffer);
654}
655

source code of compiler-rt/lib/scudo/standalone/tests/release_test.cpp