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 
129 // Simplified version of a TransferBatch.
130 template <class SizeClassMap> struct FreeBatch {
131   static const scudo::u32 MaxCount = SizeClassMap::MaxNumCachedHint;
132   void clear() { Count = 0; }
133   void add(scudo::uptr P) {
134     DCHECK_LT(Count, MaxCount);
135     Batch[Count++] = P;
136   }
137   scudo::u32 getCount() const { return Count; }
138   scudo::uptr get(scudo::u32 I) const {
139     DCHECK_LE(I, Count);
140     return Batch[I];
141   }
142   FreeBatch *Next;
143 
144 private:
145   scudo::u32 Count;
146   scudo::uptr Batch[MaxCount];
147 };
148 
149 template <class SizeClassMap> void testReleaseFreeMemoryToOS() {
150   typedef FreeBatch<SizeClassMap> Batch;
151   const scudo::uptr PagesCount = 1024;
152   const scudo::uptr PageSize = scudo::getPageSizeCached();
153   std::mt19937 R;
154   scudo::u32 RandState = 42;
155 
156   for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) {
157     const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I);
158     const scudo::uptr MaxBlocks = PagesCount * PageSize / BlockSize;
159 
160     // Generate the random free list.
161     std::vector<scudo::uptr> FreeArray;
162     bool InFreeRange = false;
163     scudo::uptr CurrentRangeEnd = 0;
164     for (scudo::uptr I = 0; I < MaxBlocks; I++) {
165       if (I == CurrentRangeEnd) {
166         InFreeRange = (scudo::getRandomU32(&RandState) & 1U) == 1;
167         CurrentRangeEnd += (scudo::getRandomU32(&RandState) & 0x7f) + 1;
168       }
169       if (InFreeRange)
170         FreeArray.push_back(I * BlockSize);
171     }
172     if (FreeArray.empty())
173       continue;
174     // Shuffle the array to ensure that the order is irrelevant.
175     std::shuffle(FreeArray.begin(), FreeArray.end(), R);
176 
177     // Build the FreeList from the FreeArray.
178     scudo::SinglyLinkedList<Batch> FreeList;
179     FreeList.clear();
180     Batch *CurrentBatch = nullptr;
181     for (auto const &Block : FreeArray) {
182       if (!CurrentBatch) {
183         CurrentBatch = new Batch;
184         CurrentBatch->clear();
185         FreeList.push_back(CurrentBatch);
186       }
187       CurrentBatch->add(Block);
188       if (CurrentBatch->getCount() == Batch::MaxCount)
189         CurrentBatch = nullptr;
190     }
191 
192     // Release the memory.
193     ReleasedPagesRecorder Recorder;
194     releaseFreeMemoryToOS(FreeList, 0, MaxBlocks * BlockSize, 1U, BlockSize,
195                           &Recorder);
196 
197     // Verify that there are no released pages touched by used chunks and all
198     // ranges of free chunks big enough to contain the entire memory pages had
199     // these pages released.
200     scudo::uptr VerifiedReleasedPages = 0;
201     std::set<scudo::uptr> FreeBlocks(FreeArray.begin(), FreeArray.end());
202 
203     scudo::uptr CurrentBlock = 0;
204     InFreeRange = false;
205     scudo::uptr CurrentFreeRangeStart = 0;
206     for (scudo::uptr I = 0; I < MaxBlocks; I++) {
207       const bool IsFreeBlock =
208           FreeBlocks.find(CurrentBlock) != FreeBlocks.end();
209       if (IsFreeBlock) {
210         if (!InFreeRange) {
211           InFreeRange = true;
212           CurrentFreeRangeStart = CurrentBlock;
213         }
214       } else {
215         // Verify that this used chunk does not touch any released page.
216         const scudo::uptr StartPage = CurrentBlock / PageSize;
217         const scudo::uptr EndPage = (CurrentBlock + BlockSize - 1) / PageSize;
218         for (scudo::uptr J = StartPage; J <= EndPage; J++) {
219           const bool PageReleased = Recorder.ReportedPages.find(J * PageSize) !=
220                                     Recorder.ReportedPages.end();
221           EXPECT_EQ(false, PageReleased);
222         }
223 
224         if (InFreeRange) {
225           InFreeRange = false;
226           // Verify that all entire memory pages covered by this range of free
227           // chunks were released.
228           scudo::uptr P = scudo::roundUpTo(CurrentFreeRangeStart, PageSize);
229           while (P + PageSize <= CurrentBlock) {
230             const bool PageReleased =
231                 Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end();
232             EXPECT_EQ(true, PageReleased);
233             VerifiedReleasedPages++;
234             P += PageSize;
235           }
236         }
237       }
238 
239       CurrentBlock += BlockSize;
240     }
241 
242     if (InFreeRange) {
243       scudo::uptr P = scudo::roundUpTo(CurrentFreeRangeStart, PageSize);
244       const scudo::uptr EndPage =
245           scudo::roundUpTo(MaxBlocks * BlockSize, PageSize);
246       while (P + PageSize <= EndPage) {
247         const bool PageReleased =
248             Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end();
249         EXPECT_EQ(true, PageReleased);
250         VerifiedReleasedPages++;
251         P += PageSize;
252       }
253     }
254 
255     EXPECT_EQ(Recorder.ReportedPages.size(), VerifiedReleasedPages);
256 
257     while (!FreeList.empty()) {
258       CurrentBatch = FreeList.front();
259       FreeList.pop_front();
260       delete CurrentBatch;
261     }
262   }
263 }
264 
265 TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSDefault) {
266   testReleaseFreeMemoryToOS<scudo::DefaultSizeClassMap>();
267 }
268 
269 TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSAndroid) {
270   testReleaseFreeMemoryToOS<scudo::AndroidSizeClassMap>();
271 }
272 
273 TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSSvelte) {
274   testReleaseFreeMemoryToOS<scudo::SvelteSizeClassMap>();
275 }
276