Update Allocator Designs post to signal OOM instead of panicking on overflow (#739)

This commit is contained in:
Philipp Oppermann
2020-02-04 09:47:43 +01:00
committed by GitHub
parent 83239394dd
commit 4707ea617f

View File

@@ -262,7 +262,10 @@ unsafe impl GlobalAlloc for Locked<BumpAllocator> {
let mut bump = self.lock(); // get a mutable reference let mut bump = self.lock(); // get a mutable reference
let alloc_start = align_up(bump.next, layout.align()); let alloc_start = align_up(bump.next, layout.align());
let alloc_end = alloc_start.checked_add(layout.size()).expect("overflow"); let alloc_end = match alloc_start.checked_add(layout.size()) {
Some(end) => end,
None => return ptr::null_mut(),
};
if alloc_end > bump.heap_end { if alloc_end > bump.heap_end {
ptr::null_mut() // out of memory ptr::null_mut() // out of memory
@@ -288,7 +291,7 @@ The first step for both `alloc` and `dealloc` is to call the [`Mutex::lock`] met
[`Mutex::lock`]: https://docs.rs/spin/0.5.0/spin/struct.Mutex.html#method.lock [`Mutex::lock`]: https://docs.rs/spin/0.5.0/spin/struct.Mutex.html#method.lock
Compared to the previous prototype, the `alloc` implementation now respects alignment requirements and performs a bounds check to ensure that the allocations stay inside the heap memory region. The first step is to round up the `next` address to the alignment specified by the `Layout` argument. The code for the `align_up` function is shown in a moment. We then add the requested allocation size to `alloc_start` to get the end address of the allocation. To prevent integer overflow on large allocations, we use the [`checked_add`] method. If the resulting end address of the allocation is larger than the end address of the heap, we return a null pointer to signal an out-of-memory situation. Otherwise, we update the `next` address and increase the `allocations` counter by 1 like before. Finally, we return the `alloc_start` address converted to a `*mut u8` pointer. Compared to the previous prototype, the `alloc` implementation now respects alignment requirements and performs a bounds check to ensure that the allocations stay inside the heap memory region. The first step is to round up the `next` address to the alignment specified by the `Layout` argument. The code for the `align_up` function is shown in a moment. We then add the requested allocation size to `alloc_start` to get the end address of the allocation. To prevent integer overflow on large allocations, we use the [`checked_add`] method. If an overflow occurs or if the resulting end address of the allocation is larger than the end address of the heap, we return a null pointer to signal an out-of-memory situation. Otherwise, we update the `next` address and increase the `allocations` counter by 1 like before. Finally, we return the `alloc_start` address converted to a `*mut u8` pointer.
[`checked_add`]: https://doc.rust-lang.org/std/primitive.usize.html#method.checked_add [`checked_add`]: https://doc.rust-lang.org/std/primitive.usize.html#method.checked_add
[`Layout`]: https://doc.rust-lang.org/alloc/alloc/struct.Layout.html [`Layout`]: https://doc.rust-lang.org/alloc/alloc/struct.Layout.html
@@ -664,7 +667,7 @@ impl LinkedListAllocator {
-> Result<usize, ()> -> Result<usize, ()>
{ {
let alloc_start = align_up(region.start_addr(), align); let alloc_start = align_up(region.start_addr(), align);
let alloc_end = alloc_start.checked_add(size).expect("overflow"); let alloc_end = alloc_start.checked_add(size).ok_or(())?;
if alloc_end > region.end_addr() { if alloc_end > region.end_addr() {
// region too small // region too small
@@ -684,7 +687,7 @@ impl LinkedListAllocator {
} }
``` ```
First, the function calculates the start and end address of a potential allocation, using the `align_up` function we defined earlier and the [`checked_add`] method. If the end address is behind the end address of the region, the allocation doesn't fit in the region and we return an error. First, the function calculates the start and end address of a potential allocation, using the `align_up` function we defined earlier and the [`checked_add`] method. If an overflow occurs or if the end address is behind the end address of the region, the allocation doesn't fit in the region and we return an error.
The function performs a less obvious check after that. This check is necessary because most of the time an allocation does not fit a suitable region perfectly, so that a part of the region remains usable after the allocation. This part of the region must store its own `ListNode` after the allocation, so it must be large enough to do so. The check verifies exactly that: either the allocation fits perfectly (`excess_size == 0`) or the excess size is large enough to store a `ListNode`. The function performs a less obvious check after that. This check is necessary because most of the time an allocation does not fit a suitable region perfectly, so that a part of the region remains usable after the allocation. This part of the region must store its own `ListNode` after the allocation, so it must be large enough to do so. The check verifies exactly that: either the allocation fits perfectly (`excess_size == 0`) or the excess size is large enough to store a `ListNode`.