1 //--------------------------------------------------------------------------
2 // Copyright (C) 2016-2021 Cisco and/or its affiliates. All rights reserved.
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License Version 2 as published
6 // by the Free Software Foundation.  You may not use, modify or distribute
7 // this program under any other version of the GNU General Public License.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 //--------------------------------------------------------------------------
18 
19 // memory_manager.cc author Joel Cornett <jocornet@cisco.com>
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include <cassert>
26 #include <new>
27 
28 #include "main/thread.h"
29 
30 #include "memory_allocator.h"
31 #include "memory_cap.h"
32 
33 #ifdef UNIT_TEST
34 #include "catch/snort_catch.h"
35 #endif
36 
37 namespace memory
38 {
39 
40 // -----------------------------------------------------------------------------
41 // metadata
42 // -----------------------------------------------------------------------------
43 
44 // This structure must be aligned to max_align_t as long as we are prefixing
45 // it to memory allocations so that the returned memory is also aligned.
46 struct alignas(max_align_t) Metadata
47 {
48 #if defined(REG_TEST) || defined(UNIT_TEST)
49     static constexpr size_t SANITY_CHECK_VALUE = 0xabcdef;
50     size_t sanity;
51 #endif
52 
53     // number of requested bytes
54     size_t payload_size;
55 
56     // total number of bytes allocated, including Metadata header
57     size_t total_size() const;
58     void* payload_offset();
59 
60 #if defined(REG_TEST) || defined(UNIT_TEST)
validmemory::Metadata61     bool valid() const
62     { return sanity == SANITY_CHECK_VALUE; }
63 #endif
64 
65     Metadata(size_t = 0);
66 
67     static size_t calculate_total_size(size_t);
68 
69     template<typename Allocator>
70     static Metadata* create(size_t);
71 
72     static Metadata* extract(void*);
73 };
74 
total_size() const75 inline size_t Metadata::total_size() const
76 { return calculate_total_size(payload_size); }
77 
payload_offset()78 inline void* Metadata::payload_offset()
79 { return this + 1; }
80 
Metadata(size_t n)81 inline Metadata::Metadata(size_t n) :
82 #if defined(REG_TEST) || defined(UNIT_TEST)
83     sanity(SANITY_CHECK_VALUE),
84 #endif
85     payload_size(n)
86 { }
87 
calculate_total_size(size_t n)88 inline size_t Metadata::calculate_total_size(size_t n)
89 { return sizeof(Metadata) + n; }
90 
91 template<typename Allocator>
create(size_t n)92 Metadata* Metadata::create(size_t n)
93 {
94     auto meta =
95         static_cast<Metadata*>(Allocator::allocate(calculate_total_size(n)));
96 
97     if ( !meta )
98         return nullptr;
99 
100     // Trigger metadata ctor
101     *meta = Metadata(n);
102 
103 #if defined(REG_TEST) || defined(UNIT_TEST)
104     assert(meta->valid());
105 #endif
106 
107     return meta;
108 }
109 
extract(void * p)110 Metadata* Metadata::extract(void* p)
111 {
112     assert(p);
113 
114     auto meta = static_cast<Metadata*>(p) - 1;
115 
116 #if defined(REG_TEST) || defined(UNIT_TEST)
117     assert(meta->valid());
118 #endif
119 
120     return meta;
121 }
122 
123 // -----------------------------------------------------------------------------
124 // the meat
125 // -----------------------------------------------------------------------------
126 
127 class ReentryContext
128 {
129 public:
ReentryContext(bool & flag)130     ReentryContext(bool& flag) :
131         already_entered(flag), flag(flag)
132     { flag = true; }
133 
~ReentryContext()134     ~ReentryContext()
135     { flag = false; }
136 
is_reentry() const137     bool is_reentry() const
138     { return already_entered; }
139 
140 private:
141     const bool already_entered;
142     bool& flag;
143 };
144 
145 template<typename Allocator = MemoryAllocator, typename Cap = MemoryCap>
146 struct Interface
147 {
148     static void* allocate(size_t);
149     static void deallocate(void*);
150 
151     static THREAD_LOCAL bool in_allocation_call;
152 };
153 
154 template<typename Allocator, typename Cap>
allocate(size_t n)155 void* Interface<Allocator, Cap>::allocate(size_t n)
156 {
157     // prevent allocation reentry
158     ReentryContext reentry_context(in_allocation_call);
159     assert(!reentry_context.is_reentry());
160 
161     auto meta = Metadata::create<Allocator>(n);
162 
163     if ( !meta )
164         return nullptr;
165 
166     Cap::allocate(meta->total_size());
167     return meta->payload_offset();
168 }
169 
170 template<typename Allocator, typename Cap>
deallocate(void * p)171 void Interface<Allocator, Cap>::deallocate(void* p)
172 {
173     if ( !p )
174         return;
175 
176     auto meta = Metadata::extract(p);
177     assert(meta);
178 
179     Cap::deallocate(meta->total_size());
180     Allocator::deallocate(meta);
181 }
182 
183 template<typename Allocator, typename Cap>
184 THREAD_LOCAL bool Interface<Allocator, Cap>::in_allocation_call = false;
185 
186 } //namespace memory
187 
188 // -----------------------------------------------------------------------------
189 // new /delete replacements
190 // -----------------------------------------------------------------------------
191 
192 // these don't have to be visible to operate as replacements
193 
194 #ifdef ENABLE_MEMORY_OVERLOADS
operator new(size_t n)195 void* operator new(size_t n)
196 {
197     auto p = memory::Interface<>::allocate(n);
198     if ( !p )
199         throw std::bad_alloc();
200 
201     return p;
202 }
203 
operator new[](size_t n)204 void* operator new[](size_t n)
205 { return ::operator new(n); }
206 
operator new(size_t n,const std::nothrow_t &)207 void* operator new(size_t n, const std::nothrow_t&) noexcept
208 { return memory::Interface<>::allocate(n); }
209 
operator new[](size_t n,const std::nothrow_t &)210 void* operator new[](size_t n, const std::nothrow_t&) noexcept
211 { return memory::Interface<>::allocate(n); }
212 
operator delete(void * p)213 void operator delete(void* p) noexcept
214 { memory::Interface<>::deallocate(p); }
215 
operator delete[](void * p)216 void operator delete[](void* p) noexcept
217 { ::operator delete(p); }
218 
operator delete(void * p,const std::nothrow_t &)219 void operator delete(void* p, const std::nothrow_t&) noexcept
220 { ::operator delete(p); }
221 
operator delete[](void * p,const std::nothrow_t &)222 void operator delete[](void* p, const std::nothrow_t&) noexcept
223 { ::operator delete[](p); }
224 
operator delete(void * p,size_t)225 void operator delete(void* p, size_t) noexcept
226 { ::operator delete(p); }
227 
operator delete[](void * p,size_t)228 void operator delete[](void* p, size_t) noexcept
229 { ::operator delete[](p); }
230 #endif
231 
232 // -----------------------------------------------------------------------------
233 // unit tests
234 // -----------------------------------------------------------------------------
235 
236 #ifdef UNIT_TEST
237 
238 namespace t_memory
239 {
240 
241 struct AllocatorSpy
242 {
allocatet_memory::AllocatorSpy243     static void* allocate(size_t n)
244     { allocate_called = true; allocate_arg = n; return pool; }
245 
deallocatet_memory::AllocatorSpy246     static void deallocate(void* p)
247     { deallocate_called = true; deallocate_arg = p; }
248 
resett_memory::AllocatorSpy249     static void reset()
250     {
251         pool = nullptr;
252         allocate_called = false;
253         allocate_arg = 0;
254         deallocate_called = false;
255         deallocate_arg = nullptr;
256     }
257 
258     static void* pool;
259     static bool allocate_called;
260     static size_t allocate_arg;
261     static bool deallocate_called;
262     static void* deallocate_arg;
263 };
264 
265 void* AllocatorSpy::pool = nullptr;
266 bool AllocatorSpy::allocate_called = false;
267 size_t AllocatorSpy::allocate_arg = 0;
268 bool AllocatorSpy::deallocate_called = false;
269 void* AllocatorSpy::deallocate_arg = nullptr;
270 
271 struct CapSpy
272 {
allocatet_memory::CapSpy273     static void allocate(size_t n)
274     {
275         update_allocations_called = true;
276         update_allocations_arg = n;
277     }
278 
deallocatet_memory::CapSpy279     static void deallocate(size_t n)
280     {
281         update_deallocations_called = true;
282         update_deallocations_arg = n;
283     }
284 
resett_memory::CapSpy285     static void reset()
286     {
287         update_allocations_called = false;
288         update_allocations_arg = 0;
289 
290         update_deallocations_called = false;
291         update_deallocations_arg = 0;
292     }
293 
294     static bool update_allocations_called;
295     static size_t update_allocations_arg;
296 
297     static bool update_deallocations_called;
298     static size_t update_deallocations_arg;
299 };
300 
301 bool CapSpy::update_allocations_called = false;
302 size_t CapSpy::update_allocations_arg = 0;
303 
304 bool CapSpy::update_deallocations_called = false;
305 size_t CapSpy::update_deallocations_arg = 0;
306 
307 } // namespace t_memory
308 
309 TEST_CASE( "memory metadata", "[memory]" )
310 {
311     using namespace t_memory;
312 
313     AllocatorSpy::reset();
314     constexpr size_t n = 1;
315     char pool[sizeof(memory::Metadata) + n];
316 
317     SECTION( "create" )
318     {
319         AllocatorSpy::pool = pool;
320 
321         auto meta = memory::Metadata::create<AllocatorSpy>(n);
322 
323         CHECK( (void*)meta == (void*)pool );
324         CHECK( meta->valid() );
325         CHECK( meta->payload_size == n );
326     }
327 
328     SECTION( "extract" )
329     {
330         auto meta_pool = reinterpret_cast<memory::Metadata*>(pool);
331         meta_pool[0] = memory::Metadata(n);
332 
333         void* p = &meta_pool[1];
334 
335         auto meta = memory::Metadata::extract(p);
336 
337         CHECK( (void*)meta == (void*)pool );
338         CHECK( meta->payload_offset() == p );
339     }
340 }
341 
342 TEST_CASE( "memory manager interface", "[memory]" )
343 {
344     using namespace t_memory;
345 
346     AllocatorSpy::reset();
347     CapSpy::reset();
348 
349     constexpr size_t n = 1;
350     char pool[sizeof(memory::Metadata) + n];
351 
352     using Interface = memory::Interface<AllocatorSpy, CapSpy>;
353 
354     SECTION( "allocation" )
355     {
356         SECTION( "allocation failure" )
357         {
358             auto p = Interface::allocate(n);
359 
360             CHECK( p == nullptr );
361 
362             CHECK( AllocatorSpy::allocate_called );
363             CHECK( AllocatorSpy::allocate_arg == memory::Metadata::calculate_total_size(n) );
364 
365             CHECK_FALSE( CapSpy::update_allocations_called );
366         }
367 
368         SECTION( "success" )
369         {
370             AllocatorSpy::pool = pool;
371 
372             auto p = Interface::allocate(n);
373 
374             CHECK( p > (void*)pool );
375 
376             CHECK( AllocatorSpy::allocate_called );
377             CHECK( AllocatorSpy::allocate_arg == memory::Metadata::calculate_total_size(n) );
378 
379             CHECK( CapSpy::update_allocations_called );
380             CHECK( CapSpy::update_allocations_arg == memory::Metadata::calculate_total_size(n) );
381         }
382     }
383 
384     SECTION( "deallocation" )
385     {
386         SECTION( "nullptr" )
387         {
388             Interface::deallocate(nullptr);
389 
390             CHECK_FALSE( AllocatorSpy::deallocate_called );
391             CHECK_FALSE( CapSpy::update_deallocations_called );
392         }
393 
394         SECTION( "success" )
395         {
396             auto meta_pool = reinterpret_cast<memory::Metadata*>(pool);
397             meta_pool[0] = memory::Metadata(n);
398 
399             auto p = meta_pool[0].payload_offset();
400 
401             Interface::deallocate(p);
402 
403             CHECK( AllocatorSpy::deallocate_called );
404             CHECK( AllocatorSpy::deallocate_arg == (void*)pool );
405             CHECK( CapSpy::update_deallocations_called );
406             CHECK( CapSpy::update_deallocations_arg == memory::Metadata::calculate_total_size(n) );
407         }
408     }
409     AllocatorSpy::pool = nullptr;
410     AllocatorSpy::deallocate_arg = nullptr;
411 }
412 
413 #endif
414 
415