Undefined Behavior: in-bounds pointer arithmetic failed: attempting to offset pointer by 20 bytes, but got alloc238 which is only 1 byte from the end of the allocation

⚓ Rust    📅 2026-04-23    👤 surdeus    👁️ 1      

surdeus

Hello, I am confused by this error from Miri :

Compiling playground v0.0.1 (/playground)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.45s
     Running `/playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo-miri runner target/miri/x86_64-unknown-linux-gnu/debug/playground`
error: Undefined Behavior: in-bounds pointer arithmetic failed: attempting to offset pointer by 20 bytes, but got alloc238 which is only 1 byte from the end of the allocation
   --> src/main.rs:124:24
    |
124 |             let next = ptr.add(size);
    |                        ^^^^^^^^^^^^^ Undefined Behavior occurred here
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
help: alloc238 was allocated here:
   --> src/main.rs:22:25
    |
 22 |             let start = std::alloc::alloc(layout);
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: stack backtrace:
            0: <&HeapArena as std::alloc::Allocator>::allocate
                at src/main.rs:124:24: 124:37
            1: alloc::raw_vec::RawVecInner::<&HeapArena>::try_allocate_in
                at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/raw_vec/mod.rs:465:41: 465:63
            2: alloc::raw_vec::RawVecInner::<&HeapArena>::with_capacity_in
                at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/raw_vec/mod.rs:434:15: 434:92
            3: alloc::raw_vec::RawVec::<i32, &HeapArena>::with_capacity_in
                at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/raw_vec/mod.rs:177:20: 177:77
            4: std::vec::Vec::<i32, &HeapArena>::with_capacity_in
                at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:977:20: 977:61
            5: main
                at src/main.rs:143:23: 143:55

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

Standard Output

My code is like this :

#![feature(allocator_api)]

pub struct Block {
    ptr: *mut u8,
    cursor: *mut u8,
    end: *mut u8,
    layout: std::alloc::Layout,
    next: Option<Box<Block>>
}

pub struct HeapArena {
    head: std::cell::UnsafeCell<Block>
}

impl HeapArena {
    #[inline(always)]
    pub fn new(size: usize) -> Self {
        
        let layout = unsafe { std::alloc::Layout::from_size_align_unchecked(size, std::mem::align_of::<usize>()) };

        let start = unsafe { std::alloc::alloc(layout) };
            if start.is_null() {
                std::alloc::handle_alloc_error(layout);
            }

            Self {
                head: std::cell::UnsafeCell::new(Block {
                    ptr: start,
                    cursor: start,
                    end: unsafe { start.add(size) },
                    layout: layout,
                    next: None
                })
            }
        
    }
    
    unsafe fn grows(&self, min_size: usize) {
        println!("grow");
        let head = unsafe { &mut *self.head.get() };
        let new_size = min_size.max(head.layout.size() * 2);
        let layout = std::alloc::Layout::from_size_align(new_size, 8).unwrap();
        let ptr = unsafe { std::alloc::alloc(layout) };
        if ptr.is_null() { std::alloc::handle_alloc_error(layout); }

        let old_block = unsafe { std::ptr::replace(head, Block {
            ptr,
            layout,
            cursor: ptr,
            end: ptr.add(new_size),
            next: None,
        }) };
        
        head.next = Some(Box::new(old_block));
    }

    #[inline(always)]
    pub fn reset(&mut self) {
        let mut current = unsafe { &mut *self.head.get() };
        loop {
            current.cursor = current.ptr;
            if let Some(ref mut next_block) = current.next {
                current = next_block.as_mut();
            } else {
                break;
            }
        }
    }
}

impl Drop for HeapArena {
    #[inline(always)]
    fn drop(&mut self) {
        let mut current = unsafe { std::ptr::replace(
            self.head.get(),
            Block {
                ptr: std::ptr::null_mut(),
                cursor: std::ptr::null_mut(),
                end: std::ptr::null_mut(),
                layout: std::alloc::Layout::from_size_align(1, 1).unwrap(),
                next: None,
            },
        ) };
        loop {
            if !current.ptr.is_null() {
                unsafe { std::alloc::dealloc(current.ptr, current.layout) };
            }

            if let Some(next_block) = current.next {
                current = *next_block;
            } else {
                break;
            }
        }
        
    }
}

unsafe impl std::alloc::Allocator for &HeapArena {
    #[inline(always)]
    fn allocate(
        &self,
        layout: std::alloc::Layout,
    ) -> Result<std::ptr::NonNull<[u8]>, std::alloc::AllocError> {
        let head = unsafe { &mut *self.head.get() };
        let size = layout.size();
        let align = layout.align();

        let handle_addr = head.cursor as usize;
        let aligned_addr = (handle_addr + (align - 1)) & !(align - 1);
        /* let next = aligned_addr + size;
            if next > head.end as usize {
                self.grows(size);
                return self.allocate(layout);
            }
        */
        // let ptr = aligned_addr as *mut u8;
        let ptr = head.cursor.map_addr(|_| aligned_addr);
        let next = unsafe { ptr.add(size) };
        if next > head.end {
            unsafe { self.grows(size) };
            return self.allocate(layout);
        }
        head.cursor = next as *mut u8;
            
        return Ok(unsafe {
            std::ptr::NonNull::new_unchecked(
                std::ptr::slice_from_raw_parts_mut(ptr, size),
            )
        });
        
    }

    #[inline(always)]
    unsafe fn deallocate(&self, _ptr: std::ptr::NonNull<u8>, _layout: std::alloc::Layout) {}
}

fn main() {
    let mut arena = HeapArena::new(1);
    {let mut vector = Vec::with_capacity_in(5, &arena);
    for _ in 0..100 {
        vector.push(10);
        vector.push(20);
    }
    println!("{}", vector[0]);
    }
    arena.reset();
    //println!("{}", vector[0]);
}

What is and which line is the cause of why the ptr.add() failed? Why it is failed?

And what is the solution?

Thank you for the helps

8 posts - 3 participants

Read full topic

🏷️ Rust_feed