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