655 lines · cpp
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-exception6//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 the32 // closest power of 2 bits.33 scudo::RegionPageMap PageMap(1U, SCUDO_WORDSIZE, 1UL << I);34 ASSERT_TRUE(PageMap.isAllocated());35 EXPECT_EQ(scudo::roundUpPowerOfTwo(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(0U, 0U);47 for (scudo::uptr C = 1; C < NumCounters - 1; C++) {48 EXPECT_EQ(0UL, PageMap.get(0U, C));49 PageMap.inc(0U, C);50 EXPECT_EQ(1UL, PageMap.get(0U, C - 1));51 }52 EXPECT_EQ(0UL, PageMap.get(0U, NumCounters - 1));53 PageMap.inc(0U, NumCounters - 1);54 if (I > 0) {55 PageMap.incRange(0u, 0U, NumCounters - 1);56 for (scudo::uptr C = 0; C < NumCounters; C++)57 EXPECT_EQ(2UL, PageMap.get(0U, 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(0U, 0, N);74 for (scudo::uptr C = 1; C < NumCounters; C++) {75 EXPECT_EQ(0UL, PageMap.get(0U, C));76 PageMap.incN(0U, C, N);77 EXPECT_EQ(N, PageMap.get(0U, C - 1));78 }79 EXPECT_EQ(N, PageMap.get(0U, NumCounters - 1));80 }81}82 83class StringRangeRecorder {84public:85 std::string ReportedPages;86 87 StringRangeRecorder()88 : PageSizeScaledLog(scudo::getLog2(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(TestCase[I] == 'x');132 Tracker.finish();133 // Strip trailing '.'-pages before comparing the results as they are not134 // going to be reported to range_recorder anyway.135 const char *LastX = strrchr(TestCase, '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(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(&RandState) & 1U) == 1;198 CurrentRangeEnd += (scudo::getRandomU32(&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 all238 // ranges of free chunks big enough to contain the entire memory pages had239 // 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(0, (J * PageSize) >> PageSizeLog));264 }265 266 if (InFreeRange) {267 InFreeRange = false;268 // Verify that all entire memory pages covered by this range of free269 // chunks were released.270 scudo::uptr P = scudo::roundUp(CurrentFreeRangeStart, 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(0, 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(CurrentFreeRangeStart, PageSize);287 const scudo::uptr EndPage =288 scudo::roundUp(MaxBlocks * BlockSize, 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(0, 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(BlockSize, PageSize) * 2;317 const scudo::uptr RegionSize =318 scudo::roundUpSlow(GroupSize * GroupNum, BlockSize);319 const scudo::uptr RoundedRegionSize = scudo::roundUp(RegionSize, 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(GroupBeg, 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, Page), 0U);348 }349 350 // Verify the pages used by the blocks in the group except that if the351 // end of the last block is not aligned with `GroupEnd`, it'll be verified352 // 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/o359 // block sitting across the boundary.360 if (Page == FirstBlock / PageSize) {361 if (FirstBlock % PageSize == 0) {362 EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0U, Page));363 } else {364 // There's a block straddling `GroupBeg`, it's supposed to only365 // increment the counter and we expect it should be 1 less366 // (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, 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 the382 // 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, 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, Page));392 else393 EXPECT_EQ(Context.PageMap.get(/*Region=*/0U, Page), 1U);394 }395 }396 397 const scudo::uptr FirstUncountedPage =398 scudo::roundUp(Block + BlockSize, PageSize);399 for (scudo::uptr Page = FirstUncountedPage;400 Page <= RoundedRegionSize / PageSize; ++Page) {401 EXPECT_EQ(Context.PageMap.get(/*Region=*/0U, Page), 0U);402 }403 } // Iterate each Group404 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, Page));412 } // Iterate each size class413}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 pages421 // and we will release all the pages except the first one. The handling of422 // the last block is tricky, so we always test the case that includes the423 // last block.424 const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);425 const scudo::uptr ReleaseBase = scudo::roundUp(BlockSize, PageSize);426 const scudo::uptr BasePageOffset = ReleaseBase / PageSize;427 const scudo::uptr RegionSize =428 scudo::roundUpSlow(scudo::roundUp(BlockSize, PageSize) + ReleaseBase,429 BlockSize) +430 BlockSize;431 const scudo::uptr RoundedRegionSize = scudo::roundUp(RegionSize, 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(ReleaseBase, 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 be448 // 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(ReleaseBase, 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(ReleaseBase, 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 touched473 // 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(ReleaseBase, 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 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 class545}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 be571 // handled properly. The case is like:572 //573 // From To574 // +----------------------+575 // +------------+------------+576 // | | |577 // +------------+------------+578 // ^579 // RegionSize580 //581 // Note that `From` will be page aligned.582 //583 // If the second from the last block is aligned at `From`, then we expect all584 // the pages after `From` will be marked as can-be-released. Otherwise, the585 // 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(BlockSize, PageSize);589 const scudo::uptr To =590 From % BlockSize == 0591 ? From + BlockSize592 : scudo::roundDownSlow(From + BlockSize, BlockSize) + BlockSize;593 const scudo::uptr RoundedRegionSize = scudo::roundUp(To, 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, Page / PageSize));618 }619 }620 } // for each size class621}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 (slightly634 // more than StaticBufferCount * StaticBufferNumElements * sizeof(uptr)) and635 // 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