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