xref: /openbsd-src/gnu/llvm/compiler-rt/lib/scudo/standalone/tests/release_test.cpp (revision 5a38ef86d0b61900239c7913d24a05e7b88a58f0)
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 
21 TEST(ScudoReleaseTest, PackedCounterArray) {
22   for (scudo::uptr I = 0; I < SCUDO_WORDSIZE; I++) {
23     // Various valid counter's max values packed into one word.
24     scudo::PackedCounterArray Counters2N(1U, 1U, 1UL << I);
25     EXPECT_EQ(sizeof(scudo::uptr), Counters2N.getBufferSize());
26     // Check the "all bit set" values too.
27     scudo::PackedCounterArray Counters2N1_1(1U, 1U, ~0UL >> I);
28     EXPECT_EQ(sizeof(scudo::uptr), Counters2N1_1.getBufferSize());
29     // Verify the packing ratio, the counter is Expected to be packed into the
30     // closest power of 2 bits.
31     scudo::PackedCounterArray Counters(1U, SCUDO_WORDSIZE, 1UL << I);
32     EXPECT_EQ(sizeof(scudo::uptr) * scudo::roundUpToPowerOfTwo(I + 1),
33               Counters.getBufferSize());
34   }
35 
36   // Go through 1, 2, 4, 8, .. {32,64} bits per counter.
37   for (scudo::uptr I = 0; (SCUDO_WORDSIZE >> I) != 0; I++) {
38     // Make sure counters request one memory page for the buffer.
39     const scudo::uptr NumCounters =
40         (scudo::getPageSizeCached() / 8) * (SCUDO_WORDSIZE >> I);
41     scudo::PackedCounterArray Counters(1U, NumCounters,
42                                        1UL << ((1UL << I) - 1));
43     Counters.inc(0U, 0U);
44     for (scudo::uptr C = 1; C < NumCounters - 1; C++) {
45       EXPECT_EQ(0UL, Counters.get(0U, C));
46       Counters.inc(0U, C);
47       EXPECT_EQ(1UL, Counters.get(0U, C - 1));
48     }
49     EXPECT_EQ(0UL, Counters.get(0U, NumCounters - 1));
50     Counters.inc(0U, NumCounters - 1);
51     if (I > 0) {
52       Counters.incRange(0u, 0U, NumCounters - 1);
53       for (scudo::uptr C = 0; C < NumCounters; C++)
54         EXPECT_EQ(2UL, Counters.get(0U, C));
55     }
56   }
57 }
58 
59 class StringRangeRecorder {
60 public:
61   std::string ReportedPages;
62 
63   StringRangeRecorder()
64       : PageSizeScaledLog(scudo::getLog2(scudo::getPageSizeCached())) {}
65 
66   void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) {
67     From >>= PageSizeScaledLog;
68     To >>= PageSizeScaledLog;
69     EXPECT_LT(From, To);
70     if (!ReportedPages.empty())
71       EXPECT_LT(LastPageReported, From);
72     ReportedPages.append(From - LastPageReported, '.');
73     ReportedPages.append(To - From, 'x');
74     LastPageReported = To;
75   }
76 
77 private:
78   const scudo::uptr PageSizeScaledLog;
79   scudo::uptr LastPageReported = 0;
80 };
81 
82 TEST(ScudoReleaseTest, FreePagesRangeTracker) {
83   // 'x' denotes a page to be released, '.' denotes a page to be kept around.
84   const char *TestCases[] = {
85       "",
86       ".",
87       "x",
88       "........",
89       "xxxxxxxxxxx",
90       "..............xxxxx",
91       "xxxxxxxxxxxxxxxxxx.....",
92       "......xxxxxxxx........",
93       "xxx..........xxxxxxxxxxxxxxx",
94       "......xxxx....xxxx........",
95       "xxx..........xxxxxxxx....xxxxxxx",
96       "x.x.x.x.x.x.x.x.x.x.x.x.",
97       ".x.x.x.x.x.x.x.x.x.x.x.x",
98       ".x.x.x.x.x.x.x.x.x.x.x.x.",
99       "x.x.x.x.x.x.x.x.x.x.x.x.x",
100   };
101   typedef scudo::FreePagesRangeTracker<StringRangeRecorder> RangeTracker;
102 
103   for (auto TestCase : TestCases) {
104     StringRangeRecorder Recorder;
105     RangeTracker Tracker(&Recorder);
106     for (scudo::uptr I = 0; TestCase[I] != 0; I++)
107       Tracker.processNextPage(TestCase[I] == 'x');
108     Tracker.finish();
109     // Strip trailing '.'-pages before comparing the results as they are not
110     // going to be reported to range_recorder anyway.
111     const char *LastX = strrchr(TestCase, 'x');
112     std::string Expected(TestCase,
113                          LastX == nullptr ? 0 : (LastX - TestCase + 1));
114     EXPECT_STREQ(Expected.c_str(), Recorder.ReportedPages.c_str());
115   }
116 }
117 
118 class ReleasedPagesRecorder {
119 public:
120   std::set<scudo::uptr> ReportedPages;
121 
122   void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) {
123     const scudo::uptr PageSize = scudo::getPageSizeCached();
124     for (scudo::uptr I = From; I < To; I += PageSize)
125       ReportedPages.insert(I);
126   }
127 
128   scudo::uptr getBase() const { return 0; }
129 };
130 
131 // Simplified version of a TransferBatch.
132 template <class SizeClassMap> struct FreeBatch {
133   static const scudo::u32 MaxCount = SizeClassMap::MaxNumCachedHint;
134   void clear() { Count = 0; }
135   void add(scudo::uptr P) {
136     DCHECK_LT(Count, MaxCount);
137     Batch[Count++] = P;
138   }
139   scudo::u32 getCount() const { return Count; }
140   scudo::uptr get(scudo::u32 I) const {
141     DCHECK_LE(I, Count);
142     return Batch[I];
143   }
144   FreeBatch *Next;
145 
146 private:
147   scudo::u32 Count;
148   scudo::uptr Batch[MaxCount];
149 };
150 
151 template <class SizeClassMap> void testReleaseFreeMemoryToOS() {
152   typedef FreeBatch<SizeClassMap> Batch;
153   const scudo::uptr PagesCount = 1024;
154   const scudo::uptr PageSize = scudo::getPageSizeCached();
155   std::mt19937 R;
156   scudo::u32 RandState = 42;
157 
158   for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) {
159     const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);
160     const scudo::uptr MaxBlocks = PagesCount * PageSize / BlockSize;
161 
162     // Generate the random free list.
163     std::vector<scudo::uptr> FreeArray;
164     bool InFreeRange = false;
165     scudo::uptr CurrentRangeEnd = 0;
166     for (scudo::uptr I = 0; I < MaxBlocks; I++) {
167       if (I == CurrentRangeEnd) {
168         InFreeRange = (scudo::getRandomU32(&RandState) & 1U) == 1;
169         CurrentRangeEnd += (scudo::getRandomU32(&RandState) & 0x7f) + 1;
170       }
171       if (InFreeRange)
172         FreeArray.push_back(I * BlockSize);
173     }
174     if (FreeArray.empty())
175       continue;
176     // Shuffle the array to ensure that the order is irrelevant.
177     std::shuffle(FreeArray.begin(), FreeArray.end(), R);
178 
179     // Build the FreeList from the FreeArray.
180     scudo::SinglyLinkedList<Batch> FreeList;
181     FreeList.clear();
182     Batch *CurrentBatch = nullptr;
183     for (auto const &Block : FreeArray) {
184       if (!CurrentBatch) {
185         CurrentBatch = new Batch;
186         CurrentBatch->clear();
187         FreeList.push_back(CurrentBatch);
188       }
189       CurrentBatch->add(Block);
190       if (CurrentBatch->getCount() == Batch::MaxCount)
191         CurrentBatch = nullptr;
192     }
193 
194     // Release the memory.
195     auto SkipRegion = [](UNUSED scudo::uptr RegionIndex) { return false; };
196     auto DecompactPtr = [](scudo::uptr P) { return P; };
197     ReleasedPagesRecorder Recorder;
198     releaseFreeMemoryToOS(FreeList, MaxBlocks * BlockSize, 1U, BlockSize,
199                           &Recorder, DecompactPtr, SkipRegion);
200 
201     // Verify that there are no released pages touched by used chunks and all
202     // ranges of free chunks big enough to contain the entire memory pages had
203     // these pages released.
204     scudo::uptr VerifiedReleasedPages = 0;
205     std::set<scudo::uptr> FreeBlocks(FreeArray.begin(), FreeArray.end());
206 
207     scudo::uptr CurrentBlock = 0;
208     InFreeRange = false;
209     scudo::uptr CurrentFreeRangeStart = 0;
210     for (scudo::uptr I = 0; I < MaxBlocks; I++) {
211       const bool IsFreeBlock =
212           FreeBlocks.find(CurrentBlock) != FreeBlocks.end();
213       if (IsFreeBlock) {
214         if (!InFreeRange) {
215           InFreeRange = true;
216           CurrentFreeRangeStart = CurrentBlock;
217         }
218       } else {
219         // Verify that this used chunk does not touch any released page.
220         const scudo::uptr StartPage = CurrentBlock / PageSize;
221         const scudo::uptr EndPage = (CurrentBlock + BlockSize - 1) / PageSize;
222         for (scudo::uptr J = StartPage; J <= EndPage; J++) {
223           const bool PageReleased = Recorder.ReportedPages.find(J * PageSize) !=
224                                     Recorder.ReportedPages.end();
225           EXPECT_EQ(false, PageReleased);
226         }
227 
228         if (InFreeRange) {
229           InFreeRange = false;
230           // Verify that all entire memory pages covered by this range of free
231           // chunks were released.
232           scudo::uptr P = scudo::roundUpTo(CurrentFreeRangeStart, PageSize);
233           while (P + PageSize <= CurrentBlock) {
234             const bool PageReleased =
235                 Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end();
236             EXPECT_EQ(true, PageReleased);
237             VerifiedReleasedPages++;
238             P += PageSize;
239           }
240         }
241       }
242 
243       CurrentBlock += BlockSize;
244     }
245 
246     if (InFreeRange) {
247       scudo::uptr P = scudo::roundUpTo(CurrentFreeRangeStart, PageSize);
248       const scudo::uptr EndPage =
249           scudo::roundUpTo(MaxBlocks * BlockSize, PageSize);
250       while (P + PageSize <= EndPage) {
251         const bool PageReleased =
252             Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end();
253         EXPECT_EQ(true, PageReleased);
254         VerifiedReleasedPages++;
255         P += PageSize;
256       }
257     }
258 
259     EXPECT_EQ(Recorder.ReportedPages.size(), VerifiedReleasedPages);
260 
261     while (!FreeList.empty()) {
262       CurrentBatch = FreeList.front();
263       FreeList.pop_front();
264       delete CurrentBatch;
265     }
266   }
267 }
268 
269 TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSDefault) {
270   testReleaseFreeMemoryToOS<scudo::DefaultSizeClassMap>();
271 }
272 
273 TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSAndroid) {
274   testReleaseFreeMemoryToOS<scudo::AndroidSizeClassMap>();
275 }
276 
277 TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSSvelte) {
278   testReleaseFreeMemoryToOS<scudo::SvelteSizeClassMap>();
279 }
280