xref: /openbsd-src/gnu/llvm/compiler-rt/lib/scudo/standalone/tests/release_test.cpp (revision 810390e339a5425391477d5d41c78d7cab2424ac)
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