|
| 1 | +#![deny(unsafe_op_in_unsafe_fn)] |
| 2 | + |
1 | 3 | use crate::alloc::{GlobalAlloc, Layout, System};
|
| 4 | +use crate::ffi::c_void; |
| 5 | +use crate::ptr; |
| 6 | +use crate::sync::atomic::{AtomicPtr, Ordering}; |
2 | 7 | use crate::sys::c;
|
3 | 8 | use crate::sys_common::alloc::{realloc_fallback, MIN_ALIGN};
|
4 | 9 |
|
5 |
| -#[repr(C)] |
6 |
| -struct Header(*mut u8); |
| 10 | +#[cfg(test)] |
| 11 | +mod tests; |
| 12 | + |
| 13 | +// Heap memory management on Windows is done by using the system Heap API (heapapi.h) |
| 14 | +// See https://docs.microsoft.com/windows/win32/api/heapapi/ |
| 15 | + |
| 16 | +// Flag to indicate that the memory returned by `HeapAlloc` should be zeroed. |
| 17 | +const HEAP_ZERO_MEMORY: c::DWORD = 0x00000008; |
7 | 18 |
|
8 |
| -unsafe fn get_header<'a>(ptr: *mut u8) -> &'a mut Header { |
9 |
| - &mut *(ptr as *mut Header).offset(-1) |
| 19 | +extern "system" { |
| 20 | + // Get a handle to the default heap of the current process, or null if the operation fails. |
| 21 | + // |
| 22 | + // SAFETY: Successful calls to this function within the same process are assumed to |
| 23 | + // always return the same handle, which remains valid for the entire lifetime of the process. |
| 24 | + // |
| 25 | + // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-getprocessheap |
| 26 | + fn GetProcessHeap() -> c::HANDLE; |
| 27 | + |
| 28 | + // Allocate a block of `dwBytes` bytes of memory from a given heap `hHeap`. |
| 29 | + // The allocated memory may be uninitialized, or zeroed if `dwFlags` is |
| 30 | + // set to `HEAP_ZERO_MEMORY`. |
| 31 | + // |
| 32 | + // Returns a pointer to the newly-allocated memory or null if the operation fails. |
| 33 | + // The returned pointer will be aligned to at least `MIN_ALIGN`. |
| 34 | + // |
| 35 | + // SAFETY: |
| 36 | + // - `hHeap` must be a non-null handle returned by `GetProcessHeap`. |
| 37 | + // - `dwFlags` must be set to either zero or `HEAP_ZERO_MEMORY`. |
| 38 | + // |
| 39 | + // Note that `dwBytes` is allowed to be zero, contrary to some other allocators. |
| 40 | + // |
| 41 | + // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heapalloc |
| 42 | + fn HeapAlloc(hHeap: c::HANDLE, dwFlags: c::DWORD, dwBytes: c::SIZE_T) -> c::LPVOID; |
| 43 | + |
| 44 | + // Reallocate a block of memory behind a given pointer `lpMem` from a given heap `hHeap`, |
| 45 | + // to a block of at least `dwBytes` bytes, either shrinking the block in place, |
| 46 | + // or allocating at a new location, copying memory, and freeing the original location. |
| 47 | + // |
| 48 | + // Returns a pointer to the reallocated memory or null if the operation fails. |
| 49 | + // The returned pointer will be aligned to at least `MIN_ALIGN`. |
| 50 | + // If the operation fails the given block will never have been freed. |
| 51 | + // |
| 52 | + // SAFETY: |
| 53 | + // - `hHeap` must be a non-null handle returned by `GetProcessHeap`. |
| 54 | + // - `dwFlags` must be set to zero. |
| 55 | + // - `lpMem` must be a non-null pointer to an allocated block returned by `HeapAlloc` or |
| 56 | + // `HeapReAlloc`, that has not already been freed. |
| 57 | + // If the block was successfully reallocated at a new location, pointers pointing to |
| 58 | + // the freed memory, such as `lpMem`, must not be dereferenced ever again. |
| 59 | + // |
| 60 | + // Note that `dwBytes` is allowed to be zero, contrary to some other allocators. |
| 61 | + // |
| 62 | + // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heaprealloc |
| 63 | + fn HeapReAlloc( |
| 64 | + hHeap: c::HANDLE, |
| 65 | + dwFlags: c::DWORD, |
| 66 | + lpMem: c::LPVOID, |
| 67 | + dwBytes: c::SIZE_T, |
| 68 | + ) -> c::LPVOID; |
| 69 | + |
| 70 | + // Free a block of memory behind a given pointer `lpMem` from a given heap `hHeap`. |
| 71 | + // Returns a nonzero value if the operation is successful, and zero if the operation fails. |
| 72 | + // |
| 73 | + // SAFETY: |
| 74 | + // - `hHeap` must be a non-null handle returned by `GetProcessHeap`. |
| 75 | + // - `dwFlags` must be set to zero. |
| 76 | + // - `lpMem` must be a pointer to an allocated block returned by `HeapAlloc` or `HeapReAlloc`, |
| 77 | + // that has not already been freed. |
| 78 | + // If the block was successfully freed, pointers pointing to the freed memory, such as `lpMem`, |
| 79 | + // must not be dereferenced ever again. |
| 80 | + // |
| 81 | + // Note that `lpMem` is allowed to be null, which will not cause the operation to fail. |
| 82 | + // |
| 83 | + // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heapfree |
| 84 | + fn HeapFree(hHeap: c::HANDLE, dwFlags: c::DWORD, lpMem: c::LPVOID) -> c::BOOL; |
10 | 85 | }
|
11 | 86 |
|
12 |
| -unsafe fn align_ptr(ptr: *mut u8, align: usize) -> *mut u8 { |
13 |
| - let aligned = ptr.add(align - (ptr as usize & (align - 1))); |
14 |
| - *get_header(aligned) = Header(ptr); |
15 |
| - aligned |
| 87 | +// Cached handle to the default heap of the current process. |
| 88 | +// Either a non-null handle returned by `GetProcessHeap`, or null when not yet initialized or `GetProcessHeap` failed. |
| 89 | +static HEAP: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut()); |
| 90 | + |
| 91 | +// Get a handle to the default heap of the current process, or null if the operation fails. |
| 92 | +// If this operation is successful, `HEAP` will be successfully initialized and contain |
| 93 | +// a non-null handle returned by `GetProcessHeap`. |
| 94 | +#[inline] |
| 95 | +fn init_or_get_process_heap() -> c::HANDLE { |
| 96 | + let heap = HEAP.load(Ordering::Relaxed); |
| 97 | + if heap.is_null() { |
| 98 | + // `HEAP` has not yet been successfully initialized |
| 99 | + let heap = unsafe { GetProcessHeap() }; |
| 100 | + if !heap.is_null() { |
| 101 | + // SAFETY: No locking is needed because within the same process, |
| 102 | + // successful calls to `GetProcessHeap` will always return the same value, even on different threads. |
| 103 | + HEAP.store(heap, Ordering::Release); |
| 104 | + |
| 105 | + // SAFETY: `HEAP` contains a non-null handle returned by `GetProcessHeap` |
| 106 | + heap |
| 107 | + } else { |
| 108 | + // Could not get the current process heap. |
| 109 | + ptr::null_mut() |
| 110 | + } |
| 111 | + } else { |
| 112 | + // SAFETY: `HEAP` contains a non-null handle returned by `GetProcessHeap` |
| 113 | + heap |
| 114 | + } |
16 | 115 | }
|
17 | 116 |
|
| 117 | +// Get a non-null handle to the default heap of the current process. |
| 118 | +// SAFETY: `HEAP` must have been successfully initialized. |
18 | 119 | #[inline]
|
19 |
| -unsafe fn allocate_with_flags(layout: Layout, flags: c::DWORD) -> *mut u8 { |
20 |
| - if layout.align() <= MIN_ALIGN { |
21 |
| - return c::HeapAlloc(c::GetProcessHeap(), flags, layout.size()) as *mut u8; |
| 120 | +unsafe fn get_process_heap() -> c::HANDLE { |
| 121 | + HEAP.load(Ordering::Acquire) |
| 122 | +} |
| 123 | + |
| 124 | +// Header containing a pointer to the start of an allocated block. |
| 125 | +// SAFETY: Size and alignment must be <= `MIN_ALIGN`. |
| 126 | +#[repr(C)] |
| 127 | +struct Header(*mut u8); |
| 128 | + |
| 129 | +// Allocate a block of optionally zeroed memory for a given `layout`. |
| 130 | +// SAFETY: Returns a pointer satisfying the guarantees of `System` about allocated pointers, |
| 131 | +// or null if the operation fails. If this returns non-null `HEAP` will have been successfully |
| 132 | +// initialized. |
| 133 | +#[inline] |
| 134 | +unsafe fn allocate(layout: Layout, zeroed: bool) -> *mut u8 { |
| 135 | + let heap = init_or_get_process_heap(); |
| 136 | + if heap.is_null() { |
| 137 | + // Allocation has failed, could not get the current process heap. |
| 138 | + return ptr::null_mut(); |
22 | 139 | }
|
23 | 140 |
|
24 |
| - let size = layout.size() + layout.align(); |
25 |
| - let ptr = c::HeapAlloc(c::GetProcessHeap(), flags, size); |
26 |
| - if ptr.is_null() { ptr as *mut u8 } else { align_ptr(ptr as *mut u8, layout.align()) } |
| 141 | + // Allocated memory will be either zeroed or uninitialized. |
| 142 | + let flags = if zeroed { HEAP_ZERO_MEMORY } else { 0 }; |
| 143 | + |
| 144 | + if layout.align() <= MIN_ALIGN { |
| 145 | + // SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`. |
| 146 | + // The returned pointer points to the start of an allocated block. |
| 147 | + unsafe { HeapAlloc(heap, flags, layout.size()) as *mut u8 } |
| 148 | + } else { |
| 149 | + // Allocate extra padding in order to be able to satisfy the alignment. |
| 150 | + let total = layout.align() + layout.size(); |
| 151 | + |
| 152 | + // SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`. |
| 153 | + let ptr = unsafe { HeapAlloc(heap, flags, total) as *mut u8 }; |
| 154 | + if ptr.is_null() { |
| 155 | + // Allocation has failed. |
| 156 | + return ptr::null_mut(); |
| 157 | + } |
| 158 | + |
| 159 | + // Create a correctly aligned pointer offset from the start of the allocated block, |
| 160 | + // and write a header before it. |
| 161 | + |
| 162 | + let offset = layout.align() - (ptr as usize & (layout.align() - 1)); |
| 163 | + // SAFETY: `MIN_ALIGN` <= `offset` <= `layout.align()` and the size of the allocated |
| 164 | + // block is `layout.align() + layout.size()`. `aligned` will thus be a correctly aligned |
| 165 | + // pointer inside the allocated block with at least `layout.size()` bytes after it and at |
| 166 | + // least `MIN_ALIGN` bytes of padding before it. |
| 167 | + let aligned = unsafe { ptr.add(offset) }; |
| 168 | + // SAFETY: Because the size and alignment of a header is <= `MIN_ALIGN` and `aligned` |
| 169 | + // is aligned to at least `MIN_ALIGN` and has at least `MIN_ALIGN` bytes of padding before |
| 170 | + // it, it is safe to write a header directly before it. |
| 171 | + unsafe { ptr::write((aligned as *mut Header).offset(-1), Header(ptr)) }; |
| 172 | + |
| 173 | + // SAFETY: The returned pointer does not point to the to the start of an allocated block, |
| 174 | + // but there is a header readable directly before it containing the location of the start |
| 175 | + // of the block. |
| 176 | + aligned |
| 177 | + } |
27 | 178 | }
|
28 | 179 |
|
| 180 | +// All pointers returned by this allocator have, in addition to the guarantees of `GlobalAlloc`, the |
| 181 | +// following properties: |
| 182 | +// |
| 183 | +// If the pointer was allocated or reallocated with a `layout` specifying an alignment <= `MIN_ALIGN` |
| 184 | +// the pointer will be aligned to at least `MIN_ALIGN` and point to the start of the allocated block. |
| 185 | +// |
| 186 | +// If the pointer was allocated or reallocated with a `layout` specifying an alignment > `MIN_ALIGN` |
| 187 | +// the pointer will be aligned to the specified alignment and not point to the start of the allocated block. |
| 188 | +// Instead there will be a header readable directly before the returned pointer, containing the actual |
| 189 | +// location of the start of the block. |
29 | 190 | #[stable(feature = "alloc_system_type", since = "1.28.0")]
|
30 | 191 | unsafe impl GlobalAlloc for System {
|
31 | 192 | #[inline]
|
32 | 193 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
33 |
| - allocate_with_flags(layout, 0) |
| 194 | + // SAFETY: Pointers returned by `allocate` satisfy the guarantees of `System` |
| 195 | + let zeroed = false; |
| 196 | + unsafe { allocate(layout, zeroed) } |
34 | 197 | }
|
35 | 198 |
|
36 | 199 | #[inline]
|
37 | 200 | unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
|
38 |
| - allocate_with_flags(layout, c::HEAP_ZERO_MEMORY) |
| 201 | + // SAFETY: Pointers returned by `allocate` satisfy the guarantees of `System` |
| 202 | + let zeroed = true; |
| 203 | + unsafe { allocate(layout, zeroed) } |
39 | 204 | }
|
40 | 205 |
|
41 | 206 | #[inline]
|
42 | 207 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
43 |
| - if layout.align() <= MIN_ALIGN { |
44 |
| - let err = c::HeapFree(c::GetProcessHeap(), 0, ptr as c::LPVOID); |
45 |
| - debug_assert!(err != 0, "Failed to free heap memory: {}", c::GetLastError()); |
46 |
| - } else { |
47 |
| - let header = get_header(ptr); |
48 |
| - let err = c::HeapFree(c::GetProcessHeap(), 0, header.0 as c::LPVOID); |
49 |
| - debug_assert!(err != 0, "Failed to free heap memory: {}", c::GetLastError()); |
50 |
| - } |
| 208 | + let block = { |
| 209 | + if layout.align() <= MIN_ALIGN { |
| 210 | + ptr |
| 211 | + } else { |
| 212 | + // The location of the start of the block is stored in the padding before `ptr`. |
| 213 | + |
| 214 | + // SAFETY: Because of the contract of `System`, `ptr` is guaranteed to be non-null |
| 215 | + // and have a header readable directly before it. |
| 216 | + unsafe { ptr::read((ptr as *mut Header).offset(-1)).0 } |
| 217 | + } |
| 218 | + }; |
| 219 | + |
| 220 | + // SAFETY: because `ptr` has been successfully allocated with this allocator, |
| 221 | + // `HEAP` must have been successfully initialized. |
| 222 | + let heap = unsafe { get_process_heap() }; |
| 223 | + |
| 224 | + // SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`, |
| 225 | + // `block` is a pointer to the start of an allocated block. |
| 226 | + unsafe { HeapFree(heap, 0, block as c::LPVOID) }; |
51 | 227 | }
|
52 | 228 |
|
53 | 229 | #[inline]
|
54 | 230 | unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
|
55 | 231 | if layout.align() <= MIN_ALIGN {
|
56 |
| - c::HeapReAlloc(c::GetProcessHeap(), 0, ptr as c::LPVOID, new_size) as *mut u8 |
| 232 | + // SAFETY: because `ptr` has been successfully allocated with this allocator, |
| 233 | + // `HEAP` must have been successfully initialized. |
| 234 | + let heap = unsafe { get_process_heap() }; |
| 235 | + |
| 236 | + // SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`, |
| 237 | + // `ptr` is a pointer to the start of an allocated block. |
| 238 | + // The returned pointer points to the start of an allocated block. |
| 239 | + unsafe { HeapReAlloc(heap, 0, ptr as c::LPVOID, new_size) as *mut u8 } |
57 | 240 | } else {
|
58 |
| - realloc_fallback(self, ptr, layout, new_size) |
| 241 | + // SAFETY: `realloc_fallback` is implemented using `dealloc` and `alloc`, which will |
| 242 | + // correctly handle `ptr` and return a pointer satisfying the guarantees of `System` |
| 243 | + unsafe { realloc_fallback(self, ptr, layout, new_size) } |
59 | 244 | }
|
60 | 245 | }
|
61 | 246 | }
|
0 commit comments