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