1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #ifndef COURGETTE_MEMORY_ALLOCATOR_H_
6 #define COURGETTE_MEMORY_ALLOCATOR_H_
13 #include "base/files/file.h"
14 #include "base/logging.h"
15 #include "base/memory/raw_ptr.h"
16 #include "base/process/memory.h"
17 #include "build/build_config.h"
21 // A helper class to track down call sites that are not handling error cases.
23 class CheckReturnValue {
25 // Not marked explicit on purpose.
26 CheckReturnValue(T value) : value_(value), checked_(false) { // NOLINT
28 CheckReturnValue(const CheckReturnValue& other)
29 : value_(other.value_), checked_(other.checked_) {
30 other.checked_ = true;
33 CheckReturnValue& operator=(const CheckReturnValue& other) {
36 value_ = other.value_;
37 checked_ = other.checked_;
38 other.checked_ = true;
47 operator const T&() const {
54 mutable bool checked_;
56 typedef CheckReturnValue<bool> CheckBool;
58 typedef bool CheckBool;
63 // Allocates memory for an instance of type T, instantiates an object in that
64 // memory with arguments |args| (of type ArgTypes), and returns the constructed
65 // instance. Returns null if allocation fails.
66 template <class T, class... ArgTypes>
67 T* UncheckedNew(ArgTypes... args) {
69 return base::UncheckedMalloc(sizeof(T), &ram) ? new (ram) T(args...)
73 // Complement of UncheckedNew(): destructs |object| and releases its memory.
75 void UncheckedDelete(T* object) {
82 // A deleter for std::unique_ptr that will delete the object via
85 struct UncheckedDeleter {
86 inline void operator()(T* ptr) const { UncheckedDelete(ptr); }
91 // Manages a read/write virtual mapping of a physical file.
97 // Map a file from beginning to |size|.
98 bool Create(HANDLE file, size_t size);
101 // Returns true iff a mapping has been created.
104 // Returns a writable pointer to the beginning of the memory mapped file.
105 // If Create has not been called successfully, return value is nullptr.
109 bool InitializeView(size_t size);
115 // Manages a temporary file and a memory mapping of the temporary file.
116 // The memory that this class manages holds a pointer back to the TempMapping
117 // object itself, so that given a memory pointer allocated by this class,
118 // you can get a pointer to the TempMapping instance that owns that memory.
124 // Creates a temporary file of size |size| and maps it into the current
125 // process's address space.
126 bool Initialize(size_t size);
128 // Returns a writable pointer to the reserved memory.
129 void* memory() const;
131 // Returns true if the mapping is valid and memory is available.
134 // Returns a pointer to the TempMapping instance that allocated the |mem|
135 // block of memory. It's the callers responsibility to make sure that
136 // the memory block was allocated by the TempMapping class.
137 static TempMapping* GetMappingFromPtr(void* mem);
141 FileMapping mapping_;
144 // A memory allocator class that allocates memory either from the heap or via a
145 // temporary file. The interface is STL inspired but the class does not throw
146 // STL exceptions on allocation failure. Instead it returns nullptr.
147 // A file allocation will be made if either the requested memory size exceeds
148 // |kMaxHeapAllocationSize| or if a heap allocation fails.
149 // Allocating the memory as a mapping of a temporary file solves the problem
150 // that there might not be enough physical memory and pagefile to support the
151 // allocation. This can happen because these resources are too small, or
152 // already committed to other processes. Provided there is enough disk, the
153 // temporary file acts like a pagefile that other processes can't access.
155 class MemoryAllocator {
157 typedef T value_type;
158 typedef value_type* pointer;
159 typedef value_type& reference;
160 typedef const value_type* const_pointer;
161 typedef const value_type& const_reference;
162 typedef size_t size_type;
163 typedef ptrdiff_t difference_type;
165 // Each allocation is tagged with a single byte so that we know how to
167 enum AllocationType : uint8_t {
168 HEAP_ALLOCATION = 0xF0, // Non-trivial constants to detect corruption.
169 FILE_ALLOCATION = 0x0F,
172 // 5MB is the maximum heap allocation size that we'll attempt.
173 // When applying a patch for Chrome 10.X we found that at this
174 // threshold there were 17 allocations higher than this threshold
175 // (largest at 136MB) 10 allocations just below the threshold and 6362
176 // smaller allocations.
177 static const size_t kMaxHeapAllocationSize = 1024 * 1024 * 5;
179 template<class OtherT>
181 // convert a MemoryAllocator<T> to a MemoryAllocator<OtherT>
182 typedef MemoryAllocator<OtherT> other;
187 // We can't use an explicit constructor here, as dictated by our style guide.
188 // The implementation of basic_string in Visual Studio 2010 prevents this.
189 MemoryAllocator(const MemoryAllocator<T>& other) { // NOLINT
192 template <class OtherT>
193 MemoryAllocator(const MemoryAllocator<OtherT>& other) { // NOLINT
199 void deallocate(pointer ptr, size_type size) {
200 uint8_t* mem = reinterpret_cast<uint8_t*>(ptr);
202 if (mem[0] == HEAP_ALLOCATION)
204 else if (mem[0] == FILE_ALLOCATION)
205 UncheckedDelete(TempMapping::GetMappingFromPtr(mem));
210 pointer allocate(size_type count) {
211 // We use the first byte of each allocation to mark the allocation type.
212 // However, so that the allocation is properly aligned, we allocate an
213 // extra element and then use the first byte of the first element
214 // to mark the allocation type.
217 if (count > max_size())
220 size_type bytes = count * sizeof(T);
221 uint8_t* mem = nullptr;
223 // First see if we can do this allocation on the heap.
224 if (count < kMaxHeapAllocationSize &&
225 base::UncheckedMalloc(bytes, reinterpret_cast<void**>(&mem))) {
226 mem[0] = static_cast<uint8_t>(HEAP_ALLOCATION);
228 // Back the allocation with a temp file if either the request exceeds the
229 // max heap allocation threshold or the heap allocation failed.
230 TempMapping* mapping = UncheckedNew<TempMapping>();
232 if (mapping->Initialize(bytes)) {
233 mem = reinterpret_cast<uint8_t*>(mapping->memory());
234 mem[0] = static_cast<uint8_t>(FILE_ALLOCATION);
236 UncheckedDelete(mapping);
240 // If the above fails (e.g. because we are in a sandbox), just try the heap.
241 if (!mem && base::UncheckedMalloc(bytes, reinterpret_cast<void**>(&mem))) {
242 mem[0] = static_cast<uint8_t>(HEAP_ALLOCATION);
244 return mem ? reinterpret_cast<pointer>(mem + sizeof(T)) : nullptr;
247 pointer allocate(size_type count, const void* hint) {
248 return allocate(count);
251 void construct(pointer ptr, const T& value) {
255 void destroy(pointer ptr) {
259 size_type max_size() const {
260 size_type count = static_cast<size_type>(-1) / sizeof(T);
261 return (0 < count ? count : 1);
265 #else // BUILDFLAG(IS_WIN)
267 // On Mac, Linux, we use a bare bones implementation that only does
270 class MemoryAllocator {
272 typedef T value_type;
273 typedef value_type* pointer;
274 typedef value_type& reference;
275 typedef const value_type* const_pointer;
276 typedef const value_type& const_reference;
277 typedef size_t size_type;
278 typedef ptrdiff_t difference_type;
280 template<class OtherT>
282 // convert a MemoryAllocator<T> to a MemoryAllocator<OtherT>
283 typedef MemoryAllocator<OtherT> other;
289 explicit MemoryAllocator(const MemoryAllocator<T>& other) {
292 template<class OtherT>
293 explicit MemoryAllocator(const MemoryAllocator<OtherT>& other) {
299 void deallocate(pointer ptr, size_type size) { free(ptr); }
301 pointer allocate(size_type count) {
302 if (count > max_size())
304 pointer result = nullptr;
305 return base::UncheckedMalloc(count * sizeof(T),
306 reinterpret_cast<void**>(&result))
311 pointer allocate(size_type count, const void* hint) {
312 return allocate(count);
315 void construct(pointer ptr, const T& value) {
319 void destroy(pointer ptr) {
323 size_type max_size() const {
324 size_type count = static_cast<size_type>(-1) / sizeof(T);
325 return (0 < count ? count : 1);
329 #endif // BUILDFLAG(IS_WIN)
331 // Manages a growable buffer. The buffer allocation is done by the
332 // MemoryAllocator class. This class will not throw exceptions so call sites
333 // must be prepared to handle memory allocation failures.
334 // The interface is STL inspired to avoid having to make too many changes
335 // to code that previously was using STL.
336 template<typename T, class Allocator = MemoryAllocator<T> >
337 class NoThrowBuffer {
339 typedef T value_type;
340 static const size_t kAllocationFailure = 0xffffffff;
341 static const size_t kStartSize = sizeof(T) > 0x100 ? 1 : 0x100 / sizeof(T);
343 NoThrowBuffer() : buffer_(nullptr), size_(0), alloc_size_(0) {}
351 alloc_.deallocate(buffer_, alloc_size_);
362 [[nodiscard]] CheckBool reserve(size_t size) {
366 if (size <= alloc_size_)
369 if (size < kStartSize)
372 T* new_buffer = alloc_.allocate(size);
375 alloc_size_ = kAllocationFailure;
378 memcpy(new_buffer, buffer_, size_ * sizeof(T));
379 alloc_.deallocate(buffer_, alloc_size_);
381 buffer_ = new_buffer;
388 [[nodiscard]] CheckBool append(const T* data, size_t size) {
392 if (size > alloc_.max_size() - size_)
398 // Disallow source range from overlapping with buffer_, since in this case
399 // reallocation would cause use-after-free.
400 DCHECK(data + size <= buffer_ || data >= buffer_ + alloc_size_);
402 if ((alloc_size_ - size_) < size) {
403 const size_t max_size = alloc_.max_size();
404 size_t new_size = alloc_size_ ? alloc_size_ : kStartSize;
405 while (new_size < size_ + size) {
406 if (new_size < max_size - new_size) {
412 if (!reserve(new_size))
416 memcpy(buffer_ + size_, data, size * sizeof(T));
422 [[nodiscard]] CheckBool resize(size_t size, const T& init_value) {
426 for (size_t i = size_; i < size; ++i)
427 buffer_[i] = init_value;
428 } else if (size < size_) {
429 // TODO(tommi): Should we allocate a new, smaller buffer?
430 // It might be faster for us to simply change the size.
438 [[nodiscard]] CheckBool push_back(const T& item) { return append(&item, 1); }
440 const T& back() const {
441 return buffer_[size_ - 1];
445 return buffer_[size_ - 1];
448 const T* begin() const {
460 const T* end() const {
463 return buffer_ + size_;
469 return buffer_ + size_;
472 const T& operator[](size_t index) const {
473 DCHECK(index < size_);
474 return buffer_[index];
477 T& operator[](size_t index) {
478 DCHECK(index < size_);
479 return buffer_[index];
482 size_t size() const {
486 size_t capacity() const {
494 // Returns true if an allocation failure has ever occurred for this object.
495 bool failed() const {
496 return alloc_size_ == kAllocationFailure;
501 size_t size_; // how much of the buffer we're using.
502 size_t alloc_size_; // how much space we have allocated.
506 } // namespace courgette
508 #endif // COURGETTE_MEMORY_ALLOCATOR_H_