Coverage Report

Created: 2018-09-25 14:53

/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