| //===-- release_test.cpp ----------------------------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "tests/scudo_unit_test.h" |
| |
| #include "list.h" |
| #include "release.h" |
| #include "size_class_map.h" |
| |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <random> |
| #include <set> |
| |
| TEST(ScudoReleaseTest, RegionPageMap) { |
| for (scudo::uptr I = 0; I < SCUDO_WORDSIZE; I++) { |
| // Various valid counter's max values packed into one word. |
| scudo::RegionPageMap PageMap2N(1U, 1U, 1UL << I); |
| EXPECT_EQ(sizeof(scudo::uptr), PageMap2N.getBufferSize()); |
| // Check the "all bit set" values too. |
| scudo::RegionPageMap PageMap2N1_1(1U, 1U, ~0UL >> I); |
| EXPECT_EQ(sizeof(scudo::uptr), PageMap2N1_1.getBufferSize()); |
| // Verify the packing ratio, the counter is Expected to be packed into the |
| // closest power of 2 bits. |
| scudo::RegionPageMap PageMap(1U, SCUDO_WORDSIZE, 1UL << I); |
| EXPECT_EQ(sizeof(scudo::uptr) * scudo::roundUpPowerOfTwo(I + 1), |
| PageMap.getBufferSize()); |
| } |
| |
| // Go through 1, 2, 4, 8, .. {32,64} bits per counter. |
| for (scudo::uptr I = 0; (SCUDO_WORDSIZE >> I) != 0; I++) { |
| // Make sure counters request one memory page for the buffer. |
| const scudo::uptr NumCounters = |
| (scudo::getPageSizeCached() / 8) * (SCUDO_WORDSIZE >> I); |
| scudo::RegionPageMap PageMap(1U, NumCounters, |
| 1UL << ((1UL << I) - 1)); |
| PageMap.inc(0U, 0U); |
| for (scudo::uptr C = 1; C < NumCounters - 1; C++) { |
| EXPECT_EQ(0UL, PageMap.get(0U, C)); |
| PageMap.inc(0U, C); |
| EXPECT_EQ(1UL, PageMap.get(0U, C - 1)); |
| } |
| EXPECT_EQ(0UL, PageMap.get(0U, NumCounters - 1)); |
| PageMap.inc(0U, NumCounters - 1); |
| if (I > 0) { |
| PageMap.incRange(0u, 0U, NumCounters - 1); |
| for (scudo::uptr C = 0; C < NumCounters; C++) |
| EXPECT_EQ(2UL, PageMap.get(0U, C)); |
| } |
| } |
| |
| // Similar to the above except that we are using incN(). |
| for (scudo::uptr I = 0; (SCUDO_WORDSIZE >> I) != 0; I++) { |
| // Make sure counters request one memory page for the buffer. |
| const scudo::uptr NumCounters = |
| (scudo::getPageSizeCached() / 8) * (SCUDO_WORDSIZE >> I); |
| scudo::uptr MaxValue = 1UL << ((1UL << I) - 1); |
| if (MaxValue <= 1U) |
| continue; |
| |
| scudo::RegionPageMap PageMap(1U, NumCounters, MaxValue); |
| |
| scudo::uptr N = MaxValue / 2; |
| PageMap.incN(0U, 0, N); |
| for (scudo::uptr C = 1; C < NumCounters; C++) { |
| EXPECT_EQ(0UL, PageMap.get(0U, C)); |
| PageMap.incN(0U, C, N); |
| EXPECT_EQ(N, PageMap.get(0U, C - 1)); |
| } |
| EXPECT_EQ(N, PageMap.get(0U, NumCounters - 1)); |
| } |
| } |
| |
| class StringRangeRecorder { |
| public: |
| std::string ReportedPages; |
| |
| StringRangeRecorder() |
| : PageSizeScaledLog(scudo::getLog2(scudo::getPageSizeCached())) {} |
| |
| void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) { |
| From >>= PageSizeScaledLog; |
| To >>= PageSizeScaledLog; |
| EXPECT_LT(From, To); |
| if (!ReportedPages.empty()) |
| EXPECT_LT(LastPageReported, From); |
| ReportedPages.append(From - LastPageReported, '.'); |
| ReportedPages.append(To - From, 'x'); |
| LastPageReported = To; |
| } |
| |
| private: |
| const scudo::uptr PageSizeScaledLog; |
| scudo::uptr LastPageReported = 0; |
| }; |
| |
| TEST(ScudoReleaseTest, FreePagesRangeTracker) { |
| // 'x' denotes a page to be released, '.' denotes a page to be kept around. |
| const char *TestCases[] = { |
| "", |
| ".", |
| "x", |
| "........", |
| "xxxxxxxxxxx", |
| "..............xxxxx", |
| "xxxxxxxxxxxxxxxxxx.....", |
| "......xxxxxxxx........", |
| "xxx..........xxxxxxxxxxxxxxx", |
| "......xxxx....xxxx........", |
| "xxx..........xxxxxxxx....xxxxxxx", |
| "x.x.x.x.x.x.x.x.x.x.x.x.", |
| ".x.x.x.x.x.x.x.x.x.x.x.x", |
| ".x.x.x.x.x.x.x.x.x.x.x.x.", |
| "x.x.x.x.x.x.x.x.x.x.x.x.x", |
| }; |
| typedef scudo::FreePagesRangeTracker<StringRangeRecorder> RangeTracker; |
| |
| for (auto TestCase : TestCases) { |
| StringRangeRecorder Recorder; |
| RangeTracker Tracker(Recorder); |
| for (scudo::uptr I = 0; TestCase[I] != 0; I++) |
| Tracker.processNextPage(TestCase[I] == 'x'); |
| Tracker.finish(); |
| // Strip trailing '.'-pages before comparing the results as they are not |
| // going to be reported to range_recorder anyway. |
| const char *LastX = strrchr(TestCase, 'x'); |
| std::string Expected(TestCase, |
| LastX == nullptr ? 0 : (LastX - TestCase + 1)); |
| EXPECT_STREQ(Expected.c_str(), Recorder.ReportedPages.c_str()); |
| } |
| } |
| |
| class ReleasedPagesRecorder { |
| public: |
| ReleasedPagesRecorder() = default; |
| explicit ReleasedPagesRecorder(scudo::uptr Base) : Base(Base) {} |
| std::set<scudo::uptr> ReportedPages; |
| |
| void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) { |
| const scudo::uptr PageSize = scudo::getPageSizeCached(); |
| for (scudo::uptr I = From; I < To; I += PageSize) |
| ReportedPages.insert(I + getBase()); |
| } |
| |
| scudo::uptr getBase() const { return Base; } |
| scudo::uptr Base = 0; |
| }; |
| |
| // Simplified version of a TransferBatch. |
| template <class SizeClassMap> struct FreeBatch { |
| static const scudo::u16 MaxCount = SizeClassMap::MaxNumCachedHint; |
| void clear() { Count = 0; } |
| void add(scudo::uptr P) { |
| DCHECK_LT(Count, MaxCount); |
| Batch[Count++] = P; |
| } |
| scudo::u16 getCount() const { return Count; } |
| scudo::uptr get(scudo::u16 I) const { |
| DCHECK_LE(I, Count); |
| return Batch[I]; |
| } |
| FreeBatch *Next; |
| |
| private: |
| scudo::uptr Batch[MaxCount]; |
| scudo::u16 Count; |
| }; |
| |
| template <class SizeClassMap> void testReleaseFreeMemoryToOS() { |
| typedef FreeBatch<SizeClassMap> Batch; |
| const scudo::uptr PagesCount = 1024; |
| const scudo::uptr PageSize = scudo::getPageSizeCached(); |
| const scudo::uptr PageSizeLog = scudo::getLog2(PageSize); |
| std::mt19937 R; |
| scudo::u32 RandState = 42; |
| |
| for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) { |
| const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I); |
| const scudo::uptr MaxBlocks = PagesCount * PageSize / BlockSize; |
| |
| // Generate the random free list. |
| std::vector<scudo::uptr> FreeArray; |
| bool InFreeRange = false; |
| scudo::uptr CurrentRangeEnd = 0; |
| for (scudo::uptr I = 0; I < MaxBlocks; I++) { |
| if (I == CurrentRangeEnd) { |
| InFreeRange = (scudo::getRandomU32(&RandState) & 1U) == 1; |
| CurrentRangeEnd += (scudo::getRandomU32(&RandState) & 0x7f) + 1; |
| } |
| if (InFreeRange) |
| FreeArray.push_back(I * BlockSize); |
| } |
| if (FreeArray.empty()) |
| continue; |
| // Shuffle the array to ensure that the order is irrelevant. |
| std::shuffle(FreeArray.begin(), FreeArray.end(), R); |
| |
| // Build the FreeList from the FreeArray. |
| scudo::SinglyLinkedList<Batch> FreeList; |
| FreeList.clear(); |
| Batch *CurrentBatch = nullptr; |
| for (auto const &Block : FreeArray) { |
| if (!CurrentBatch) { |
| CurrentBatch = new Batch; |
| CurrentBatch->clear(); |
| FreeList.push_back(CurrentBatch); |
| } |
| CurrentBatch->add(Block); |
| if (CurrentBatch->getCount() == Batch::MaxCount) |
| CurrentBatch = nullptr; |
| } |
| |
| // Release the memory. |
| auto SkipRegion = [](UNUSED scudo::uptr RegionIndex) { return false; }; |
| auto DecompactPtr = [](scudo::uptr P) { return P; }; |
| ReleasedPagesRecorder Recorder; |
| scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U, |
| /*ReleaseSize=*/MaxBlocks * BlockSize); |
| ASSERT_FALSE(Context.hasBlockMarked()); |
| Context.markFreeBlocksInRegion(FreeList, DecompactPtr, Recorder.getBase(), |
| /*RegionIndex=*/0, MaxBlocks * BlockSize, |
| /*MayContainLastBlockInRegion=*/true); |
| ASSERT_TRUE(Context.hasBlockMarked()); |
| releaseFreeMemoryToOS(Context, Recorder, SkipRegion); |
| scudo::RegionPageMap &PageMap = Context.PageMap; |
| |
| // Verify that there are no released pages touched by used chunks and all |
| // ranges of free chunks big enough to contain the entire memory pages had |
| // these pages released. |
| scudo::uptr VerifiedReleasedPages = 0; |
| std::set<scudo::uptr> FreeBlocks(FreeArray.begin(), FreeArray.end()); |
| |
| scudo::uptr CurrentBlock = 0; |
| InFreeRange = false; |
| scudo::uptr CurrentFreeRangeStart = 0; |
| for (scudo::uptr I = 0; I < MaxBlocks; I++) { |
| const bool IsFreeBlock = |
| FreeBlocks.find(CurrentBlock) != FreeBlocks.end(); |
| if (IsFreeBlock) { |
| if (!InFreeRange) { |
| InFreeRange = true; |
| CurrentFreeRangeStart = CurrentBlock; |
| } |
| } else { |
| // Verify that this used chunk does not touch any released page. |
| const scudo::uptr StartPage = CurrentBlock / PageSize; |
| const scudo::uptr EndPage = (CurrentBlock + BlockSize - 1) / PageSize; |
| for (scudo::uptr J = StartPage; J <= EndPage; J++) { |
| const bool PageReleased = Recorder.ReportedPages.find(J * PageSize) != |
| Recorder.ReportedPages.end(); |
| EXPECT_EQ(false, PageReleased); |
| EXPECT_EQ(false, |
| PageMap.isAllCounted(0, (J * PageSize) >> PageSizeLog)); |
| } |
| |
| if (InFreeRange) { |
| InFreeRange = false; |
| // Verify that all entire memory pages covered by this range of free |
| // chunks were released. |
| scudo::uptr P = scudo::roundUp(CurrentFreeRangeStart, PageSize); |
| while (P + PageSize <= CurrentBlock) { |
| const bool PageReleased = |
| Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end(); |
| EXPECT_EQ(true, PageReleased); |
| EXPECT_EQ(true, PageMap.isAllCounted(0, P >> PageSizeLog)); |
| VerifiedReleasedPages++; |
| P += PageSize; |
| } |
| } |
| } |
| |
| CurrentBlock += BlockSize; |
| } |
| |
| if (InFreeRange) { |
| scudo::uptr P = scudo::roundUp(CurrentFreeRangeStart, PageSize); |
| const scudo::uptr EndPage = |
| scudo::roundUp(MaxBlocks * BlockSize, PageSize); |
| while (P + PageSize <= EndPage) { |
| const bool PageReleased = |
| Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end(); |
| EXPECT_EQ(true, PageReleased); |
| EXPECT_EQ(true, PageMap.isAllCounted(0, P >> PageSizeLog)); |
| VerifiedReleasedPages++; |
| P += PageSize; |
| } |
| } |
| |
| EXPECT_EQ(Recorder.ReportedPages.size(), VerifiedReleasedPages); |
| |
| while (!FreeList.empty()) { |
| CurrentBatch = FreeList.front(); |
| FreeList.pop_front(); |
| delete CurrentBatch; |
| } |
| } |
| } |
| |
| template <class SizeClassMap> void testPageMapMarkRange() { |
| const scudo::uptr PageSize = scudo::getPageSizeCached(); |
| |
| for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) { |
| const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I); |
| |
| const scudo::uptr GroupNum = 2; |
| const scudo::uptr GroupSize = scudo::roundUp(BlockSize, PageSize) * 2; |
| const scudo::uptr RegionSize = |
| scudo::roundUpSlow(GroupSize * GroupNum, BlockSize); |
| const scudo::uptr RoundedRegionSize = scudo::roundUp(RegionSize, PageSize); |
| |
| std::vector<scudo::uptr> Pages(RoundedRegionSize / PageSize, 0); |
| for (scudo::uptr Block = 0; Block + BlockSize <= RoundedRegionSize; |
| Block += BlockSize) { |
| for (scudo::uptr page = Block / PageSize; |
| page <= (Block + BlockSize - 1) / PageSize; ++page) { |
| ASSERT_LT(page, Pages.size()); |
| ++Pages[page]; |
| } |
| } |
| |
| for (scudo::uptr GroupId = 0; GroupId < GroupNum; ++GroupId) { |
| const scudo::uptr GroupBeg = GroupId * GroupSize; |
| const scudo::uptr GroupEnd = GroupBeg + GroupSize; |
| |
| scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U, |
| /*ReleaseSize=*/RegionSize); |
| Context.markRangeAsAllCounted(GroupBeg, GroupEnd, /*Base=*/0U, |
| /*RegionIndex=*/0, RegionSize); |
| |
| scudo::uptr FirstBlock = |
| ((GroupBeg + BlockSize - 1) / BlockSize) * BlockSize; |
| |
| // All the pages before first block page are not supposed to be marked. |
| if (FirstBlock / PageSize > 0) { |
| for (scudo::uptr Page = 0; Page <= FirstBlock / PageSize - 1; ++Page) |
| EXPECT_EQ(Context.PageMap.get(/*Region=*/0, Page), 0U); |
| } |
| |
| // Verify the pages used by the blocks in the group except that if the |
| // end of the last block is not aligned with `GroupEnd`, it'll be verified |
| // later. |
| scudo::uptr Block; |
| for (Block = FirstBlock; Block + BlockSize <= GroupEnd; |
| Block += BlockSize) { |
| for (scudo::uptr Page = Block / PageSize; |
| Page <= (Block + BlockSize - 1) / PageSize; ++Page) { |
| // First used page in the group has two cases, which are w/ and w/o |
| // block sitting across the boundary. |
| if (Page == FirstBlock / PageSize) { |
| if (FirstBlock % PageSize == 0) { |
| EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0U, Page)); |
| } else { |
| // There's a block straddling `GroupBeg`, it's supposed to only |
| // increment the counter and we expect it should be 1 less |
| // (exclude the straddling one) than the total blocks on the page. |
| EXPECT_EQ(Context.PageMap.get(/*Region=*/0U, Page), |
| Pages[Page] - 1); |
| } |
| } else { |
| EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0, Page)); |
| } |
| } |
| } |
| |
| if (Block == GroupEnd) |
| continue; |
| |
| // Examine the last block which sits across the group boundary. |
| if (Block + BlockSize == RegionSize) { |
| // This is the last block in the region, it's supposed to mark all the |
| // pages as all counted. |
| for (scudo::uptr Page = Block / PageSize; |
| Page <= (Block + BlockSize - 1) / PageSize; ++Page) { |
| EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0, Page)); |
| } |
| } else { |
| for (scudo::uptr Page = Block / PageSize; |
| Page <= (Block + BlockSize - 1) / PageSize; ++Page) { |
| if (Page <= (GroupEnd - 1) / PageSize) |
| EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0, Page)); |
| else |
| EXPECT_EQ(Context.PageMap.get(/*Region=*/0U, Page), 1U); |
| } |
| } |
| |
| const scudo::uptr FirstUncountedPage = |
| scudo::roundUp(Block + BlockSize, PageSize); |
| for (scudo::uptr Page = FirstUncountedPage; |
| Page <= RoundedRegionSize / PageSize; ++Page) { |
| EXPECT_EQ(Context.PageMap.get(/*Region=*/0U, Page), 0U); |
| } |
| } // Iterate each Group |
| |
| // Release the entire region. This is to ensure the last page is counted. |
| scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U, |
| /*ReleaseSize=*/RegionSize); |
| Context.markRangeAsAllCounted(/*From=*/0U, /*To=*/RegionSize, /*Base=*/0, |
| /*RegionIndex=*/0, RegionSize); |
| for (scudo::uptr Page = 0; Page < RoundedRegionSize / PageSize; ++Page) |
| EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0, Page)); |
| } // Iterate each size class |
| } |
| |
| template <class SizeClassMap> void testReleasePartialRegion() { |
| typedef FreeBatch<SizeClassMap> Batch; |
| const scudo::uptr PageSize = scudo::getPageSizeCached(); |
| const scudo::uptr ReleaseBase = PageSize; |
| const scudo::uptr BasePageOffset = ReleaseBase / PageSize; |
| |
| for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) { |
| // In the following, we want to ensure the region includes at least 2 pages |
| // and we will release all the pages except the first one. The handling of |
| // the last block is tricky, so we always test the case that includes the |
| // last block. |
| const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I); |
| const scudo::uptr RegionSize = |
| scudo::roundUpSlow(scudo::roundUp(BlockSize, PageSize) * 2, BlockSize) + |
| BlockSize; |
| const scudo::uptr RoundedRegionSize = scudo::roundUp(RegionSize, PageSize); |
| |
| scudo::SinglyLinkedList<Batch> FreeList; |
| FreeList.clear(); |
| |
| // Skip the blocks in the first page and add the remaining. |
| std::vector<scudo::uptr> Pages(RoundedRegionSize / PageSize, 0); |
| for (scudo::uptr Block = scudo::roundUpSlow(PageSize, BlockSize); |
| Block + BlockSize <= RoundedRegionSize; Block += BlockSize) { |
| for (scudo::uptr Page = Block / PageSize; |
| Page <= (Block + BlockSize - 1) / PageSize; ++Page) { |
| ASSERT_LT(Page, Pages.size()); |
| ++Pages[Page]; |
| } |
| } |
| |
| // This follows the logic how we count the last page. It should be |
| // consistent with how markFreeBlocksInRegion() handles the last block. |
| if (RoundedRegionSize % BlockSize != 0) |
| ++Pages.back(); |
| |
| Batch *CurrentBatch = nullptr; |
| for (scudo::uptr Block = scudo::roundUpSlow(PageSize, BlockSize); |
| Block < RegionSize; Block += BlockSize) { |
| if (CurrentBatch == nullptr || |
| CurrentBatch->getCount() == Batch::MaxCount) { |
| CurrentBatch = new Batch; |
| CurrentBatch->clear(); |
| FreeList.push_back(CurrentBatch); |
| } |
| CurrentBatch->add(Block); |
| } |
| |
| auto VerifyReleaseToOs = [&](scudo::PageReleaseContext &Context) { |
| auto SkipRegion = [](UNUSED scudo::uptr RegionIndex) { return false; }; |
| ReleasedPagesRecorder Recorder(ReleaseBase); |
| releaseFreeMemoryToOS(Context, Recorder, SkipRegion); |
| const scudo::uptr FirstBlock = scudo::roundUpSlow(PageSize, BlockSize); |
| |
| for (scudo::uptr P = 0; P < RoundedRegionSize; P += PageSize) { |
| if (P < FirstBlock) { |
| // If FirstBlock is not aligned with page boundary, the first touched |
| // page will not be released either. |
| EXPECT_TRUE(Recorder.ReportedPages.find(P) == |
| Recorder.ReportedPages.end()); |
| } else { |
| EXPECT_TRUE(Recorder.ReportedPages.find(P) != |
| Recorder.ReportedPages.end()); |
| } |
| } |
| }; |
| |
| // Test marking by visiting each block. |
| { |
| auto DecompactPtr = [](scudo::uptr P) { return P; }; |
| scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U, |
| /*ReleaseSize=*/RegionSize - PageSize, |
| ReleaseBase); |
| Context.markFreeBlocksInRegion(FreeList, DecompactPtr, /*Base=*/0U, |
| /*RegionIndex=*/0, RegionSize, |
| /*MayContainLastBlockInRegion=*/true); |
| for (const Batch &It : FreeList) { |
| for (scudo::u16 I = 0; I < It.getCount(); I++) { |
| scudo::uptr Block = It.get(I); |
| for (scudo::uptr Page = Block / PageSize; |
| Page <= (Block + BlockSize - 1) / PageSize; ++Page) { |
| EXPECT_EQ(Pages[Page], Context.PageMap.get(/*Region=*/0U, |
| Page - BasePageOffset)); |
| } |
| } |
| } |
| |
| VerifyReleaseToOs(Context); |
| } |
| |
| // Test range marking. |
| { |
| scudo::PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U, |
| /*ReleaseSize=*/RegionSize - PageSize, |
| ReleaseBase); |
| Context.markRangeAsAllCounted(ReleaseBase, RegionSize, /*Base=*/0U, |
| /*RegionIndex=*/0, RegionSize); |
| for (scudo::uptr Page = ReleaseBase / PageSize; |
| Page < RoundedRegionSize / PageSize; ++Page) { |
| if (Context.PageMap.get(/*Region=*/0, Page - BasePageOffset) != |
| Pages[Page]) { |
| EXPECT_TRUE(Context.PageMap.isAllCounted(/*Region=*/0, |
| Page - BasePageOffset)); |
| } |
| } |
| |
| VerifyReleaseToOs(Context); |
| } |
| |
| // Check the buffer size of PageMap. |
| { |
| scudo::PageReleaseContext Full(BlockSize, /*NumberOfRegions=*/1U, |
| /*ReleaseSize=*/RegionSize); |
| Full.ensurePageMapAllocated(); |
| scudo::PageReleaseContext Partial(BlockSize, /*NumberOfRegions=*/1U, |
| /*ReleaseSize=*/RegionSize - PageSize, |
| ReleaseBase); |
| Partial.ensurePageMapAllocated(); |
| |
| EXPECT_GE(Full.PageMap.getBufferSize(), Partial.PageMap.getBufferSize()); |
| } |
| |
| while (!FreeList.empty()) { |
| CurrentBatch = FreeList.front(); |
| FreeList.pop_front(); |
| delete CurrentBatch; |
| } |
| } // Iterate each size class |
| } |
| |
| TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSDefault) { |
| testReleaseFreeMemoryToOS<scudo::DefaultSizeClassMap>(); |
| } |
| |
| TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSAndroid) { |
| testReleaseFreeMemoryToOS<scudo::AndroidSizeClassMap>(); |
| } |
| |
| TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSSvelte) { |
| testReleaseFreeMemoryToOS<scudo::SvelteSizeClassMap>(); |
| } |
| |
| TEST(ScudoReleaseTest, PageMapMarkRange) { |
| testPageMapMarkRange<scudo::DefaultSizeClassMap>(); |
| testPageMapMarkRange<scudo::AndroidSizeClassMap>(); |
| testPageMapMarkRange<scudo::FuchsiaSizeClassMap>(); |
| testPageMapMarkRange<scudo::SvelteSizeClassMap>(); |
| } |
| |
| TEST(ScudoReleaseTest, ReleasePartialRegion) { |
| testReleasePartialRegion<scudo::DefaultSizeClassMap>(); |
| testReleasePartialRegion<scudo::AndroidSizeClassMap>(); |
| testReleasePartialRegion<scudo::FuchsiaSizeClassMap>(); |
| testReleasePartialRegion<scudo::SvelteSizeClassMap>(); |
| } |
| |
| TEST(ScudoReleaseTest, BufferPool) { |
| constexpr scudo::uptr StaticBufferCount = SCUDO_WORDSIZE - 1; |
| constexpr scudo::uptr StaticBufferSize = 512U; |
| |
| // Allocate the buffer pool on the heap because it is quite large (slightly |
| // more than StaticBufferCount * StaticBufferSize * sizeof(uptr)) and it may |
| // not fit in the stack on some platforms. |
| using BufferPool = scudo::BufferPool<StaticBufferCount, StaticBufferSize>; |
| std::unique_ptr<BufferPool> Pool(new BufferPool()); |
| |
| std::vector<std::pair<scudo::uptr *, scudo::uptr>> Buffers; |
| for (scudo::uptr I = 0; I < StaticBufferCount; ++I) { |
| scudo::uptr *P = Pool->getBuffer(StaticBufferSize); |
| EXPECT_TRUE(Pool->isStaticBufferTestOnly(P, StaticBufferSize)); |
| Buffers.emplace_back(P, StaticBufferSize); |
| } |
| |
| // The static buffer is supposed to be used up. |
| scudo::uptr *P = Pool->getBuffer(StaticBufferSize); |
| EXPECT_FALSE(Pool->isStaticBufferTestOnly(P, StaticBufferSize)); |
| |
| Pool->releaseBuffer(P, StaticBufferSize); |
| for (auto &Buffer : Buffers) |
| Pool->releaseBuffer(Buffer.first, Buffer.second); |
| } |