-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Rework std::sys::windows::alloc
#83065
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
0dbed61
b01bf0e
4cce9e3
c86e098
db1d003
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
std::sys::windows::alloc
Add documentation to the system functions and `SAFETY` comments. Refactored helper functions, fixing the correctness of `get_header`.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,61 +1,200 @@ | ||||||||
| #![deny(unsafe_op_in_unsafe_fn)] | ||||||||
|
|
||||||||
| use crate::alloc::{GlobalAlloc, Layout, System}; | ||||||||
| use crate::ptr; | ||||||||
| use crate::sys::c; | ||||||||
| use crate::sys_common::alloc::{realloc_fallback, MIN_ALIGN}; | ||||||||
|
|
||||||||
| #[repr(C)] | ||||||||
| struct Header(*mut u8); | ||||||||
| #[cfg(test)] | ||||||||
| mod tests; | ||||||||
|
|
||||||||
| unsafe fn get_header<'a>(ptr: *mut u8) -> &'a mut Header { | ||||||||
| &mut *(ptr as *mut Header).offset(-1) | ||||||||
| } | ||||||||
| // Heap memory management on Windows is done by using the system Heap API (heapapi.h) | ||||||||
| // See https://docs.microsoft.com/windows/win32/api/heapapi/ | ||||||||
|
|
||||||||
| // Flag to indicate that the memory returned by `HeapAlloc` should be zeroed. | ||||||||
| const HEAP_ZERO_MEMORY: c::DWORD = 0x00000008; | ||||||||
|
|
||||||||
| extern "system" { | ||||||||
| // Get a handle to the default heap of the current process, or null if the operation fails. | ||||||||
| // | ||||||||
| // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-getprocessheap | ||||||||
| fn GetProcessHeap() -> c::HANDLE; | ||||||||
|
|
||||||||
| // Allocate a block of `dwBytes` bytes of memory from a given heap `hHeap`. | ||||||||
| // The allocated memory may be uninitialized, or zeroed if `dwFlags` is | ||||||||
| // set to `HEAP_ZERO_MEMORY`. | ||||||||
| // | ||||||||
| // Returns a pointer to the newly-allocated memory or null if the operation fails. | ||||||||
| // The returned pointer will be aligned to at least `MIN_ALIGN`. | ||||||||
| // | ||||||||
| // SAFETY: | ||||||||
| // - `hHeap` must be a non-null handle returned by `GetProcessHeap`. | ||||||||
| // - `dwFlags` must be set to either zero or `HEAP_ZERO_MEMORY`. | ||||||||
| // | ||||||||
| // Note that `dwBytes` is allowed to be zero, contrary to some other allocators. | ||||||||
| // | ||||||||
| // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heapalloc | ||||||||
| fn HeapAlloc(hHeap: c::HANDLE, dwFlags: c::DWORD, dwBytes: c::SIZE_T) -> c::LPVOID; | ||||||||
|
|
||||||||
| unsafe fn align_ptr(ptr: *mut u8, align: usize) -> *mut u8 { | ||||||||
| let aligned = ptr.add(align - (ptr as usize & (align - 1))); | ||||||||
| *get_header(aligned) = Header(ptr); | ||||||||
| aligned | ||||||||
| // Reallocate a block of memory behind a given pointer `lpMem` from a given heap `hHeap`, | ||||||||
| // to a block of at least `dwBytes` bytes, either shrinking the block in place, | ||||||||
| // or allocating at a new location, copying memory, and freeing the original location. | ||||||||
| // | ||||||||
| // Returns a pointer to the reallocated memory or null if the operation fails. | ||||||||
| // The returned pointer will be aligned to at least `MIN_ALIGN`. | ||||||||
| // If the operation fails the given block will never have been freed. | ||||||||
| // | ||||||||
| // SAFETY: | ||||||||
| // - `hHeap` must be a non-null handle returned by `GetProcessHeap`. | ||||||||
| // - `dwFlags` must be set to zero. | ||||||||
| // - `lpMem` must be a non-null pointer to an allocated block returned by `HeapAlloc` or | ||||||||
| // `HeapReAlloc`, that has not already been freed. | ||||||||
| // If the block was successfully reallocated at a new location, pointers pointing to | ||||||||
| // the freed memory, such as `lpMem`, must not be dereferenced ever again. | ||||||||
| // | ||||||||
| // Note that `dwBytes` is allowed to be zero, contrary to some other allocators. | ||||||||
| // | ||||||||
| // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heaprealloc | ||||||||
| fn HeapReAlloc( | ||||||||
| hHeap: c::HANDLE, | ||||||||
| dwFlags: c::DWORD, | ||||||||
| lpMem: c::LPVOID, | ||||||||
| dwBytes: c::SIZE_T, | ||||||||
| ) -> c::LPVOID; | ||||||||
|
|
||||||||
| // Free a block of memory behind a given pointer `lpMem` from a given heap `hHeap`. | ||||||||
| // Returns a nonzero value if the operation is successful, and zero if the operation fails. | ||||||||
| // | ||||||||
| // SAFETY: | ||||||||
| // - `dwFlags` must be set to zero. | ||||||||
| // - `lpMem` must be a pointer to an allocated block returned by `HeapAlloc` or `HeapReAlloc`, | ||||||||
| // that has not already been freed. | ||||||||
| // If the block was successfully freed, pointers pointing to the freed memory, such as `lpMem`, | ||||||||
| // must not be dereferenced ever again. | ||||||||
| // | ||||||||
| // Note that both `hHeap` is allowed to be any value, and `lpMem` is allowed to be null, | ||||||||
| // both of which will not cause the operation to fail. | ||||||||
| // | ||||||||
| // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heapfree | ||||||||
| fn HeapFree(hHeap: c::HANDLE, dwFlags: c::DWORD, lpMem: c::LPVOID) -> c::BOOL; | ||||||||
| } | ||||||||
|
|
||||||||
| // Header containing a pointer to the start of an allocated block. | ||||||||
| // SAFETY: size and alignment must be <= `MIN_ALIGN`. | ||||||||
| #[repr(C)] | ||||||||
| struct Header(*mut u8); | ||||||||
|
|
||||||||
| // Allocates a block of optionally zeroed memory for a given `layout`. | ||||||||
| // Returns a pointer satisfying the guarantees of `System` about allocated pointers. | ||||||||
| #[inline] | ||||||||
| unsafe fn allocate_with_flags(layout: Layout, flags: c::DWORD) -> *mut u8 { | ||||||||
| if layout.align() <= MIN_ALIGN { | ||||||||
| return c::HeapAlloc(c::GetProcessHeap(), flags, layout.size()) as *mut u8; | ||||||||
| unsafe fn allocate(layout: Layout, zeroed: bool) -> *mut u8 { | ||||||||
| let heap = unsafe { GetProcessHeap() }; | ||||||||
|
||||||||
| #[stable(feature = "alloc_system_type", since = "1.28.0")] | |
| #[derive(Debug, Default, Copy, Clone)] | |
| pub struct System; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to cache it somewhere.
CDirkx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
CDirkx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relevant to my comment on HeapFree above -- does this need to check hHeap for null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dealloc gets to make the same assumption as realloc, since ptr has been successfully allocated with this allocator, the default process heap must be available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation of GlobalAlloc has the point:
- It's undefined behavior if global allocators unwind. This restriction may be lifted in the future, but currently a panic from any of these functions may lead to memory unsafety.
So I removed the debug_assert
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not documented but I could believe this gets to assume GetProcessHeap returns nonnull without a check, because ptr is guaranteed to have been previously allocated from the default process heap, so the process has a default heap. Do you believe we need to add a null check? Is it possible for a process to go from having a heap to not having a heap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried looking into the conditions under which GetProcessHeap would fail, but couldn't find any extra information. It does sound like a reasonable assumption, and even if it is wrong it is legal for implemenations of GlobalAlloc to abort (not unwind), which is what HeapRealloc does in practice when given an invalid hHeap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the return value need a null check. As we don't specify HEAP_GENERATE_EXCEPTIONS flag, the functions will fail when specified heap is null, and return a null pointer, which is just what we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HEAP_GENERATE_EXCEPTIONS may be enabled for the default process heap, at least on my machine the following code produces an exception, even with flags set to 0:
unsafe {
let ptr = HeapAlloc(GetProcessHeap(), 0, 64);
println!("{:?}", HeapReAlloc(std::ptr::null_mut(), 0, ptr, 128)) // exit code: 0xc0000005, STATUS_ACCESS_VIOLATION
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the following to the GetProcessHeap documentation:
// SAFETY: Successful calls to this function within the same process are assumed to
// always return the same handle, which remains valid for the entire lifetime of the process.Which means we can cache the value, and assume we have a valid handle in dealloc and realloc.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| use super::{Header, MIN_ALIGN}; | ||
| use crate::mem; | ||
|
|
||
| #[test] | ||
| fn alloc_header() { | ||
| // Header must fit in the padding before an aligned pointer | ||
| assert!(mem::size_of::<Header>() <= MIN_ALIGN); | ||
| assert!(mem::align_of::<Header>() <= MIN_ALIGN); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding "hHeap is allowed to be any value" -- I didn't get that from the link. What's the specific text that indicates this? Under the "Parameters" section
hHeapis documented exactly the same as it is for HeapAlloc, where you've put a more restrictive safety condition: "hHeap must be a non-null handle returned by GetProcessHeap."There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the testing I did
HeapAllocandHeapReallocthrow an exception if given a wronghHeapvalue, butHeapFreejust returns true indicating success. But you're right that the documentation is the same so this maybe shouldn't be relied on.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the safety condition.