xref: /aosp_15_r20/external/scudo/standalone/tests/tsd_test.cpp (revision 76559068c068bd27e82aff38fac3bfc865233bca)
1*76559068SAndroid Build Coastguard Worker //===-- tsd_test.cpp --------------------------------------------*- C++ -*-===//
2*76559068SAndroid Build Coastguard Worker //
3*76559068SAndroid Build Coastguard Worker // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*76559068SAndroid Build Coastguard Worker // See https://llvm.org/LICENSE.txt for license information.
5*76559068SAndroid Build Coastguard Worker // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*76559068SAndroid Build Coastguard Worker //
7*76559068SAndroid Build Coastguard Worker //===----------------------------------------------------------------------===//
8*76559068SAndroid Build Coastguard Worker 
9*76559068SAndroid Build Coastguard Worker #include "tests/scudo_unit_test.h"
10*76559068SAndroid Build Coastguard Worker 
11*76559068SAndroid Build Coastguard Worker #include "tsd_exclusive.h"
12*76559068SAndroid Build Coastguard Worker #include "tsd_shared.h"
13*76559068SAndroid Build Coastguard Worker 
14*76559068SAndroid Build Coastguard Worker #include <stdlib.h>
15*76559068SAndroid Build Coastguard Worker 
16*76559068SAndroid Build Coastguard Worker #include <condition_variable>
17*76559068SAndroid Build Coastguard Worker #include <mutex>
18*76559068SAndroid Build Coastguard Worker #include <set>
19*76559068SAndroid Build Coastguard Worker #include <thread>
20*76559068SAndroid Build Coastguard Worker #include <type_traits>
21*76559068SAndroid Build Coastguard Worker 
22*76559068SAndroid Build Coastguard Worker // We mock out an allocator with a TSD registry, mostly using empty stubs. The
23*76559068SAndroid Build Coastguard Worker // cache contains a single volatile uptr, to be able to test that several
24*76559068SAndroid Build Coastguard Worker // concurrent threads will not access or modify the same cache at the same time.
25*76559068SAndroid Build Coastguard Worker template <class Config> class MockAllocator {
26*76559068SAndroid Build Coastguard Worker public:
27*76559068SAndroid Build Coastguard Worker   using ThisT = MockAllocator<Config>;
28*76559068SAndroid Build Coastguard Worker   using TSDRegistryT = typename Config::template TSDRegistryT<ThisT>;
29*76559068SAndroid Build Coastguard Worker   using CacheT = struct MockCache {
30*76559068SAndroid Build Coastguard Worker     volatile scudo::uptr Canary;
31*76559068SAndroid Build Coastguard Worker   };
32*76559068SAndroid Build Coastguard Worker   using QuarantineCacheT = struct MockQuarantine {};
33*76559068SAndroid Build Coastguard Worker 
init()34*76559068SAndroid Build Coastguard Worker   void init() {
35*76559068SAndroid Build Coastguard Worker     // This should only be called once by the registry.
36*76559068SAndroid Build Coastguard Worker     EXPECT_FALSE(Initialized);
37*76559068SAndroid Build Coastguard Worker     Initialized = true;
38*76559068SAndroid Build Coastguard Worker   }
39*76559068SAndroid Build Coastguard Worker 
unmapTestOnly()40*76559068SAndroid Build Coastguard Worker   void unmapTestOnly() { TSDRegistry.unmapTestOnly(this); }
initCache(CacheT * Cache)41*76559068SAndroid Build Coastguard Worker   void initCache(CacheT *Cache) { *Cache = {}; }
commitBack(UNUSED scudo::TSD<MockAllocator> * TSD)42*76559068SAndroid Build Coastguard Worker   void commitBack(UNUSED scudo::TSD<MockAllocator> *TSD) {}
getTSDRegistry()43*76559068SAndroid Build Coastguard Worker   TSDRegistryT *getTSDRegistry() { return &TSDRegistry; }
callPostInitCallback()44*76559068SAndroid Build Coastguard Worker   void callPostInitCallback() {}
45*76559068SAndroid Build Coastguard Worker 
isInitialized()46*76559068SAndroid Build Coastguard Worker   bool isInitialized() { return Initialized; }
47*76559068SAndroid Build Coastguard Worker 
operator new(size_t Size)48*76559068SAndroid Build Coastguard Worker   void *operator new(size_t Size) {
49*76559068SAndroid Build Coastguard Worker     void *P = nullptr;
50*76559068SAndroid Build Coastguard Worker     EXPECT_EQ(0, posix_memalign(&P, alignof(ThisT), Size));
51*76559068SAndroid Build Coastguard Worker     return P;
52*76559068SAndroid Build Coastguard Worker   }
operator delete(void * P)53*76559068SAndroid Build Coastguard Worker   void operator delete(void *P) { free(P); }
54*76559068SAndroid Build Coastguard Worker 
55*76559068SAndroid Build Coastguard Worker private:
56*76559068SAndroid Build Coastguard Worker   bool Initialized = false;
57*76559068SAndroid Build Coastguard Worker   TSDRegistryT TSDRegistry;
58*76559068SAndroid Build Coastguard Worker };
59*76559068SAndroid Build Coastguard Worker 
60*76559068SAndroid Build Coastguard Worker struct OneCache {
61*76559068SAndroid Build Coastguard Worker   template <class Allocator>
62*76559068SAndroid Build Coastguard Worker   using TSDRegistryT = scudo::TSDRegistrySharedT<Allocator, 1U, 1U>;
63*76559068SAndroid Build Coastguard Worker };
64*76559068SAndroid Build Coastguard Worker 
65*76559068SAndroid Build Coastguard Worker struct SharedCaches {
66*76559068SAndroid Build Coastguard Worker   template <class Allocator>
67*76559068SAndroid Build Coastguard Worker   using TSDRegistryT = scudo::TSDRegistrySharedT<Allocator, 16U, 8U>;
68*76559068SAndroid Build Coastguard Worker };
69*76559068SAndroid Build Coastguard Worker 
70*76559068SAndroid Build Coastguard Worker struct ExclusiveCaches {
71*76559068SAndroid Build Coastguard Worker   template <class Allocator>
72*76559068SAndroid Build Coastguard Worker   using TSDRegistryT = scudo::TSDRegistryExT<Allocator>;
73*76559068SAndroid Build Coastguard Worker };
74*76559068SAndroid Build Coastguard Worker 
TEST(ScudoTSDTest,TSDRegistryInit)75*76559068SAndroid Build Coastguard Worker TEST(ScudoTSDTest, TSDRegistryInit) {
76*76559068SAndroid Build Coastguard Worker   using AllocatorT = MockAllocator<OneCache>;
77*76559068SAndroid Build Coastguard Worker   auto Deleter = [](AllocatorT *A) {
78*76559068SAndroid Build Coastguard Worker     A->unmapTestOnly();
79*76559068SAndroid Build Coastguard Worker     delete A;
80*76559068SAndroid Build Coastguard Worker   };
81*76559068SAndroid Build Coastguard Worker   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
82*76559068SAndroid Build Coastguard Worker                                                            Deleter);
83*76559068SAndroid Build Coastguard Worker   EXPECT_FALSE(Allocator->isInitialized());
84*76559068SAndroid Build Coastguard Worker 
85*76559068SAndroid Build Coastguard Worker   auto Registry = Allocator->getTSDRegistry();
86*76559068SAndroid Build Coastguard Worker   Registry->initOnceMaybe(Allocator.get());
87*76559068SAndroid Build Coastguard Worker   EXPECT_TRUE(Allocator->isInitialized());
88*76559068SAndroid Build Coastguard Worker }
89*76559068SAndroid Build Coastguard Worker 
90*76559068SAndroid Build Coastguard Worker template <class AllocatorT>
testRegistry()91*76559068SAndroid Build Coastguard Worker static void testRegistry() NO_THREAD_SAFETY_ANALYSIS {
92*76559068SAndroid Build Coastguard Worker   auto Deleter = [](AllocatorT *A) {
93*76559068SAndroid Build Coastguard Worker     A->unmapTestOnly();
94*76559068SAndroid Build Coastguard Worker     delete A;
95*76559068SAndroid Build Coastguard Worker   };
96*76559068SAndroid Build Coastguard Worker   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
97*76559068SAndroid Build Coastguard Worker                                                            Deleter);
98*76559068SAndroid Build Coastguard Worker   EXPECT_FALSE(Allocator->isInitialized());
99*76559068SAndroid Build Coastguard Worker 
100*76559068SAndroid Build Coastguard Worker   auto Registry = Allocator->getTSDRegistry();
101*76559068SAndroid Build Coastguard Worker   Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/true);
102*76559068SAndroid Build Coastguard Worker   EXPECT_TRUE(Allocator->isInitialized());
103*76559068SAndroid Build Coastguard Worker 
104*76559068SAndroid Build Coastguard Worker   {
105*76559068SAndroid Build Coastguard Worker     typename AllocatorT::TSDRegistryT::ScopedTSD TSD(*Registry);
106*76559068SAndroid Build Coastguard Worker     EXPECT_EQ(TSD->getCache().Canary, 0U);
107*76559068SAndroid Build Coastguard Worker   }
108*76559068SAndroid Build Coastguard Worker 
109*76559068SAndroid Build Coastguard Worker   Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/false);
110*76559068SAndroid Build Coastguard Worker   {
111*76559068SAndroid Build Coastguard Worker     typename AllocatorT::TSDRegistryT::ScopedTSD TSD(*Registry);
112*76559068SAndroid Build Coastguard Worker     EXPECT_EQ(TSD->getCache().Canary, 0U);
113*76559068SAndroid Build Coastguard Worker     memset(&TSD->getCache(), 0x42, sizeof(TSD->getCache()));
114*76559068SAndroid Build Coastguard Worker   }
115*76559068SAndroid Build Coastguard Worker }
116*76559068SAndroid Build Coastguard Worker 
TEST(ScudoTSDTest,TSDRegistryBasic)117*76559068SAndroid Build Coastguard Worker TEST(ScudoTSDTest, TSDRegistryBasic) {
118*76559068SAndroid Build Coastguard Worker   testRegistry<MockAllocator<OneCache>>();
119*76559068SAndroid Build Coastguard Worker   testRegistry<MockAllocator<SharedCaches>>();
120*76559068SAndroid Build Coastguard Worker #if !SCUDO_FUCHSIA
121*76559068SAndroid Build Coastguard Worker   testRegistry<MockAllocator<ExclusiveCaches>>();
122*76559068SAndroid Build Coastguard Worker #endif
123*76559068SAndroid Build Coastguard Worker }
124*76559068SAndroid Build Coastguard Worker 
125*76559068SAndroid Build Coastguard Worker static std::mutex Mutex;
126*76559068SAndroid Build Coastguard Worker static std::condition_variable Cv;
127*76559068SAndroid Build Coastguard Worker static bool Ready;
128*76559068SAndroid Build Coastguard Worker 
129*76559068SAndroid Build Coastguard Worker // Accessing `TSD->getCache()` requires `TSD::Mutex` which isn't easy to test
130*76559068SAndroid Build Coastguard Worker // using thread-safety analysis. Alternatively, we verify the thread safety
131*76559068SAndroid Build Coastguard Worker // through a runtime check in ScopedTSD and mark the test body with
132*76559068SAndroid Build Coastguard Worker // NO_THREAD_SAFETY_ANALYSIS.
133*76559068SAndroid Build Coastguard Worker template <typename AllocatorT>
stressCache(AllocatorT * Allocator)134*76559068SAndroid Build Coastguard Worker static void stressCache(AllocatorT *Allocator) NO_THREAD_SAFETY_ANALYSIS {
135*76559068SAndroid Build Coastguard Worker   auto Registry = Allocator->getTSDRegistry();
136*76559068SAndroid Build Coastguard Worker   {
137*76559068SAndroid Build Coastguard Worker     std::unique_lock<std::mutex> Lock(Mutex);
138*76559068SAndroid Build Coastguard Worker     while (!Ready)
139*76559068SAndroid Build Coastguard Worker       Cv.wait(Lock);
140*76559068SAndroid Build Coastguard Worker   }
141*76559068SAndroid Build Coastguard Worker   Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false);
142*76559068SAndroid Build Coastguard Worker   typename AllocatorT::TSDRegistryT::ScopedTSD TSD(*Registry);
143*76559068SAndroid Build Coastguard Worker   // For an exclusive TSD, the cache should be empty. We cannot guarantee the
144*76559068SAndroid Build Coastguard Worker   // same for a shared TSD.
145*76559068SAndroid Build Coastguard Worker   if (std::is_same<typename AllocatorT::TSDRegistryT,
146*76559068SAndroid Build Coastguard Worker                    scudo::TSDRegistryExT<AllocatorT>>()) {
147*76559068SAndroid Build Coastguard Worker     EXPECT_EQ(TSD->getCache().Canary, 0U);
148*76559068SAndroid Build Coastguard Worker   }
149*76559068SAndroid Build Coastguard Worker   // Transform the thread id to a uptr to use it as canary.
150*76559068SAndroid Build Coastguard Worker   const scudo::uptr Canary = static_cast<scudo::uptr>(
151*76559068SAndroid Build Coastguard Worker       std::hash<std::thread::id>{}(std::this_thread::get_id()));
152*76559068SAndroid Build Coastguard Worker   TSD->getCache().Canary = Canary;
153*76559068SAndroid Build Coastguard Worker   // Loop a few times to make sure that a concurrent thread isn't modifying it.
154*76559068SAndroid Build Coastguard Worker   for (scudo::uptr I = 0; I < 4096U; I++)
155*76559068SAndroid Build Coastguard Worker     EXPECT_EQ(TSD->getCache().Canary, Canary);
156*76559068SAndroid Build Coastguard Worker }
157*76559068SAndroid Build Coastguard Worker 
testRegistryThreaded()158*76559068SAndroid Build Coastguard Worker template <class AllocatorT> static void testRegistryThreaded() {
159*76559068SAndroid Build Coastguard Worker   Ready = false;
160*76559068SAndroid Build Coastguard Worker   auto Deleter = [](AllocatorT *A) {
161*76559068SAndroid Build Coastguard Worker     A->unmapTestOnly();
162*76559068SAndroid Build Coastguard Worker     delete A;
163*76559068SAndroid Build Coastguard Worker   };
164*76559068SAndroid Build Coastguard Worker   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
165*76559068SAndroid Build Coastguard Worker                                                            Deleter);
166*76559068SAndroid Build Coastguard Worker   std::thread Threads[32];
167*76559068SAndroid Build Coastguard Worker   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
168*76559068SAndroid Build Coastguard Worker     Threads[I] = std::thread(stressCache<AllocatorT>, Allocator.get());
169*76559068SAndroid Build Coastguard Worker   {
170*76559068SAndroid Build Coastguard Worker     std::unique_lock<std::mutex> Lock(Mutex);
171*76559068SAndroid Build Coastguard Worker     Ready = true;
172*76559068SAndroid Build Coastguard Worker     Cv.notify_all();
173*76559068SAndroid Build Coastguard Worker   }
174*76559068SAndroid Build Coastguard Worker   for (auto &T : Threads)
175*76559068SAndroid Build Coastguard Worker     T.join();
176*76559068SAndroid Build Coastguard Worker }
177*76559068SAndroid Build Coastguard Worker 
TEST(ScudoTSDTest,TSDRegistryThreaded)178*76559068SAndroid Build Coastguard Worker TEST(ScudoTSDTest, TSDRegistryThreaded) {
179*76559068SAndroid Build Coastguard Worker   testRegistryThreaded<MockAllocator<OneCache>>();
180*76559068SAndroid Build Coastguard Worker   testRegistryThreaded<MockAllocator<SharedCaches>>();
181*76559068SAndroid Build Coastguard Worker #if !SCUDO_FUCHSIA
182*76559068SAndroid Build Coastguard Worker   testRegistryThreaded<MockAllocator<ExclusiveCaches>>();
183*76559068SAndroid Build Coastguard Worker #endif
184*76559068SAndroid Build Coastguard Worker }
185*76559068SAndroid Build Coastguard Worker 
186*76559068SAndroid Build Coastguard Worker static std::set<void *> Pointers;
187*76559068SAndroid Build Coastguard Worker 
stressSharedRegistry(MockAllocator<SharedCaches> * Allocator)188*76559068SAndroid Build Coastguard Worker static void stressSharedRegistry(MockAllocator<SharedCaches> *Allocator) {
189*76559068SAndroid Build Coastguard Worker   std::set<void *> Set;
190*76559068SAndroid Build Coastguard Worker   auto Registry = Allocator->getTSDRegistry();
191*76559068SAndroid Build Coastguard Worker   {
192*76559068SAndroid Build Coastguard Worker     std::unique_lock<std::mutex> Lock(Mutex);
193*76559068SAndroid Build Coastguard Worker     while (!Ready)
194*76559068SAndroid Build Coastguard Worker       Cv.wait(Lock);
195*76559068SAndroid Build Coastguard Worker   }
196*76559068SAndroid Build Coastguard Worker   Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false);
197*76559068SAndroid Build Coastguard Worker   for (scudo::uptr I = 0; I < 4096U; I++) {
198*76559068SAndroid Build Coastguard Worker     typename MockAllocator<SharedCaches>::TSDRegistryT::ScopedTSD TSD(
199*76559068SAndroid Build Coastguard Worker         *Registry);
200*76559068SAndroid Build Coastguard Worker     Set.insert(reinterpret_cast<void *>(&*TSD));
201*76559068SAndroid Build Coastguard Worker   }
202*76559068SAndroid Build Coastguard Worker   {
203*76559068SAndroid Build Coastguard Worker     std::unique_lock<std::mutex> Lock(Mutex);
204*76559068SAndroid Build Coastguard Worker     Pointers.insert(Set.begin(), Set.end());
205*76559068SAndroid Build Coastguard Worker   }
206*76559068SAndroid Build Coastguard Worker }
207*76559068SAndroid Build Coastguard Worker 
TEST(ScudoTSDTest,TSDRegistryTSDsCount)208*76559068SAndroid Build Coastguard Worker TEST(ScudoTSDTest, TSDRegistryTSDsCount) {
209*76559068SAndroid Build Coastguard Worker   Ready = false;
210*76559068SAndroid Build Coastguard Worker   Pointers.clear();
211*76559068SAndroid Build Coastguard Worker   using AllocatorT = MockAllocator<SharedCaches>;
212*76559068SAndroid Build Coastguard Worker   auto Deleter = [](AllocatorT *A) {
213*76559068SAndroid Build Coastguard Worker     A->unmapTestOnly();
214*76559068SAndroid Build Coastguard Worker     delete A;
215*76559068SAndroid Build Coastguard Worker   };
216*76559068SAndroid Build Coastguard Worker   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
217*76559068SAndroid Build Coastguard Worker                                                            Deleter);
218*76559068SAndroid Build Coastguard Worker   // We attempt to use as many TSDs as the shared cache offers by creating a
219*76559068SAndroid Build Coastguard Worker   // decent amount of threads that will be run concurrently and attempt to get
220*76559068SAndroid Build Coastguard Worker   // and lock TSDs. We put them all in a set and count the number of entries
221*76559068SAndroid Build Coastguard Worker   // after we are done.
222*76559068SAndroid Build Coastguard Worker   std::thread Threads[32];
223*76559068SAndroid Build Coastguard Worker   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
224*76559068SAndroid Build Coastguard Worker     Threads[I] = std::thread(stressSharedRegistry, Allocator.get());
225*76559068SAndroid Build Coastguard Worker   {
226*76559068SAndroid Build Coastguard Worker     std::unique_lock<std::mutex> Lock(Mutex);
227*76559068SAndroid Build Coastguard Worker     Ready = true;
228*76559068SAndroid Build Coastguard Worker     Cv.notify_all();
229*76559068SAndroid Build Coastguard Worker   }
230*76559068SAndroid Build Coastguard Worker   for (auto &T : Threads)
231*76559068SAndroid Build Coastguard Worker     T.join();
232*76559068SAndroid Build Coastguard Worker   // The initial number of TSDs we get will be the minimum of the default count
233*76559068SAndroid Build Coastguard Worker   // and the number of CPUs.
234*76559068SAndroid Build Coastguard Worker   EXPECT_LE(Pointers.size(), 8U);
235*76559068SAndroid Build Coastguard Worker   Pointers.clear();
236*76559068SAndroid Build Coastguard Worker   auto Registry = Allocator->getTSDRegistry();
237*76559068SAndroid Build Coastguard Worker   // Increase the number of TSDs to 16.
238*76559068SAndroid Build Coastguard Worker   Registry->setOption(scudo::Option::MaxTSDsCount, 16);
239*76559068SAndroid Build Coastguard Worker   Ready = false;
240*76559068SAndroid Build Coastguard Worker   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
241*76559068SAndroid Build Coastguard Worker     Threads[I] = std::thread(stressSharedRegistry, Allocator.get());
242*76559068SAndroid Build Coastguard Worker   {
243*76559068SAndroid Build Coastguard Worker     std::unique_lock<std::mutex> Lock(Mutex);
244*76559068SAndroid Build Coastguard Worker     Ready = true;
245*76559068SAndroid Build Coastguard Worker     Cv.notify_all();
246*76559068SAndroid Build Coastguard Worker   }
247*76559068SAndroid Build Coastguard Worker   for (auto &T : Threads)
248*76559068SAndroid Build Coastguard Worker     T.join();
249*76559068SAndroid Build Coastguard Worker   // We should get 16 distinct TSDs back.
250*76559068SAndroid Build Coastguard Worker   EXPECT_EQ(Pointers.size(), 16U);
251*76559068SAndroid Build Coastguard Worker }
252