Bug 1839147 - Fix PHC getting stuck in disabled state r=glandium
[gecko.git] / memory / replace / phc / test / gtest / TestPHC.cpp
blob5d5e083d46c2569c2a2b988c2ee3e5f866a52b7e
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "gtest/gtest.h"
7 #include "mozmemory.h"
8 #include "replace_malloc_bridge.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/mozalloc.h"
11 #include "../../PHC.h"
13 using namespace mozilla;
15 bool PHCInfoEq(phc::AddrInfo& aInfo, phc::AddrInfo::Kind aKind, void* aBaseAddr,
16 size_t aUsableSize, bool aHasAllocStack, bool aHasFreeStack) {
17 return aInfo.mKind == aKind && aInfo.mBaseAddr == aBaseAddr &&
18 aInfo.mUsableSize == aUsableSize &&
19 // Proper stack traces will have at least 3 elements.
20 (aHasAllocStack ? (aInfo.mAllocStack->mLength > 2)
21 : (aInfo.mAllocStack.isNothing())) &&
22 (aHasFreeStack ? (aInfo.mFreeStack->mLength > 2)
23 : (aInfo.mFreeStack.isNothing()));
26 bool JeInfoEq(jemalloc_ptr_info_t& aInfo, PtrInfoTag aTag, void* aAddr,
27 size_t aSize, arena_id_t arenaId) {
28 return aInfo.tag == aTag && aInfo.addr == aAddr && aInfo.size == aSize
29 #ifdef MOZ_DEBUG
30 && aInfo.arenaId == arenaId
31 #endif
35 uint8_t* GetPHCAllocation(size_t aSize, size_t aAlignment = 1) {
36 // A crude but effective way to get a PHC allocation.
37 for (int i = 0; i < 2000000; i++) {
38 void* p = (aAlignment == 1) ? moz_xmalloc(aSize)
39 : moz_xmemalign(aAlignment, aSize);
40 if (ReplaceMalloc::IsPHCAllocation(p, nullptr)) {
41 return (uint8_t*)p;
43 free(p);
45 return nullptr;
48 static const size_t kPageSize = 4096;
50 TEST(PHC, TestPHCAllocations)
52 // First, check that allocations of various sizes all get put at the end of
53 // their page as expected. Also, check their sizes are as expected.
55 #define ASSERT_POS(n1, n2) \
56 p = (uint8_t*)moz_xrealloc(p, (n1)); \
57 ASSERT_EQ((reinterpret_cast<uintptr_t>(p) & (kPageSize - 1)), \
58 kPageSize - (n2)); \
59 ASSERT_EQ(moz_malloc_usable_size(p), (n2));
61 uint8_t* p = GetPHCAllocation(1);
62 if (!p) {
63 MOZ_CRASH("failed to get a PHC allocation");
66 // On Win64 the smallest possible allocation is 16 bytes. On other platforms
67 // it is 8 bytes.
68 #if defined(XP_WIN) && defined(HAVE_64BIT_BUILD)
69 ASSERT_POS(8U, 16U);
70 #else
71 ASSERT_POS(8U, 8U);
72 #endif
73 ASSERT_POS(16U, 16U);
74 ASSERT_POS(32U, 32U);
75 ASSERT_POS(64U, 64U);
76 ASSERT_POS(128U, 128U);
77 ASSERT_POS(256U, 256U);
78 ASSERT_POS(512U, 512U);
79 ASSERT_POS(1024U, 1024U);
80 ASSERT_POS(2048U, 2048U);
81 ASSERT_POS(4096U, 4096U);
83 free(p);
85 #undef ASSERT_POS
87 // Second, do similar checking with allocations of various alignments. Also
88 // check that their sizes (which are different to allocations with normal
89 // alignment) are the same as the sizes of equivalent non-PHC allocations.
91 #define ASSERT_ALIGN(a1, a2) \
92 p = (uint8_t*)GetPHCAllocation(8, (a1)); \
93 ASSERT_EQ((reinterpret_cast<uintptr_t>(p) & (kPageSize - 1)), \
94 kPageSize - (a2)); \
95 ASSERT_EQ(moz_malloc_usable_size(p), (a2)); \
96 free(p); \
97 p = (uint8_t*)moz_xmemalign((a1), 8); \
98 ASSERT_EQ(moz_malloc_usable_size(p), (a2)); \
99 free(p);
101 // On Win64 the smallest possible allocation is 16 bytes. On other platforms
102 // it is 8 bytes.
103 #if defined(XP_WIN) && defined(HAVE_64BIT_BUILD)
104 ASSERT_ALIGN(8U, 16U);
105 #else
106 ASSERT_ALIGN(8U, 8U);
107 #endif
108 ASSERT_ALIGN(16U, 16U);
109 ASSERT_ALIGN(32U, 32U);
110 ASSERT_ALIGN(64U, 64U);
111 ASSERT_ALIGN(128U, 128U);
112 ASSERT_ALIGN(256U, 256U);
113 ASSERT_ALIGN(512U, 512U);
114 ASSERT_ALIGN(1024U, 1024U);
115 ASSERT_ALIGN(2048U, 2048U);
116 ASSERT_ALIGN(4096U, 4096U);
118 #undef ASSERT_ALIGN
121 TEST(PHC, TestPHCInfo)
123 int stackVar;
124 phc::AddrInfo phcInfo;
125 jemalloc_ptr_info_t jeInfo;
127 // Test a default AddrInfo.
128 ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0ul,
129 false, false));
131 // Test some non-PHC allocation addresses.
132 ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(nullptr, &phcInfo));
133 ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0,
134 false, false));
135 ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(&stackVar, &phcInfo));
136 ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0,
137 false, false));
139 uint8_t* p = GetPHCAllocation(32);
140 if (!p) {
141 MOZ_CRASH("failed to get a PHC allocation");
144 // Test an in-use PHC allocation: first byte within it.
145 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p, &phcInfo));
146 ASSERT_TRUE(
147 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
148 ASSERT_EQ(moz_malloc_usable_size(p), 32ul);
149 jemalloc_ptr_info(p, &jeInfo);
150 ASSERT_TRUE(JeInfoEq(jeInfo, TagLiveAlloc, p, 32, 0));
152 // Test an in-use PHC allocation: last byte within it.
153 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 31, &phcInfo));
154 ASSERT_TRUE(
155 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
156 ASSERT_EQ(moz_malloc_usable_size(p + 31), 32ul);
157 jemalloc_ptr_info(p + 31, &jeInfo);
158 ASSERT_TRUE(JeInfoEq(jeInfo, TagLiveAlloc, p, 32, 0));
160 // Test an in-use PHC allocation: last byte before it.
161 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p - 1, &phcInfo));
162 ASSERT_TRUE(
163 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
164 ASSERT_EQ(moz_malloc_usable_size(p - 1), 0ul);
165 jemalloc_ptr_info(p - 1, &jeInfo);
166 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
168 // Test an in-use PHC allocation: first byte on its allocation page.
169 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 32 - kPageSize, &phcInfo));
170 ASSERT_TRUE(
171 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
172 jemalloc_ptr_info(p + 32 - kPageSize, &jeInfo);
173 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
175 // Test an in-use PHC allocation: first byte in the following guard page.
176 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 32, &phcInfo));
177 ASSERT_TRUE(
178 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, false));
179 jemalloc_ptr_info(p + 32, &jeInfo);
180 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
182 // Test an in-use PHC allocation: last byte in the lower half of the
183 // following guard page.
184 ASSERT_TRUE(
185 ReplaceMalloc::IsPHCAllocation(p + 32 + (kPageSize / 2 - 1), &phcInfo));
186 ASSERT_TRUE(
187 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, false));
188 jemalloc_ptr_info(p + 32 + (kPageSize / 2 - 1), &jeInfo);
189 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
191 // Test an in-use PHC allocation: last byte in the preceding guard page.
192 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 31 - kPageSize, &phcInfo));
193 ASSERT_TRUE(
194 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, false));
195 jemalloc_ptr_info(p + 31 - kPageSize, &jeInfo);
196 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
198 // Test an in-use PHC allocation: first byte in the upper half of the
199 // preceding guard page.
200 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(
201 p + 31 - kPageSize - (kPageSize / 2 - 1), &phcInfo));
202 ASSERT_TRUE(
203 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, false));
204 jemalloc_ptr_info(p + 31 - kPageSize - (kPageSize / 2 - 1), &jeInfo);
205 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
207 free(p);
209 // Test a freed PHC allocation: first byte within it.
210 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p, &phcInfo));
211 ASSERT_TRUE(
212 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
213 jemalloc_ptr_info(p, &jeInfo);
214 ASSERT_TRUE(JeInfoEq(jeInfo, TagFreedAlloc, p, 32, 0));
216 // Test a freed PHC allocation: last byte within it.
217 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 31, &phcInfo));
218 ASSERT_TRUE(
219 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
220 jemalloc_ptr_info(p + 31, &jeInfo);
221 ASSERT_TRUE(JeInfoEq(jeInfo, TagFreedAlloc, p, 32, 0));
223 // Test a freed PHC allocation: last byte before it.
224 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p - 1, &phcInfo));
225 ASSERT_TRUE(
226 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
227 jemalloc_ptr_info(p - 1, &jeInfo);
228 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
230 // Test a freed PHC allocation: first byte on its allocation page.
231 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 32 - kPageSize, &phcInfo));
232 ASSERT_TRUE(
233 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
234 jemalloc_ptr_info(p + 32 - kPageSize, &jeInfo);
235 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
237 // Test a freed PHC allocation: first byte in the following guard page.
238 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 32, &phcInfo));
239 ASSERT_TRUE(
240 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, true));
241 jemalloc_ptr_info(p + 32, &jeInfo);
242 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
244 // Test a freed PHC allocation: last byte in the lower half of the following
245 // guard page.
246 ASSERT_TRUE(
247 ReplaceMalloc::IsPHCAllocation(p + 32 + (kPageSize / 2 - 1), &phcInfo));
248 ASSERT_TRUE(
249 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, true));
250 jemalloc_ptr_info(p + 32 + (kPageSize / 2 - 1), &jeInfo);
251 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
253 // Test a freed PHC allocation: last byte in the preceding guard page.
254 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 31 - kPageSize, &phcInfo));
255 ASSERT_TRUE(
256 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, true));
257 jemalloc_ptr_info(p + 31 - kPageSize, &jeInfo);
258 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
260 // Test a freed PHC allocation: first byte in the upper half of the preceding
261 // guard page.
262 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(
263 p + 31 - kPageSize - (kPageSize / 2 - 1), &phcInfo));
264 ASSERT_TRUE(
265 PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, true));
266 jemalloc_ptr_info(p + 31 - kPageSize - (kPageSize / 2 - 1), &jeInfo);
267 ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
269 // There are no tests for `mKind == NeverAllocatedPage` because it's not
270 // possible to reliably get ahold of such a page.
273 TEST(PHC, TestPHCDisabling)
275 uint8_t* p = GetPHCAllocation(32);
276 uint8_t* q = GetPHCAllocation(32);
277 if (!p || !q) {
278 MOZ_CRASH("failed to get a PHC allocation");
281 ASSERT_TRUE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
282 ReplaceMalloc::DisablePHCOnCurrentThread();
283 ASSERT_FALSE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
285 // Test realloc() on a PHC allocation while PHC is disabled on the thread.
286 uint8_t* p2 = (uint8_t*)realloc(p, 128);
287 // The small realloc is fulfilled within the same page, but it does move.
288 ASSERT_TRUE(p2 == p - 96);
289 ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p2, nullptr));
290 uint8_t* p3 = (uint8_t*)realloc(p2, 8192);
291 // The big realloc is not in-place, and the result is not a PHC allocation.
292 ASSERT_TRUE(p3 != p2);
293 ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(p3, nullptr));
294 free(p3);
296 // Test free() on a PHC allocation while PHC is disabled on the thread.
297 free(q);
299 // These must not be PHC allocations.
300 uint8_t* r = GetPHCAllocation(32); // This will fail.
301 ASSERT_FALSE(!!r);
303 ReplaceMalloc::ReenablePHCOnCurrentThread();
304 ASSERT_TRUE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
306 // If it really was reenabled we should be able to get PHC allocations
307 // again.
308 uint8_t* s = GetPHCAllocation(32); // This should succeed.
309 ASSERT_TRUE(!!s);
310 free(s);