Skip to content

Commit 0dbed61

Browse files
committed
Rework std::sys::windows::alloc
Add documentation to the system functions and `SAFETY` comments. Refactored helper functions, fixing the correctness of `get_header`.
1 parent db492ec commit 0dbed61

File tree

3 files changed

+173
-32
lines changed

3 files changed

+173
-32
lines changed

library/std/src/sys/windows/alloc.rs

+164-25
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,200 @@
1+
#![deny(unsafe_op_in_unsafe_fn)]
2+
13
use crate::alloc::{GlobalAlloc, Layout, System};
4+
use crate::ptr;
25
use crate::sys::c;
36
use crate::sys_common::alloc::{realloc_fallback, MIN_ALIGN};
47

5-
#[repr(C)]
6-
struct Header(*mut u8);
8+
#[cfg(test)]
9+
mod tests;
710

8-
unsafe fn get_header<'a>(ptr: *mut u8) -> &'a mut Header {
9-
&mut *(ptr as *mut Header).offset(-1)
10-
}
11+
// Heap memory management on Windows is done by using the system Heap API (heapapi.h)
12+
// See https://docs.microsoft.com/windows/win32/api/heapapi/
13+
14+
// Flag to indicate that the memory returned by `HeapAlloc` should be zeroed.
15+
const HEAP_ZERO_MEMORY: c::DWORD = 0x00000008;
16+
17+
extern "system" {
18+
// Get a handle to the default heap of the current process, or null if the operation fails.
19+
//
20+
// See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-getprocessheap
21+
fn GetProcessHeap() -> c::HANDLE;
22+
23+
// Allocate a block of `dwBytes` bytes of memory from a given heap `hHeap`.
24+
// The allocated memory may be uninitialized, or zeroed if `dwFlags` is
25+
// set to `HEAP_ZERO_MEMORY`.
26+
//
27+
// Returns a pointer to the newly-allocated memory or null if the operation fails.
28+
// The returned pointer will be aligned to at least `MIN_ALIGN`.
29+
//
30+
// SAFETY:
31+
// - `hHeap` must be a non-null handle returned by `GetProcessHeap`.
32+
// - `dwFlags` must be set to either zero or `HEAP_ZERO_MEMORY`.
33+
//
34+
// Note that `dwBytes` is allowed to be zero, contrary to some other allocators.
35+
//
36+
// See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heapalloc
37+
fn HeapAlloc(hHeap: c::HANDLE, dwFlags: c::DWORD, dwBytes: c::SIZE_T) -> c::LPVOID;
1138

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
39+
// Reallocate a block of memory behind a given pointer `lpMem` from a given heap `hHeap`,
40+
// to a block of at least `dwBytes` bytes, either shrinking the block in place,
41+
// or allocating at a new location, copying memory, and freeing the original location.
42+
//
43+
// Returns a pointer to the reallocated memory or null if the operation fails.
44+
// The returned pointer will be aligned to at least `MIN_ALIGN`.
45+
// If the operation fails the given block will never have been freed.
46+
//
47+
// SAFETY:
48+
// - `hHeap` must be a non-null handle returned by `GetProcessHeap`.
49+
// - `dwFlags` must be set to zero.
50+
// - `lpMem` must be a non-null pointer to an allocated block returned by `HeapAlloc` or
51+
// `HeapReAlloc`, that has not already been freed.
52+
// If the block was successfully reallocated at a new location, pointers pointing to
53+
// the freed memory, such as `lpMem`, must not be dereferenced ever again.
54+
//
55+
// Note that `dwBytes` is allowed to be zero, contrary to some other allocators.
56+
//
57+
// See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heaprealloc
58+
fn HeapReAlloc(
59+
hHeap: c::HANDLE,
60+
dwFlags: c::DWORD,
61+
lpMem: c::LPVOID,
62+
dwBytes: c::SIZE_T,
63+
) -> c::LPVOID;
64+
65+
// Free a block of memory behind a given pointer `lpMem` from a given heap `hHeap`.
66+
// Returns a nonzero value if the operation is successful, and zero if the operation fails.
67+
//
68+
// SAFETY:
69+
// - `dwFlags` must be set to zero.
70+
// - `lpMem` must be a pointer to an allocated block returned by `HeapAlloc` or `HeapReAlloc`,
71+
// that has not already been freed.
72+
// If the block was successfully freed, pointers pointing to the freed memory, such as `lpMem`,
73+
// must not be dereferenced ever again.
74+
//
75+
// Note that both `hHeap` is allowed to be any value, and `lpMem` is allowed to be null,
76+
// both of which will not cause the operation to fail.
77+
//
78+
// See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heapfree
79+
fn HeapFree(hHeap: c::HANDLE, dwFlags: c::DWORD, lpMem: c::LPVOID) -> c::BOOL;
1680
}
1781

82+
// Header containing a pointer to the start of an allocated block.
83+
// SAFETY: size and alignment must be <= `MIN_ALIGN`.
84+
#[repr(C)]
85+
struct Header(*mut u8);
86+
87+
// Allocates a block of optionally zeroed memory for a given `layout`.
88+
// Returns a pointer satisfying the guarantees of `System` about allocated pointers.
1889
#[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;
90+
unsafe fn allocate(layout: Layout, zeroed: bool) -> *mut u8 {
91+
let heap = unsafe { GetProcessHeap() };
92+
if heap.is_null() {
93+
// Allocation has failed, could not get the current process heap.
94+
return ptr::null_mut();
2295
}
2396

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()) }
97+
// Allocated memory will be either zeroed or uninitialized.
98+
let flags = if zeroed { HEAP_ZERO_MEMORY } else { 0 };
99+
100+
if layout.align() <= MIN_ALIGN {
101+
// SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`.
102+
// The returned pointer points to the start of an allocated block.
103+
unsafe { HeapAlloc(heap, flags, layout.size()) as *mut u8 }
104+
} else {
105+
// Allocate extra padding in order to be able to satisfy the alignment.
106+
let total = layout.align() + layout.size();
107+
108+
// SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`.
109+
let ptr = unsafe { HeapAlloc(heap, flags, total) as *mut u8 };
110+
if ptr.is_null() {
111+
// Allocation has failed.
112+
return ptr::null_mut();
113+
}
114+
115+
// Create a correctly aligned pointer offset from the start of the allocated block,
116+
// and write a header before it.
117+
118+
let offset = layout.align() - (ptr as usize & (layout.align() - 1));
119+
// SAFETY: `MIN_ALIGN` <= `offset` <= `layout.align()` and the size of the allocated
120+
// block is `layout.align() + layout.size()`. `aligned` will thus be a correctly aligned
121+
// pointer inside the allocated block with at least `layout.size()` bytes after it and at
122+
// least `MIN_ALIGN` bytes of padding before it.
123+
let aligned = unsafe { ptr.add(offset) };
124+
// SAFETY: Because the size and alignment of a header is <= `MIN_ALIGN` and `aligned`
125+
// is aligned to at least `MIN_ALIGN` and has at least `MIN_ALIGN` bytes of padding before
126+
// it, it is safe to write a header directly before it.
127+
unsafe { ptr::write((aligned as *mut Header).offset(-1), Header(ptr)) };
128+
129+
// SAFETY: The returned pointer does not point to the to the start of an allocated block,
130+
// but there is a header readable directly before it containing the location of the start
131+
// of the block.
132+
aligned
133+
}
27134
}
28135

136+
// All pointers returned by this allocator have, in addition to the guarantees of `GlobalAlloc`, the
137+
// following properties:
138+
//
139+
// If the pointer was allocated or reallocated with a `layout` specifying an alignment <= `MIN_ALIGN`
140+
// the pointer will be aligned to at least `MIN_ALIGN` and point to the start of the allocated block.
141+
//
142+
// If the pointer was allocated or reallocated with a `layout` specifying an alignment > `MIN_ALIGN`
143+
// the pointer will be aligned to the specified alignment and not point to the start of the allocated block.
144+
// Instead there will be a header readable directly before the returned pointer, containing the actual
145+
// location of the start of the block.
29146
#[stable(feature = "alloc_system_type", since = "1.28.0")]
30147
unsafe impl GlobalAlloc for System {
31148
#[inline]
32149
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
33-
allocate_with_flags(layout, 0)
150+
// SAFETY: pointers returned by `allocate` satisfy the guarantees of `System`
151+
unsafe { allocate(layout, false) }
34152
}
35153

36154
#[inline]
37155
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
38-
allocate_with_flags(layout, c::HEAP_ZERO_MEMORY)
156+
// SAFETY: pointers returned by `allocate` satisfy the guarantees of `System`
157+
unsafe { allocate(layout, true) }
39158
}
40159

41160
#[inline]
42161
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);
162+
let block = {
163+
if layout.align() <= MIN_ALIGN {
164+
ptr
165+
} else {
166+
// The location of the start of the block is stored in the padding before `ptr`.
167+
168+
// SAFETY: Because of the contract of `System`, `ptr` is guaranteed to be non-null
169+
// and have a header readable directly before it.
170+
unsafe { ptr::read((ptr as *mut Header).offset(-1)).0 }
171+
}
172+
};
173+
174+
// SAFETY: `block` is a pointer to the start of an allocated block.
175+
unsafe {
176+
let err = HeapFree(GetProcessHeap(), 0, block as c::LPVOID);
49177
debug_assert!(err != 0, "Failed to free heap memory: {}", c::GetLastError());
50178
}
51179
}
52180

53181
#[inline]
54182
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
55183
if layout.align() <= MIN_ALIGN {
56-
c::HeapReAlloc(c::GetProcessHeap(), 0, ptr as c::LPVOID, new_size) as *mut u8
184+
let heap = unsafe { GetProcessHeap() };
185+
if heap.is_null() {
186+
// Reallocation has failed, could not get the current process heap.
187+
return ptr::null_mut();
188+
}
189+
190+
// SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`,
191+
// `ptr` is a pointer to the start of an allocated block.
192+
// The returned pointer points to the start of an allocated block.
193+
unsafe { HeapReAlloc(heap, 0, ptr as c::LPVOID, new_size) as *mut u8 }
57194
} else {
58-
realloc_fallback(self, ptr, layout, new_size)
195+
// SAFETY: `realloc_fallback` is implemented using `dealloc` and `alloc`, which will
196+
// correctly handle `ptr` and return a pointer satisfying the guarantees of `System`
197+
unsafe { realloc_fallback(self, ptr, layout, new_size) }
59198
}
60199
}
61200
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use super::{Header, MIN_ALIGN};
2+
use crate::mem;
3+
4+
#[test]
5+
fn alloc_header() {
6+
// Header must fit in the padding before an aligned pointer
7+
assert!(mem::size_of::<Header>() <= MIN_ALIGN);
8+
assert!(mem::align_of::<Header>() <= MIN_ALIGN);
9+
}

library/std/src/sys/windows/c.rs

-7
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,6 @@ pub const FD_SETSIZE: usize = 64;
285285

286286
pub const STACK_SIZE_PARAM_IS_A_RESERVATION: DWORD = 0x00010000;
287287

288-
pub const HEAP_ZERO_MEMORY: DWORD = 0x00000008;
289-
290288
pub const STATUS_SUCCESS: NTSTATUS = 0x00000000;
291289

292290
#[repr(C)]
@@ -1017,11 +1015,6 @@ extern "system" {
10171015
timeout: *const timeval,
10181016
) -> c_int;
10191017

1020-
pub fn GetProcessHeap() -> HANDLE;
1021-
pub fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) -> LPVOID;
1022-
pub fn HeapReAlloc(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID, dwBytes: SIZE_T) -> LPVOID;
1023-
pub fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL;
1024-
10251018
// >= Vista / Server 2008
10261019
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
10271020
pub fn CreateSymbolicLinkW(

0 commit comments

Comments
 (0)