xref: /aosp_15_r20/external/scudo/standalone/tests/wrappers_cpp_test.cpp (revision 76559068c068bd27e82aff38fac3bfc865233bca)
1 //===-- wrappers_cpp_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 "memtag.h"
10 #include "tests/scudo_unit_test.h"
11 
12 #include <atomic>
13 #include <condition_variable>
14 #include <fstream>
15 #include <memory>
16 #include <mutex>
17 #include <thread>
18 #include <vector>
19 
20 // Android does not support checking for new/delete mismatches.
21 #if SCUDO_ANDROID
22 #define SKIP_MISMATCH_TESTS 1
23 #else
24 #define SKIP_MISMATCH_TESTS 0
25 #endif
26 
27 void operator delete(void *, size_t) noexcept;
28 void operator delete[](void *, size_t) noexcept;
29 
30 extern "C" {
31 #ifndef SCUDO_ENABLE_HOOKS_TESTS
32 #define SCUDO_ENABLE_HOOKS_TESTS 0
33 #endif
34 
35 #if (SCUDO_ENABLE_HOOKS_TESTS == 1) && (SCUDO_ENABLE_HOOKS == 0)
36 #error "Hooks tests should have hooks enabled as well!"
37 #endif
38 
39 struct AllocContext {
40   void *Ptr;
41   size_t Size;
42 };
43 struct DeallocContext {
44   void *Ptr;
45 };
46 static AllocContext AC;
47 static DeallocContext DC;
48 
49 #if (SCUDO_ENABLE_HOOKS_TESTS == 1)
__scudo_allocate_hook(void * Ptr,size_t Size)50 __attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
51                                                                   size_t Size) {
52   AC.Ptr = Ptr;
53   AC.Size = Size;
54 }
__scudo_deallocate_hook(void * Ptr)55 __attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
56   DC.Ptr = Ptr;
57 }
58 #endif // (SCUDO_ENABLE_HOOKS_TESTS == 1)
59 }
60 
61 class ScudoWrappersCppTest : public Test {
62 protected:
SetUp()63   void SetUp() override {
64     if (SCUDO_ENABLE_HOOKS && !SCUDO_ENABLE_HOOKS_TESTS)
65       printf("Hooks are enabled but hooks tests are disabled.\n");
66   }
67 
verifyAllocHookPtr(UNUSED void * Ptr)68   void verifyAllocHookPtr(UNUSED void *Ptr) {
69     if (SCUDO_ENABLE_HOOKS_TESTS)
70       EXPECT_EQ(Ptr, AC.Ptr);
71   }
verifyAllocHookSize(UNUSED size_t Size)72   void verifyAllocHookSize(UNUSED size_t Size) {
73     if (SCUDO_ENABLE_HOOKS_TESTS)
74       EXPECT_EQ(Size, AC.Size);
75   }
verifyDeallocHookPtr(UNUSED void * Ptr)76   void verifyDeallocHookPtr(UNUSED void *Ptr) {
77     if (SCUDO_ENABLE_HOOKS_TESTS)
78       EXPECT_EQ(Ptr, DC.Ptr);
79   }
80 
testCxxNew()81   template <typename T> void testCxxNew() {
82     T *P = new T;
83     EXPECT_NE(P, nullptr);
84     verifyAllocHookPtr(P);
85     verifyAllocHookSize(sizeof(T));
86     memset(P, 0x42, sizeof(T));
87     EXPECT_DEATH(delete[] P, "");
88     delete P;
89     verifyDeallocHookPtr(P);
90     EXPECT_DEATH(delete P, "");
91 
92     P = new T;
93     EXPECT_NE(P, nullptr);
94     memset(P, 0x42, sizeof(T));
95     operator delete(P, sizeof(T));
96     verifyDeallocHookPtr(P);
97 
98     P = new (std::nothrow) T;
99     verifyAllocHookPtr(P);
100     verifyAllocHookSize(sizeof(T));
101     EXPECT_NE(P, nullptr);
102     memset(P, 0x42, sizeof(T));
103     delete P;
104     verifyDeallocHookPtr(P);
105 
106     const size_t N = 16U;
107     T *A = new T[N];
108     EXPECT_NE(A, nullptr);
109     verifyAllocHookPtr(A);
110     verifyAllocHookSize(sizeof(T) * N);
111     memset(A, 0x42, sizeof(T) * N);
112     EXPECT_DEATH(delete A, "");
113     delete[] A;
114     verifyDeallocHookPtr(A);
115     EXPECT_DEATH(delete[] A, "");
116 
117     A = new T[N];
118     EXPECT_NE(A, nullptr);
119     memset(A, 0x42, sizeof(T) * N);
120     operator delete[](A, sizeof(T) * N);
121     verifyDeallocHookPtr(A);
122 
123     A = new (std::nothrow) T[N];
124     verifyAllocHookPtr(A);
125     verifyAllocHookSize(sizeof(T) * N);
126     EXPECT_NE(A, nullptr);
127     memset(A, 0x42, sizeof(T) * N);
128     delete[] A;
129     verifyDeallocHookPtr(A);
130   }
131 };
132 using ScudoWrappersCppDeathTest = ScudoWrappersCppTest;
133 
134 class Pixel {
135 public:
136   enum class Color { Red, Green, Blue };
137   int X = 0;
138   int Y = 0;
139   Color C = Color::Red;
140 };
141 
142 // Note that every Cxx allocation function in the test binary will be fulfilled
143 // by Scudo. See the comment in the C counterpart of this file.
144 
TEST_F(ScudoWrappersCppDeathTest,New)145 TEST_F(ScudoWrappersCppDeathTest, New) {
146   if (getenv("SKIP_TYPE_MISMATCH") || SKIP_MISMATCH_TESTS) {
147     printf("Skipped type mismatch tests.\n");
148     return;
149   }
150   testCxxNew<bool>();
151   testCxxNew<uint8_t>();
152   testCxxNew<uint16_t>();
153   testCxxNew<uint32_t>();
154   testCxxNew<uint64_t>();
155   testCxxNew<float>();
156   testCxxNew<double>();
157   testCxxNew<long double>();
158   testCxxNew<Pixel>();
159 }
160 
161 static std::mutex Mutex;
162 static std::condition_variable Cv;
163 static bool Ready;
164 
stressNew()165 static void stressNew() {
166   std::vector<uintptr_t *> V;
167   {
168     std::unique_lock<std::mutex> Lock(Mutex);
169     while (!Ready)
170       Cv.wait(Lock);
171   }
172   for (size_t I = 0; I < 256U; I++) {
173     const size_t N = static_cast<size_t>(std::rand()) % 128U;
174     uintptr_t *P = new uintptr_t[N];
175     if (P) {
176       memset(P, 0x42, sizeof(uintptr_t) * N);
177       V.push_back(P);
178     }
179   }
180   while (!V.empty()) {
181     delete[] V.back();
182     V.pop_back();
183   }
184 }
185 
TEST_F(ScudoWrappersCppTest,ThreadedNew)186 TEST_F(ScudoWrappersCppTest, ThreadedNew) {
187   // TODO: Investigate why libc sometimes crashes with tag missmatch in
188   // __pthread_clockjoin_ex.
189   std::unique_ptr<scudo::ScopedDisableMemoryTagChecks> NoTags;
190   if (!SCUDO_ANDROID && scudo::archSupportsMemoryTagging() &&
191       scudo::systemSupportsMemoryTagging())
192     NoTags = std::make_unique<scudo::ScopedDisableMemoryTagChecks>();
193 
194   Ready = false;
195   std::thread Threads[32];
196   for (size_t I = 0U; I < sizeof(Threads) / sizeof(Threads[0]); I++)
197     Threads[I] = std::thread(stressNew);
198   {
199     std::unique_lock<std::mutex> Lock(Mutex);
200     Ready = true;
201     Cv.notify_all();
202   }
203   for (auto &T : Threads)
204     T.join();
205 }
206 
207 #if !SCUDO_FUCHSIA
TEST_F(ScudoWrappersCppTest,AllocAfterFork)208 TEST_F(ScudoWrappersCppTest, AllocAfterFork) {
209   // This test can fail flakily when ran as a part of large number of
210   // other tests if the maxmimum number of mappings allowed is low.
211   // We tried to reduce the number of iterations of the loops with
212   // moderate success, so we will now skip this test under those
213   // circumstances.
214   if (SCUDO_LINUX) {
215     long MaxMapCount = 0;
216     // If the file can't be accessed, we proceed with the test.
217     std::ifstream Stream("/proc/sys/vm/max_map_count");
218     if (Stream.good()) {
219       Stream >> MaxMapCount;
220       if (MaxMapCount < 200000)
221         return;
222     }
223   }
224 
225   std::atomic_bool Stop;
226 
227   // Create threads that simply allocate and free different sizes.
228   std::vector<std::thread *> Threads;
229   for (size_t N = 0; N < 5; N++) {
230     std::thread *T = new std::thread([&Stop] {
231       while (!Stop) {
232         for (size_t SizeLog = 3; SizeLog <= 20; SizeLog++) {
233           char *P = new char[1UL << SizeLog];
234           EXPECT_NE(P, nullptr);
235           // Make sure this value is not optimized away.
236           asm volatile("" : : "r,m"(P) : "memory");
237           delete[] P;
238         }
239       }
240     });
241     Threads.push_back(T);
242   }
243 
244   // Create a thread to fork and allocate.
245   for (size_t N = 0; N < 50; N++) {
246     pid_t Pid;
247     if ((Pid = fork()) == 0) {
248       for (size_t SizeLog = 3; SizeLog <= 20; SizeLog++) {
249         char *P = new char[1UL << SizeLog];
250         EXPECT_NE(P, nullptr);
251         // Make sure this value is not optimized away.
252         asm volatile("" : : "r,m"(P) : "memory");
253         // Make sure we can touch all of the allocation.
254         memset(P, 0x32, 1U << SizeLog);
255         // EXPECT_LE(1U << SizeLog, malloc_usable_size(ptr));
256         delete[] P;
257       }
258       _exit(10);
259     }
260     EXPECT_NE(-1, Pid);
261     int Status;
262     EXPECT_EQ(Pid, waitpid(Pid, &Status, 0));
263     EXPECT_FALSE(WIFSIGNALED(Status));
264     EXPECT_EQ(10, WEXITSTATUS(Status));
265   }
266 
267   printf("Waiting for threads to complete\n");
268   Stop = true;
269   for (auto Thread : Threads)
270     Thread->join();
271   Threads.clear();
272 }
273 #endif
274