1 //===-- tsd_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 "tsd_exclusive.h"
10 #include "tsd_shared.h"
11 
12 #include "gtest/gtest.h"
13 
14 #include <condition_variable>
15 #include <mutex>
16 #include <thread>
17 
18 // We mock out an allocator with a TSD registry, mostly using empty stubs. The
19 // cache contains a single volatile uptr, to be able to test that several
20 // concurrent threads will not access or modify the same cache at the same time.
21 template <class Config> class MockAllocator {
22 public:
23   using ThisT = MockAllocator<Config>;
24   using TSDRegistryT = typename Config::template TSDRegistryT<ThisT>;
25   using CacheT = struct MockCache { volatile scudo::uptr Canary; };
26   using QuarantineCacheT = struct MockQuarantine {};
27 
28   void initLinkerInitialized() {
29     // This should only be called once by the registry.
30     EXPECT_FALSE(Initialized);
31     Initialized = true;
32   }
33   void reset() { memset(this, 0, sizeof(*this)); }
34 
35   void unmapTestOnly() { TSDRegistry.unmapTestOnly(); }
36   void initCache(CacheT *Cache) { memset(Cache, 0, sizeof(*Cache)); }
37   void commitBack(scudo::TSD<MockAllocator> *TSD) {}
38   TSDRegistryT *getTSDRegistry() { return &TSDRegistry; }
39 
40   bool isInitialized() { return Initialized; }
41 
42 private:
43   bool Initialized;
44   TSDRegistryT TSDRegistry;
45 };
46 
47 struct OneCache {
48   template <class Allocator>
49   using TSDRegistryT = scudo::TSDRegistrySharedT<Allocator, 1U>;
50 };
51 
52 struct SharedCaches {
53   template <class Allocator>
54   using TSDRegistryT = scudo::TSDRegistrySharedT<Allocator, 16U>;
55 };
56 
57 struct ExclusiveCaches {
58   template <class Allocator>
59   using TSDRegistryT = scudo::TSDRegistryExT<Allocator>;
60 };
61 
62 TEST(ScudoTSDTest, TSDRegistryInit) {
63   using AllocatorT = MockAllocator<OneCache>;
64   auto Deleter = [](AllocatorT *A) {
65     A->unmapTestOnly();
66     delete A;
67   };
68   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
69                                                            Deleter);
70   Allocator->reset();
71   EXPECT_FALSE(Allocator->isInitialized());
72 
73   auto Registry = Allocator->getTSDRegistry();
74   Registry->initLinkerInitialized(Allocator.get());
75   EXPECT_TRUE(Allocator->isInitialized());
76 }
77 
78 template <class AllocatorT> static void testRegistry() {
79   auto Deleter = [](AllocatorT *A) {
80     A->unmapTestOnly();
81     delete A;
82   };
83   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
84                                                            Deleter);
85   Allocator->reset();
86   EXPECT_FALSE(Allocator->isInitialized());
87 
88   auto Registry = Allocator->getTSDRegistry();
89   Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/true);
90   EXPECT_TRUE(Allocator->isInitialized());
91 
92   bool UnlockRequired;
93   auto TSD = Registry->getTSDAndLock(&UnlockRequired);
94   EXPECT_NE(TSD, nullptr);
95   EXPECT_EQ(TSD->Cache.Canary, 0U);
96   if (UnlockRequired)
97     TSD->unlock();
98 
99   Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/false);
100   TSD = Registry->getTSDAndLock(&UnlockRequired);
101   EXPECT_NE(TSD, nullptr);
102   EXPECT_EQ(TSD->Cache.Canary, 0U);
103   memset(&TSD->Cache, 0x42, sizeof(TSD->Cache));
104   if (UnlockRequired)
105     TSD->unlock();
106 }
107 
108 TEST(ScudoTSDTest, TSDRegistryBasic) {
109   testRegistry<MockAllocator<OneCache>>();
110   testRegistry<MockAllocator<SharedCaches>>();
111   testRegistry<MockAllocator<ExclusiveCaches>>();
112 }
113 
114 static std::mutex Mutex;
115 static std::condition_variable Cv;
116 static bool Ready = false;
117 
118 template <typename AllocatorT> static void stressCache(AllocatorT *Allocator) {
119   auto Registry = Allocator->getTSDRegistry();
120   {
121     std::unique_lock<std::mutex> Lock(Mutex);
122     while (!Ready)
123       Cv.wait(Lock);
124   }
125   Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false);
126   bool UnlockRequired;
127   auto TSD = Registry->getTSDAndLock(&UnlockRequired);
128   EXPECT_NE(TSD, nullptr);
129   // For an exclusive TSD, the cache should be empty. We cannot guarantee the
130   // same for a shared TSD.
131   if (!UnlockRequired)
132     EXPECT_EQ(TSD->Cache.Canary, 0U);
133   // Transform the thread id to a uptr to use it as canary.
134   const scudo::uptr Canary = static_cast<scudo::uptr>(
135       std::hash<std::thread::id>{}(std::this_thread::get_id()));
136   TSD->Cache.Canary = Canary;
137   // Loop a few times to make sure that a concurrent thread isn't modifying it.
138   for (scudo::uptr I = 0; I < 4096U; I++)
139     EXPECT_EQ(TSD->Cache.Canary, Canary);
140   if (UnlockRequired)
141     TSD->unlock();
142 }
143 
144 template <class AllocatorT> static void testRegistryThreaded() {
145   auto Deleter = [](AllocatorT *A) {
146     A->unmapTestOnly();
147     delete A;
148   };
149   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
150                                                            Deleter);
151   Allocator->reset();
152   std::thread Threads[32];
153   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
154     Threads[I] = std::thread(stressCache<AllocatorT>, Allocator.get());
155   {
156     std::unique_lock<std::mutex> Lock(Mutex);
157     Ready = true;
158     Cv.notify_all();
159   }
160   for (auto &T : Threads)
161     T.join();
162 }
163 
164 TEST(ScudoTSDTest, TSDRegistryThreaded) {
165   testRegistryThreaded<MockAllocator<OneCache>>();
166   testRegistryThreaded<MockAllocator<SharedCaches>>();
167   testRegistryThreaded<MockAllocator<ExclusiveCaches>>();
168 }
169