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
27void operator delete(void *, size_t) noexcept;
28void operator delete[](void *, size_t) noexcept;
29
30extern "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
39struct AllocContext {
40 void *Ptr;
41 size_t Size;
42};
43struct DeallocContext {
44 void *Ptr;
45};
46static AllocContext AC;
47static DeallocContext DC;
48
49#if (SCUDO_ENABLE_HOOKS_TESTS == 1)
50__attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
51 size_t Size) {
52 AC.Ptr = Ptr;
53 AC.Size = Size;
54}
55__attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
56 DC.Ptr = Ptr;
57}
58#endif // (SCUDO_ENABLE_HOOKS_TESTS == 1)
59}
60
61class ScudoWrappersCppTest : public Test {
62protected:
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
68 void verifyAllocHookPtr(UNUSED void *Ptr) {
69 if (SCUDO_ENABLE_HOOKS_TESTS)
70 EXPECT_EQ(Ptr, AC.Ptr);
71 }
72 void verifyAllocHookSize(UNUSED size_t Size) {
73 if (SCUDO_ENABLE_HOOKS_TESTS)
74 EXPECT_EQ(Size, AC.Size);
75 }
76 void verifyDeallocHookPtr(UNUSED void *Ptr) {
77 if (SCUDO_ENABLE_HOOKS_TESTS)
78 EXPECT_EQ(Ptr, DC.Ptr);
79 }
80
81 template <typename T> void testCxxNew() {
82 T *P = new T;
83 EXPECT_NE(P, nullptr);
84 verifyAllocHookPtr(Ptr: P);
85 verifyAllocHookSize(sizeof(T));
86 memset(P, 0x42, sizeof(T));
87 EXPECT_DEATH(delete[] P, "");
88 delete P;
89 verifyDeallocHookPtr(Ptr: 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(Ptr: P);
97
98 P = new (std::nothrow) T;
99 verifyAllocHookPtr(Ptr: P);
100 verifyAllocHookSize(sizeof(T));
101 EXPECT_NE(P, nullptr);
102 memset(P, 0x42, sizeof(T));
103 delete P;
104 verifyDeallocHookPtr(Ptr: P);
105
106 const size_t N = 16U;
107 T *A = new T[N];
108 EXPECT_NE(A, nullptr);
109 verifyAllocHookPtr(Ptr: A);
110 verifyAllocHookSize(sizeof(T) * N);
111 memset(A, 0x42, sizeof(T) * N);
112 EXPECT_DEATH(delete A, "");
113 delete[] A;
114 verifyDeallocHookPtr(Ptr: 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(Ptr: A);
122
123 A = new (std::nothrow) T[N];
124 verifyAllocHookPtr(Ptr: A);
125 verifyAllocHookSize(sizeof(T) * N);
126 EXPECT_NE(A, nullptr);
127 memset(A, 0x42, sizeof(T) * N);
128 delete[] A;
129 verifyDeallocHookPtr(Ptr: A);
130 }
131};
132using ScudoWrappersCppDeathTest = ScudoWrappersCppTest;
133
134class Pixel {
135public:
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
145TEST_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
161static std::mutex Mutex;
162static std::condition_variable Cv;
163static bool Ready;
164
165static 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
186TEST_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
208TEST_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

source code of compiler-rt/lib/scudo/standalone/tests/wrappers_cpp_test.cpp