From 4707ea617f8be82c36717da69a1aba1e27290b42 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 4 Feb 2020 09:47:43 +0100 Subject: [PATCH] Update Allocator Designs post to signal OOM instead of panicking on overflow (#739) --- .../posts/11-allocator-designs/index.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/blog/content/second-edition/posts/11-allocator-designs/index.md b/blog/content/second-edition/posts/11-allocator-designs/index.md index 32fc4664..3c7ab66d 100644 --- a/blog/content/second-edition/posts/11-allocator-designs/index.md +++ b/blog/content/second-edition/posts/11-allocator-designs/index.md @@ -262,7 +262,10 @@ unsafe impl GlobalAlloc for Locked { let mut bump = self.lock(); // get a mutable reference 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 { 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 -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 [`Layout`]: https://doc.rust-lang.org/alloc/alloc/struct.Layout.html @@ -664,7 +667,7 @@ impl LinkedListAllocator { -> Result { 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() { // 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`.