1 // Copyright 2016 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.
4
5 // This code should move into the default Windows shim once the win-specific
6 // allocation shim has been removed, and the generic shim has becaome the
7 // default.
8
9 #include "winheap_stubs_win.h"
10
11 #include <limits.h>
12 #include <malloc.h>
13 #include <new.h>
14 #include <windows.h>
15 #include <algorithm>
16 #include <limits>
17
18 #include "base/allocator/partition_allocator/partition_alloc_base/bits.h"
19 #include "base/allocator/partition_allocator/partition_alloc_base/numerics/safe_conversions.h"
20 #include "base/allocator/partition_allocator/partition_alloc_check.h"
21
22 namespace allocator_shim {
23
24 bool g_is_win_shim_layer_initialized = false;
25
26 namespace {
27
28 const size_t kWindowsPageSize = 4096;
29 const size_t kMaxWindowsAllocation = INT_MAX - kWindowsPageSize;
30
get_heap_handle()31 inline HANDLE get_heap_handle() {
32 return reinterpret_cast<HANDLE>(_get_heap_handle());
33 }
34
35 } // namespace
36
WinHeapMalloc(size_t size)37 void* WinHeapMalloc(size_t size) {
38 if (size < kMaxWindowsAllocation)
39 return HeapAlloc(get_heap_handle(), 0, size);
40 return nullptr;
41 }
42
WinHeapFree(void * ptr)43 void WinHeapFree(void* ptr) {
44 if (!ptr)
45 return;
46
47 HeapFree(get_heap_handle(), 0, ptr);
48 }
49
WinHeapRealloc(void * ptr,size_t size)50 void* WinHeapRealloc(void* ptr, size_t size) {
51 if (!ptr)
52 return WinHeapMalloc(size);
53 if (!size) {
54 WinHeapFree(ptr);
55 return nullptr;
56 }
57 if (size < kMaxWindowsAllocation)
58 return HeapReAlloc(get_heap_handle(), 0, ptr, size);
59 return nullptr;
60 }
61
WinHeapGetSizeEstimate(void * ptr)62 size_t WinHeapGetSizeEstimate(void* ptr) {
63 if (!ptr)
64 return 0;
65
66 return HeapSize(get_heap_handle(), 0, ptr);
67 }
68
69 // Call the new handler, if one has been set.
70 // Returns true on successfully calling the handler, false otherwise.
WinCallNewHandler(size_t size)71 bool WinCallNewHandler(size_t size) {
72 #ifdef _CPPUNWIND
73 #error "Exceptions in allocator shim are not supported!"
74 #endif // _CPPUNWIND
75 // Get the current new handler.
76 _PNH nh = _query_new_handler();
77 if (!nh)
78 return false;
79 // Since exceptions are disabled, we don't really know if new_handler
80 // failed. Assume it will abort if it fails.
81 return nh(size) ? true : false;
82 }
83
84 // The Windows _aligned_* functions are implemented by creating an allocation
85 // with enough space to create an aligned allocation internally. The offset to
86 // the original allocation is prefixed to the aligned allocation so that it can
87 // be correctly freed.
88
89 namespace {
90
91 struct AlignedPrefix {
92 // Offset to the original allocation point.
93 unsigned int original_allocation_offset;
94 // Make sure an unsigned int is enough to store the offset
95 static_assert(
96 kMaxWindowsAllocation < std::numeric_limits<unsigned int>::max(),
97 "original_allocation_offset must be able to fit into an unsigned int");
98 #if BUILDFLAG(PA_DCHECK_IS_ON)
99 // Magic value used to check that _aligned_free() and _aligned_realloc() are
100 // only ever called on an aligned allocated chunk.
101 static constexpr unsigned int kMagic = 0x12003400;
102 unsigned int magic;
103 #endif // BUILDFLAG(PA_DCHECK_IS_ON)
104 };
105
106 // Compute how large an allocation we need to fit an allocation with the given
107 // size and alignment and space for a prefix pointer.
AdjustedSize(size_t size,size_t alignment)108 size_t AdjustedSize(size_t size, size_t alignment) {
109 // Minimal alignment is the prefix size so the prefix is properly aligned.
110 alignment = std::max(alignment, alignof(AlignedPrefix));
111 return size + sizeof(AlignedPrefix) + alignment - 1;
112 }
113
114 // Align the allocation and write the prefix.
AlignAllocation(void * ptr,size_t alignment)115 void* AlignAllocation(void* ptr, size_t alignment) {
116 // Minimal alignment is the prefix size so the prefix is properly aligned.
117 alignment = std::max(alignment, alignof(AlignedPrefix));
118
119 uintptr_t address = reinterpret_cast<uintptr_t>(ptr);
120 address = partition_alloc::internal::base::bits::AlignUp(
121 address + sizeof(AlignedPrefix), alignment);
122
123 // Write the prefix.
124 AlignedPrefix* prefix = reinterpret_cast<AlignedPrefix*>(address) - 1;
125 prefix->original_allocation_offset =
126 partition_alloc::internal::base::checked_cast<unsigned int>(
127 address - reinterpret_cast<uintptr_t>(ptr));
128 #if BUILDFLAG(PA_DCHECK_IS_ON)
129 prefix->magic = AlignedPrefix::kMagic;
130 #endif // BUILDFLAG(PA_DCHECK_IS_ON)
131 return reinterpret_cast<void*>(address);
132 }
133
134 // Return the original allocation from an aligned allocation.
UnalignAllocation(void * ptr)135 void* UnalignAllocation(void* ptr) {
136 AlignedPrefix* prefix = reinterpret_cast<AlignedPrefix*>(ptr) - 1;
137 #if BUILDFLAG(PA_DCHECK_IS_ON)
138 PA_DCHECK(prefix->magic == AlignedPrefix::kMagic);
139 #endif // BUILDFLAG(PA_DCHECK_IS_ON)
140 void* unaligned =
141 static_cast<uint8_t*>(ptr) - prefix->original_allocation_offset;
142 PA_CHECK(unaligned < ptr);
143 PA_CHECK(reinterpret_cast<uintptr_t>(ptr) -
144 reinterpret_cast<uintptr_t>(unaligned) <=
145 kMaxWindowsAllocation);
146 return unaligned;
147 }
148
149 } // namespace
150
WinHeapAlignedMalloc(size_t size,size_t alignment)151 void* WinHeapAlignedMalloc(size_t size, size_t alignment) {
152 PA_CHECK(partition_alloc::internal::base::bits::IsPowerOfTwo(alignment));
153
154 size_t adjusted = AdjustedSize(size, alignment);
155 if (adjusted >= kMaxWindowsAllocation)
156 return nullptr;
157
158 void* ptr = WinHeapMalloc(adjusted);
159 if (!ptr)
160 return nullptr;
161
162 return AlignAllocation(ptr, alignment);
163 }
164
WinHeapAlignedRealloc(void * ptr,size_t size,size_t alignment)165 void* WinHeapAlignedRealloc(void* ptr, size_t size, size_t alignment) {
166 PA_CHECK(partition_alloc::internal::base::bits::IsPowerOfTwo(alignment));
167
168 if (!ptr)
169 return WinHeapAlignedMalloc(size, alignment);
170 if (!size) {
171 WinHeapAlignedFree(ptr);
172 return nullptr;
173 }
174
175 size_t adjusted = AdjustedSize(size, alignment);
176 if (adjusted >= kMaxWindowsAllocation)
177 return nullptr;
178
179 // Try to resize the allocation in place first.
180 void* unaligned = UnalignAllocation(ptr);
181 if (HeapReAlloc(get_heap_handle(), HEAP_REALLOC_IN_PLACE_ONLY, unaligned,
182 adjusted)) {
183 return ptr;
184 }
185
186 // Otherwise manually perform an _aligned_malloc() and copy since an
187 // unaligned allocation from HeapReAlloc() would force us to copy the
188 // allocation twice.
189 void* new_ptr = WinHeapAlignedMalloc(size, alignment);
190 if (!new_ptr)
191 return nullptr;
192
193 size_t gap =
194 reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(unaligned);
195 size_t old_size = WinHeapGetSizeEstimate(unaligned) - gap;
196 memcpy(new_ptr, ptr, std::min(size, old_size));
197 WinHeapAlignedFree(ptr);
198 return new_ptr;
199 }
200
WinHeapAlignedFree(void * ptr)201 void WinHeapAlignedFree(void* ptr) {
202 if (!ptr)
203 return;
204
205 void* original_allocation = UnalignAllocation(ptr);
206 WinHeapFree(original_allocation);
207 }
208
209 } // namespace allocator_shim
210