/src/mozilla-central/xpcom/tests/gtest/TestAllocReplacement.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/Attributes.h" |
8 | | #include "mozilla/mozalloc.h" |
9 | | #include "mozilla/ScopeExit.h" |
10 | | #include "nsCOMPtr.h" |
11 | | #include "nsIMemoryReporter.h" |
12 | | #include "nsServiceManagerUtils.h" |
13 | | #include "gtest/gtest.h" |
14 | | |
15 | | // We want to ensure that various functions are hooked properly and that |
16 | | // allocations are getting routed through jemalloc. The strategy |
17 | | // pursued below relies on jemalloc's statistics tracking: we measure |
18 | | // the size of the jemalloc heap using nsIMemoryReporterManager, |
19 | | // allocate a chunk of memory with whatever function is supposed to be |
20 | | // hooked, and then ask for the size of the jemalloc heap again. If the |
21 | | // function has been hooked correctly, then the heap size should be |
22 | | // different between the two measurements. We can also check the |
23 | | // hooking of |free| and similar functions: once we free() the returned |
24 | | // pointer, we can measure the jemalloc heap size again, expecting it to |
25 | | // be identical to the size prior to the allocation. |
26 | | // |
27 | | // If we're not using jemalloc, then nsIMemoryReporterManager will |
28 | | // simply report an error, and we will ignore the entire test. |
29 | | // |
30 | | // This strategy is not perfect: it relies on GTests being |
31 | | // single-threaded, which they are, and no other threads doing |
32 | | // allocation during the test, which is uncertain, as XPCOM has started |
33 | | // up during gtests, and who knows what might be going on behind the |
34 | | // scenes. This latter assumption, however, does not seem to be a |
35 | | // problem in practice. |
36 | | #if defined(MOZ_MEMORY) |
37 | | #define ALLOCATION_ASSERT(b) ASSERT_TRUE((b)) |
38 | | #else |
39 | 0 | #define ALLOCATION_ASSERT(b) (void)(b) Unexecuted instantiation: TestAllocReplacement.cpp:AllocReplacementDeathTest_malloc_check_Test::TestBody()::$_0::operator()() const Unexecuted instantiation: TestAllocReplacement.cpp:AllocReplacementDeathTest_calloc_check_Test::TestBody()::$_1::operator()() const Unexecuted instantiation: TestAllocReplacement.cpp:AllocReplacementDeathTest_realloc_check_Test::TestBody()::$_2::operator()() const Unexecuted instantiation: TestAllocReplacement.cpp:AllocReplacementDeathTest_posix_memalign_check_Test::TestBody()::$_3::operator()() const |
40 | | #endif |
41 | | |
42 | | #define ASSERT_ALLOCATION_HAPPENED(lambda) \ |
43 | 0 | ALLOCATION_ASSERT(ValidateHookedAllocation(lambda, free)); |
44 | | |
45 | | // We do run the risk of OOM'ing when we allocate something...all we can |
46 | | // do is try to allocate something so small that OOM'ing is unlikely. |
47 | | const size_t kAllocAmount = 16; |
48 | | |
49 | | // We declare this function MOZ_NEVER_INLINE to work around optimizing |
50 | | // compilers. If we permitted inlining here, then the compiler might |
51 | | // inline both this function and the calls to the function pointers we |
52 | | // pass in, giving something like: |
53 | | // |
54 | | // void* p = malloc(...); |
55 | | // ...do nothing with p except check nullptr-ness... |
56 | | // free(p); |
57 | | // |
58 | | // and the optimizer can delete the calls to malloc and free entirely, |
59 | | // which would make checking that the jemalloc heap had never changed |
60 | | // difficult. |
61 | | static MOZ_NEVER_INLINE bool |
62 | | ValidateHookedAllocation(void* (*aAllocator)(void), |
63 | | void (*aFreeFunction)(void*)) |
64 | 0 | { |
65 | 0 | nsCOMPtr<nsIMemoryReporterManager> manager = |
66 | 0 | do_GetService("@mozilla.org/memory-reporter-manager;1"); |
67 | 0 |
|
68 | 0 | int64_t before = 0; |
69 | 0 | nsresult rv = manager->GetHeapAllocated(&before); |
70 | 0 | if (NS_FAILED(rv)) { |
71 | 0 | return false; |
72 | 0 | } |
73 | 0 | |
74 | 0 | { |
75 | 0 | void* p = aAllocator(); |
76 | 0 |
|
77 | 0 | if (!p) { |
78 | 0 | return false; |
79 | 0 | } |
80 | 0 | |
81 | 0 | int64_t after = 0; |
82 | 0 | rv = manager->GetHeapAllocated(&after); |
83 | 0 |
|
84 | 0 | // Regardless of whether that call succeeded or failed, we are done with |
85 | 0 | // the allocated buffer now. |
86 | 0 | aFreeFunction(p); |
87 | 0 |
|
88 | 0 | if (NS_FAILED(rv)) { |
89 | 0 | return false; |
90 | 0 | } |
91 | 0 | |
92 | 0 | // Verify that our heap stats have changed. |
93 | 0 | if ((before + int64_t(kAllocAmount)) != after) { |
94 | 0 | return false; |
95 | 0 | } |
96 | 0 | } |
97 | 0 | |
98 | 0 | // Verify that freeing the allocated pointer resets our heap to what it |
99 | 0 | // was before. |
100 | 0 | int64_t after = 0; |
101 | 0 | rv = manager->GetHeapAllocated(&after); |
102 | 0 | if (NS_FAILED(rv)) { |
103 | 0 | return false; |
104 | 0 | } |
105 | 0 | |
106 | 0 | return before == after; |
107 | 0 | } |
108 | | |
109 | | // We use the "*DeathTest" suffix for all tests in this file to ensure they |
110 | | // run before other GTests. As noted at the top, this is important because |
111 | | // other tests might spawn threads that interfere with heap memory |
112 | | // measurements. |
113 | | // |
114 | | // See <https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#death-tests> |
115 | | // for more information about death tests in the GTest framework. |
116 | | TEST(AllocReplacementDeathTest, malloc_check) |
117 | 0 | { |
118 | 0 | ASSERT_ALLOCATION_HAPPENED([] { |
119 | 0 | return malloc(kAllocAmount); |
120 | 0 | }); |
121 | 0 | } |
122 | | |
123 | | // See above for an explanation of the "*DeathTest" suffix used here. |
124 | | TEST(AllocReplacementDeathTest, calloc_check) |
125 | 0 | { |
126 | 0 | ASSERT_ALLOCATION_HAPPENED([] { |
127 | 0 | return calloc(1, kAllocAmount); |
128 | 0 | }); |
129 | 0 | } |
130 | | |
131 | | // See above for an explanation of the "*DeathTest" suffix used here. |
132 | | TEST(AllocReplacementDeathTest, realloc_check) |
133 | 0 | { |
134 | 0 | ASSERT_ALLOCATION_HAPPENED([] { |
135 | 0 | return realloc(nullptr, kAllocAmount); |
136 | 0 | }); |
137 | 0 | } |
138 | | |
139 | | #if defined(HAVE_POSIX_MEMALIGN) |
140 | | // See above for an explanation of the "*DeathTest" suffix used here. |
141 | | TEST(AllocReplacementDeathTest, posix_memalign_check) |
142 | 0 | { |
143 | 0 | ASSERT_ALLOCATION_HAPPENED([] { |
144 | 0 | void* p = nullptr; |
145 | 0 | int result = posix_memalign(&p, sizeof(void*), kAllocAmount); |
146 | 0 | if (result != 0) { |
147 | 0 | return static_cast<void*>(nullptr); |
148 | 0 | } |
149 | 0 | return p; |
150 | 0 | }); |
151 | 0 | } |
152 | | #endif |
153 | | |
154 | | #if defined(XP_WIN) |
155 | | #include <windows.h> |
156 | | |
157 | | #undef ASSERT_ALLOCATION_HAPPENED |
158 | | #define ASSERT_ALLOCATION_HAPPENED(lambda) \ |
159 | | ALLOCATION_ASSERT(ValidateHookedAllocation(lambda, [](void* p) { \ |
160 | | HeapFree(GetProcessHeap(), 0, p); \ |
161 | | })); |
162 | | |
163 | | // See above for an explanation of the "*DeathTest" suffix used here. |
164 | | TEST(AllocReplacementDeathTest, HeapAlloc_check) |
165 | | { |
166 | | ASSERT_ALLOCATION_HAPPENED([] { |
167 | | HANDLE h = GetProcessHeap(); |
168 | | return HeapAlloc(h, 0, kAllocAmount); |
169 | | }); |
170 | | } |
171 | | |
172 | | // See above for an explanation of the "*DeathTest" suffix used here. |
173 | | TEST(AllocReplacementDeathTest, HeapReAlloc_check) |
174 | | { |
175 | | ASSERT_ALLOCATION_HAPPENED([] { |
176 | | HANDLE h = GetProcessHeap(); |
177 | | void *p = HeapAlloc(h, 0, kAllocAmount / 2); |
178 | | |
179 | | if (!p) { |
180 | | return static_cast<void*>(nullptr); |
181 | | } |
182 | | |
183 | | return HeapReAlloc(h, 0, p, kAllocAmount); |
184 | | }); |
185 | | } |
186 | | #endif |