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 #ifndef CountingAllocatorBase_h
8 #define CountingAllocatorBase_h
9 
10 #include <cstdlib>
11 #include "mozilla/Assertions.h"
12 #include "mozilla/Atomics.h"
13 #include "mozilla/mozalloc.h"
14 #include "nsIMemoryReporter.h"
15 
16 namespace mozilla {
17 
18 // This CRTP class handles several details of wrapping allocators and should
19 // be preferred to manually counting with MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC
20 // and MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE.  The typical use is in a memory
21 // reporter for a particular third party library:
22 //
23 //   class MyMemoryReporter : public CountingAllocatorBase<MyMemoryReporter>
24 //   {
25 //     ...
26 //     NS_IMETHOD
27 //     CollectReports(nsIHandleReportCallback* aHandleReport,
28 //                    nsISupports* aData, bool aAnonymize) override
29 //     {
30 //        MOZ_COLLECT_REPORT(
31 //          "explicit/path/to/somewhere", KIND_HEAP, UNITS_BYTES,
32 //          MemoryAllocated(),
33 //          "A description of what we are reporting.");
34 //
35 //        return NS_OK;
36 //     }
37 //   };
38 //
39 //   ...somewhere later in the code...
40 //   SetThirdPartyMemoryFunctions(MyMemoryReporter::CountingAlloc,
41 //                                MyMemoryReporter::CountingFree);
42 template <typename T>
43 class CountingAllocatorBase {
44  public:
CountingAllocatorBase()45   CountingAllocatorBase() {
46 #ifdef DEBUG
47     // There must be only one instance of this class, due to |sAmount| being
48     // static.
49     static bool hasRun = false;
50     MOZ_ASSERT(!hasRun);
51     hasRun = true;
52 #endif
53   }
54 
MemoryAllocated()55   static size_t MemoryAllocated() { return sAmount; }
56 
CountingMalloc(size_t size)57   static void* CountingMalloc(size_t size) {
58     void* p = malloc(size);
59     sAmount += MallocSizeOfOnAlloc(p);
60     return p;
61   }
62 
CountingCalloc(size_t nmemb,size_t size)63   static void* CountingCalloc(size_t nmemb, size_t size) {
64     void* p = calloc(nmemb, size);
65     sAmount += MallocSizeOfOnAlloc(p);
66     return p;
67   }
68 
CountingRealloc(void * p,size_t size)69   static void* CountingRealloc(void* p, size_t size) {
70     size_t oldsize = MallocSizeOfOnFree(p);
71     void* pnew = realloc(p, size);
72     if (pnew) {
73       size_t newsize = MallocSizeOfOnAlloc(pnew);
74       sAmount += newsize - oldsize;
75     } else if (size == 0) {
76       // We asked for a 0-sized (re)allocation of some existing pointer
77       // and received NULL in return.  0-sized allocations are permitted
78       // to either return NULL or to allocate a unique object per call (!).
79       // For a malloc implementation that chooses the second strategy,
80       // that allocation may fail (unlikely, but possible).
81       //
82       // Given a NULL return value and an allocation size of 0, then, we
83       // don't know if that means the original pointer was freed or if
84       // the allocation of the unique object failed.  If the original
85       // pointer was freed, then we have nothing to do here.  If the
86       // allocation of the unique object failed, the original pointer is
87       // still valid and we ought to undo the decrement from above.
88       // However, we have no way of knowing how the underlying realloc
89       // implementation is behaving.  Assuming that the original pointer
90       // was freed is the safest course of action.  We do, however, need
91       // to note that we freed memory.
92       sAmount -= oldsize;
93     } else {
94       // realloc failed.  The amount allocated hasn't changed.
95     }
96     return pnew;
97   }
98 
99   // Some library code expects that realloc(x, 0) will free x, which is not
100   // the behavior of the version of jemalloc we're using, so this wrapped
101   // version of realloc is needed.
CountingFreeingRealloc(void * p,size_t size)102   static void* CountingFreeingRealloc(void* p, size_t size) {
103     if (size == 0) {
104       CountingFree(p);
105       return nullptr;
106     }
107     return CountingRealloc(p, size);
108   }
109 
CountingFree(void * p)110   static void CountingFree(void* p) {
111     sAmount -= MallocSizeOfOnFree(p);
112     free(p);
113   }
114 
115   // Infallible-allocation wrappers for the counting malloc/calloc/realloc
116   // functions, for clients that don't safely handle allocation failures
117   // themselves.
InfallibleCountingMalloc(size_t size)118   static void* InfallibleCountingMalloc(size_t size) {
119     void* p = moz_xmalloc(size);
120     sAmount += MallocSizeOfOnAlloc(p);
121     return p;
122   }
123 
InfallibleCountingCalloc(size_t nmemb,size_t size)124   static void* InfallibleCountingCalloc(size_t nmemb, size_t size) {
125     void* p = moz_xcalloc(nmemb, size);
126     sAmount += MallocSizeOfOnAlloc(p);
127     return p;
128   }
129 
InfallibleCountingRealloc(void * p,size_t size)130   static void* InfallibleCountingRealloc(void* p, size_t size) {
131     size_t oldsize = MallocSizeOfOnFree(p);
132     void* pnew = moz_xrealloc(p, size);
133     if (pnew) {
134       size_t newsize = MallocSizeOfOnAlloc(pnew);
135       sAmount += newsize - oldsize;
136     } else if (size == 0) {
137       // See comment in CountingRealloc above.
138       sAmount -= oldsize;
139     } else {
140       // realloc failed.  The amount allocated hasn't changed.
141     }
142     return pnew;
143   }
144 
145  private:
146   // |sAmount| can be (implicitly) accessed by multiple threads, so it
147   // must be thread-safe. It may be written during GC, so accesses are not
148   // recorded.
149   typedef Atomic<size_t, SequentiallyConsistent> AmountType;
150   static AmountType sAmount;
151 
152   MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(MallocSizeOfOnAlloc)
153   MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(MallocSizeOfOnFree)
154 };
155 
156 }  // namespace mozilla
157 
158 #endif  // CountingAllocatorBase_h
159