13cab2bb3Spatrick //===-- release_test.cpp ----------------------------------------*- C++ -*-===//
23cab2bb3Spatrick //
33cab2bb3Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43cab2bb3Spatrick // See https://llvm.org/LICENSE.txt for license information.
53cab2bb3Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63cab2bb3Spatrick //
73cab2bb3Spatrick //===----------------------------------------------------------------------===//
83cab2bb3Spatrick
93cab2bb3Spatrick #include "tests/scudo_unit_test.h"
103cab2bb3Spatrick
113cab2bb3Spatrick #include "list.h"
123cab2bb3Spatrick #include "release.h"
133cab2bb3Spatrick #include "size_class_map.h"
143cab2bb3Spatrick
153cab2bb3Spatrick #include <string.h>
163cab2bb3Spatrick
173cab2bb3Spatrick #include <algorithm>
183cab2bb3Spatrick #include <random>
193cab2bb3Spatrick #include <set>
203cab2bb3Spatrick
TEST(ScudoReleaseTest,RegionPageMap)21*810390e3Srobert TEST(ScudoReleaseTest, RegionPageMap) {
223cab2bb3Spatrick for (scudo::uptr I = 0; I < SCUDO_WORDSIZE; I++) {
233cab2bb3Spatrick // Various valid counter's max values packed into one word.
24*810390e3Srobert scudo::RegionPageMap PageMap2N(1U, 1U, 1UL << I);
25*810390e3Srobert EXPECT_EQ(sizeof(scudo::uptr), PageMap2N.getBufferSize());
263cab2bb3Spatrick // Check the "all bit set" values too.
27*810390e3Srobert scudo::RegionPageMap PageMap2N1_1(1U, 1U, ~0UL >> I);
28*810390e3Srobert EXPECT_EQ(sizeof(scudo::uptr), PageMap2N1_1.getBufferSize());
293cab2bb3Spatrick // Verify the packing ratio, the counter is Expected to be packed into the
303cab2bb3Spatrick // closest power of 2 bits.
31*810390e3Srobert scudo::RegionPageMap PageMap(1U, SCUDO_WORDSIZE, 1UL << I);
323cab2bb3Spatrick EXPECT_EQ(sizeof(scudo::uptr) * scudo::roundUpToPowerOfTwo(I + 1),
33*810390e3Srobert PageMap.getBufferSize());
343cab2bb3Spatrick }
353cab2bb3Spatrick
363cab2bb3Spatrick // Go through 1, 2, 4, 8, .. {32,64} bits per counter.
373cab2bb3Spatrick for (scudo::uptr I = 0; (SCUDO_WORDSIZE >> I) != 0; I++) {
383cab2bb3Spatrick // Make sure counters request one memory page for the buffer.
393cab2bb3Spatrick const scudo::uptr NumCounters =
403cab2bb3Spatrick (scudo::getPageSizeCached() / 8) * (SCUDO_WORDSIZE >> I);
41*810390e3Srobert scudo::RegionPageMap PageMap(1U, NumCounters,
42d89ec533Spatrick 1UL << ((1UL << I) - 1));
43*810390e3Srobert PageMap.inc(0U, 0U);
443cab2bb3Spatrick for (scudo::uptr C = 1; C < NumCounters - 1; C++) {
45*810390e3Srobert EXPECT_EQ(0UL, PageMap.get(0U, C));
46*810390e3Srobert PageMap.inc(0U, C);
47*810390e3Srobert EXPECT_EQ(1UL, PageMap.get(0U, C - 1));
483cab2bb3Spatrick }
49*810390e3Srobert EXPECT_EQ(0UL, PageMap.get(0U, NumCounters - 1));
50*810390e3Srobert PageMap.inc(0U, NumCounters - 1);
513cab2bb3Spatrick if (I > 0) {
52*810390e3Srobert PageMap.incRange(0u, 0U, NumCounters - 1);
533cab2bb3Spatrick for (scudo::uptr C = 0; C < NumCounters; C++)
54*810390e3Srobert EXPECT_EQ(2UL, PageMap.get(0U, C));
553cab2bb3Spatrick }
563cab2bb3Spatrick }
573cab2bb3Spatrick }
583cab2bb3Spatrick
593cab2bb3Spatrick class StringRangeRecorder {
603cab2bb3Spatrick public:
613cab2bb3Spatrick std::string ReportedPages;
623cab2bb3Spatrick
StringRangeRecorder()633cab2bb3Spatrick StringRangeRecorder()
643cab2bb3Spatrick : PageSizeScaledLog(scudo::getLog2(scudo::getPageSizeCached())) {}
653cab2bb3Spatrick
releasePageRangeToOS(scudo::uptr From,scudo::uptr To)663cab2bb3Spatrick void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) {
673cab2bb3Spatrick From >>= PageSizeScaledLog;
683cab2bb3Spatrick To >>= PageSizeScaledLog;
693cab2bb3Spatrick EXPECT_LT(From, To);
703cab2bb3Spatrick if (!ReportedPages.empty())
713cab2bb3Spatrick EXPECT_LT(LastPageReported, From);
723cab2bb3Spatrick ReportedPages.append(From - LastPageReported, '.');
733cab2bb3Spatrick ReportedPages.append(To - From, 'x');
743cab2bb3Spatrick LastPageReported = To;
753cab2bb3Spatrick }
763cab2bb3Spatrick
773cab2bb3Spatrick private:
783cab2bb3Spatrick const scudo::uptr PageSizeScaledLog;
793cab2bb3Spatrick scudo::uptr LastPageReported = 0;
803cab2bb3Spatrick };
813cab2bb3Spatrick
TEST(ScudoReleaseTest,FreePagesRangeTracker)823cab2bb3Spatrick TEST(ScudoReleaseTest, FreePagesRangeTracker) {
833cab2bb3Spatrick // 'x' denotes a page to be released, '.' denotes a page to be kept around.
843cab2bb3Spatrick const char *TestCases[] = {
853cab2bb3Spatrick "",
863cab2bb3Spatrick ".",
873cab2bb3Spatrick "x",
883cab2bb3Spatrick "........",
893cab2bb3Spatrick "xxxxxxxxxxx",
903cab2bb3Spatrick "..............xxxxx",
913cab2bb3Spatrick "xxxxxxxxxxxxxxxxxx.....",
923cab2bb3Spatrick "......xxxxxxxx........",
933cab2bb3Spatrick "xxx..........xxxxxxxxxxxxxxx",
943cab2bb3Spatrick "......xxxx....xxxx........",
953cab2bb3Spatrick "xxx..........xxxxxxxx....xxxxxxx",
963cab2bb3Spatrick "x.x.x.x.x.x.x.x.x.x.x.x.",
973cab2bb3Spatrick ".x.x.x.x.x.x.x.x.x.x.x.x",
983cab2bb3Spatrick ".x.x.x.x.x.x.x.x.x.x.x.x.",
993cab2bb3Spatrick "x.x.x.x.x.x.x.x.x.x.x.x.x",
1003cab2bb3Spatrick };
1013cab2bb3Spatrick typedef scudo::FreePagesRangeTracker<StringRangeRecorder> RangeTracker;
1023cab2bb3Spatrick
1033cab2bb3Spatrick for (auto TestCase : TestCases) {
1043cab2bb3Spatrick StringRangeRecorder Recorder;
105*810390e3Srobert RangeTracker Tracker(Recorder);
1063cab2bb3Spatrick for (scudo::uptr I = 0; TestCase[I] != 0; I++)
1073cab2bb3Spatrick Tracker.processNextPage(TestCase[I] == 'x');
1083cab2bb3Spatrick Tracker.finish();
1093cab2bb3Spatrick // Strip trailing '.'-pages before comparing the results as they are not
1103cab2bb3Spatrick // going to be reported to range_recorder anyway.
1113cab2bb3Spatrick const char *LastX = strrchr(TestCase, 'x');
1123cab2bb3Spatrick std::string Expected(TestCase,
1133cab2bb3Spatrick LastX == nullptr ? 0 : (LastX - TestCase + 1));
1143cab2bb3Spatrick EXPECT_STREQ(Expected.c_str(), Recorder.ReportedPages.c_str());
1153cab2bb3Spatrick }
1163cab2bb3Spatrick }
1173cab2bb3Spatrick
1183cab2bb3Spatrick class ReleasedPagesRecorder {
1193cab2bb3Spatrick public:
1203cab2bb3Spatrick std::set<scudo::uptr> ReportedPages;
1213cab2bb3Spatrick
releasePageRangeToOS(scudo::uptr From,scudo::uptr To)1223cab2bb3Spatrick void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) {
1233cab2bb3Spatrick const scudo::uptr PageSize = scudo::getPageSizeCached();
1243cab2bb3Spatrick for (scudo::uptr I = From; I < To; I += PageSize)
1253cab2bb3Spatrick ReportedPages.insert(I);
1263cab2bb3Spatrick }
127d89ec533Spatrick
getBase() const128d89ec533Spatrick scudo::uptr getBase() const { return 0; }
1293cab2bb3Spatrick };
1303cab2bb3Spatrick
1313cab2bb3Spatrick // Simplified version of a TransferBatch.
1323cab2bb3Spatrick template <class SizeClassMap> struct FreeBatch {
133*810390e3Srobert static const scudo::u16 MaxCount = SizeClassMap::MaxNumCachedHint;
clearFreeBatch1343cab2bb3Spatrick void clear() { Count = 0; }
addFreeBatch1353cab2bb3Spatrick void add(scudo::uptr P) {
1363cab2bb3Spatrick DCHECK_LT(Count, MaxCount);
1373cab2bb3Spatrick Batch[Count++] = P;
1383cab2bb3Spatrick }
getCountFreeBatch139*810390e3Srobert scudo::u16 getCount() const { return Count; }
getFreeBatch140*810390e3Srobert scudo::uptr get(scudo::u16 I) const {
1413cab2bb3Spatrick DCHECK_LE(I, Count);
1423cab2bb3Spatrick return Batch[I];
1433cab2bb3Spatrick }
1443cab2bb3Spatrick FreeBatch *Next;
1453cab2bb3Spatrick
1463cab2bb3Spatrick private:
1473cab2bb3Spatrick scudo::uptr Batch[MaxCount];
148*810390e3Srobert scudo::u16 Count;
1493cab2bb3Spatrick };
1503cab2bb3Spatrick
testReleaseFreeMemoryToOS()1513cab2bb3Spatrick template <class SizeClassMap> void testReleaseFreeMemoryToOS() {
1523cab2bb3Spatrick typedef FreeBatch<SizeClassMap> Batch;
1531f9cb04fSpatrick const scudo::uptr PagesCount = 1024;
1543cab2bb3Spatrick const scudo::uptr PageSize = scudo::getPageSizeCached();
155*810390e3Srobert const scudo::uptr PageSizeLog = scudo::getLog2(PageSize);
1563cab2bb3Spatrick std::mt19937 R;
1573cab2bb3Spatrick scudo::u32 RandState = 42;
1583cab2bb3Spatrick
1593cab2bb3Spatrick for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) {
1603cab2bb3Spatrick const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);
1611f9cb04fSpatrick const scudo::uptr MaxBlocks = PagesCount * PageSize / BlockSize;
1623cab2bb3Spatrick
1633cab2bb3Spatrick // Generate the random free list.
1643cab2bb3Spatrick std::vector<scudo::uptr> FreeArray;
1653cab2bb3Spatrick bool InFreeRange = false;
1663cab2bb3Spatrick scudo::uptr CurrentRangeEnd = 0;
1673cab2bb3Spatrick for (scudo::uptr I = 0; I < MaxBlocks; I++) {
1683cab2bb3Spatrick if (I == CurrentRangeEnd) {
1693cab2bb3Spatrick InFreeRange = (scudo::getRandomU32(&RandState) & 1U) == 1;
1703cab2bb3Spatrick CurrentRangeEnd += (scudo::getRandomU32(&RandState) & 0x7f) + 1;
1713cab2bb3Spatrick }
1723cab2bb3Spatrick if (InFreeRange)
1733cab2bb3Spatrick FreeArray.push_back(I * BlockSize);
1743cab2bb3Spatrick }
1753cab2bb3Spatrick if (FreeArray.empty())
1763cab2bb3Spatrick continue;
1773cab2bb3Spatrick // Shuffle the array to ensure that the order is irrelevant.
1783cab2bb3Spatrick std::shuffle(FreeArray.begin(), FreeArray.end(), R);
1793cab2bb3Spatrick
1803cab2bb3Spatrick // Build the FreeList from the FreeArray.
1813cab2bb3Spatrick scudo::SinglyLinkedList<Batch> FreeList;
1823cab2bb3Spatrick FreeList.clear();
1833cab2bb3Spatrick Batch *CurrentBatch = nullptr;
1843cab2bb3Spatrick for (auto const &Block : FreeArray) {
1853cab2bb3Spatrick if (!CurrentBatch) {
1863cab2bb3Spatrick CurrentBatch = new Batch;
1873cab2bb3Spatrick CurrentBatch->clear();
1883cab2bb3Spatrick FreeList.push_back(CurrentBatch);
1893cab2bb3Spatrick }
1903cab2bb3Spatrick CurrentBatch->add(Block);
1913cab2bb3Spatrick if (CurrentBatch->getCount() == Batch::MaxCount)
1923cab2bb3Spatrick CurrentBatch = nullptr;
1933cab2bb3Spatrick }
1943cab2bb3Spatrick
1953cab2bb3Spatrick // Release the memory.
196d89ec533Spatrick auto SkipRegion = [](UNUSED scudo::uptr RegionIndex) { return false; };
197d89ec533Spatrick auto DecompactPtr = [](scudo::uptr P) { return P; };
1983cab2bb3Spatrick ReleasedPagesRecorder Recorder;
199*810390e3Srobert scudo::PageReleaseContext Context(BlockSize,
200*810390e3Srobert /*RegionSize=*/MaxBlocks * BlockSize,
201*810390e3Srobert /*NumberOfRegions=*/1U);
202*810390e3Srobert ASSERT_FALSE(Context.hasBlockMarked());
203*810390e3Srobert Context.markFreeBlocks(FreeList, DecompactPtr, Recorder.getBase());
204*810390e3Srobert ASSERT_TRUE(Context.hasBlockMarked());
205*810390e3Srobert releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
206*810390e3Srobert scudo::RegionPageMap &PageMap = Context.PageMap;
2073cab2bb3Spatrick
2083cab2bb3Spatrick // Verify that there are no released pages touched by used chunks and all
2093cab2bb3Spatrick // ranges of free chunks big enough to contain the entire memory pages had
2103cab2bb3Spatrick // these pages released.
2113cab2bb3Spatrick scudo::uptr VerifiedReleasedPages = 0;
2123cab2bb3Spatrick std::set<scudo::uptr> FreeBlocks(FreeArray.begin(), FreeArray.end());
2133cab2bb3Spatrick
2143cab2bb3Spatrick scudo::uptr CurrentBlock = 0;
2153cab2bb3Spatrick InFreeRange = false;
2163cab2bb3Spatrick scudo::uptr CurrentFreeRangeStart = 0;
2171f9cb04fSpatrick for (scudo::uptr I = 0; I < MaxBlocks; I++) {
2183cab2bb3Spatrick const bool IsFreeBlock =
2193cab2bb3Spatrick FreeBlocks.find(CurrentBlock) != FreeBlocks.end();
2203cab2bb3Spatrick if (IsFreeBlock) {
2213cab2bb3Spatrick if (!InFreeRange) {
2223cab2bb3Spatrick InFreeRange = true;
2233cab2bb3Spatrick CurrentFreeRangeStart = CurrentBlock;
2243cab2bb3Spatrick }
2253cab2bb3Spatrick } else {
2263cab2bb3Spatrick // Verify that this used chunk does not touch any released page.
2273cab2bb3Spatrick const scudo::uptr StartPage = CurrentBlock / PageSize;
2283cab2bb3Spatrick const scudo::uptr EndPage = (CurrentBlock + BlockSize - 1) / PageSize;
2293cab2bb3Spatrick for (scudo::uptr J = StartPage; J <= EndPage; J++) {
2303cab2bb3Spatrick const bool PageReleased = Recorder.ReportedPages.find(J * PageSize) !=
2313cab2bb3Spatrick Recorder.ReportedPages.end();
2323cab2bb3Spatrick EXPECT_EQ(false, PageReleased);
233*810390e3Srobert EXPECT_EQ(false,
234*810390e3Srobert PageMap.isAllCounted(0, (J * PageSize) >> PageSizeLog));
2353cab2bb3Spatrick }
2363cab2bb3Spatrick
2373cab2bb3Spatrick if (InFreeRange) {
2383cab2bb3Spatrick InFreeRange = false;
2393cab2bb3Spatrick // Verify that all entire memory pages covered by this range of free
2403cab2bb3Spatrick // chunks were released.
2413cab2bb3Spatrick scudo::uptr P = scudo::roundUpTo(CurrentFreeRangeStart, PageSize);
2423cab2bb3Spatrick while (P + PageSize <= CurrentBlock) {
2433cab2bb3Spatrick const bool PageReleased =
2443cab2bb3Spatrick Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end();
2453cab2bb3Spatrick EXPECT_EQ(true, PageReleased);
246*810390e3Srobert EXPECT_EQ(true, PageMap.isAllCounted(0, P >> PageSizeLog));
2473cab2bb3Spatrick VerifiedReleasedPages++;
2483cab2bb3Spatrick P += PageSize;
2493cab2bb3Spatrick }
2503cab2bb3Spatrick }
2513cab2bb3Spatrick }
2523cab2bb3Spatrick
2533cab2bb3Spatrick CurrentBlock += BlockSize;
2543cab2bb3Spatrick }
2553cab2bb3Spatrick
2561f9cb04fSpatrick if (InFreeRange) {
2571f9cb04fSpatrick scudo::uptr P = scudo::roundUpTo(CurrentFreeRangeStart, PageSize);
2581f9cb04fSpatrick const scudo::uptr EndPage =
2591f9cb04fSpatrick scudo::roundUpTo(MaxBlocks * BlockSize, PageSize);
2601f9cb04fSpatrick while (P + PageSize <= EndPage) {
2611f9cb04fSpatrick const bool PageReleased =
2621f9cb04fSpatrick Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end();
2631f9cb04fSpatrick EXPECT_EQ(true, PageReleased);
264*810390e3Srobert EXPECT_EQ(true, PageMap.isAllCounted(0, P >> PageSizeLog));
2651f9cb04fSpatrick VerifiedReleasedPages++;
2661f9cb04fSpatrick P += PageSize;
2671f9cb04fSpatrick }
2681f9cb04fSpatrick }
2691f9cb04fSpatrick
2703cab2bb3Spatrick EXPECT_EQ(Recorder.ReportedPages.size(), VerifiedReleasedPages);
2713cab2bb3Spatrick
2723cab2bb3Spatrick while (!FreeList.empty()) {
2733cab2bb3Spatrick CurrentBatch = FreeList.front();
2743cab2bb3Spatrick FreeList.pop_front();
2753cab2bb3Spatrick delete CurrentBatch;
2763cab2bb3Spatrick }
2773cab2bb3Spatrick }
2783cab2bb3Spatrick }
2793cab2bb3Spatrick
TEST(ScudoReleaseTest,ReleaseFreeMemoryToOSDefault)2803cab2bb3Spatrick TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSDefault) {
2813cab2bb3Spatrick testReleaseFreeMemoryToOS<scudo::DefaultSizeClassMap>();
2823cab2bb3Spatrick }
2833cab2bb3Spatrick
TEST(ScudoReleaseTest,ReleaseFreeMemoryToOSAndroid)2843cab2bb3Spatrick TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSAndroid) {
2853cab2bb3Spatrick testReleaseFreeMemoryToOS<scudo::AndroidSizeClassMap>();
2863cab2bb3Spatrick }
2873cab2bb3Spatrick
TEST(ScudoReleaseTest,ReleaseFreeMemoryToOSSvelte)2883cab2bb3Spatrick TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSSvelte) {
2893cab2bb3Spatrick testReleaseFreeMemoryToOS<scudo::SvelteSizeClassMap>();
2903cab2bb3Spatrick }
291