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