Skip to content

Tree-borrows false positive with iter::Extend? #3764

@ohsayan

Description

@ohsayan

Violating code

While this code might look unnecessarily funky, it is so because I stripped it down to the (hopefully) bare minimum for a repro. Here's the code:

use std::{mem::ManuallyDrop, slice};

#[test]
fn extend_fail() {
    let data = Data::allocate(b"hello");
    assert_eq!(data.read(), b"hello");
    let mut buffer = Vec::<u8>::with_capacity(1024);
    buffer.extend(data.read());
    assert_eq!(buffer.len(), 5);
}

#[test]
fn extend_pass() {
    let data = Data::allocate(b"hello");
    assert_eq!(data.read(), b"hello");
    let mut buffer = Vec::<u8>::with_capacity(1024);
    for byte in data.read() {
        buffer.push(*byte);
    }
    assert_eq!(buffer.len(), 5);
}

pub struct Data {
    data: DataUnion,
}

impl Data {
    pub fn allocate(data: &[u8]) -> Self {
        let mut data = ManuallyDrop::new(data.to_owned().into_boxed_slice());
        Self {
            data: DataUnion {
                x: ManuallyDrop::new(FatPointer::new(
                    data.as_mut_ptr() as usize,
                    data.len() as u64,
                )),
            },
        }
    }
    pub fn read(&self) -> &[u8] {
        unsafe {
            // SAFETY: fine since we r/w it directly!
            slice::from_raw_parts(self.data.x.ptr as *mut u8, self.data.x.len as usize)
        }
    }
}

impl Drop for Data {
    fn drop(&mut self) {
        unsafe {
            Vec::from_raw_parts(
                self.data.x.ptr as *mut u8,
                self.data.x.len as usize,
                self.data.x.len as usize,
            );
        }
    }
}

struct FatPointer {
    len: u64,
    ptr: usize,
}

impl FatPointer {
    fn new(ptr: usize, len: u64) -> Self {
        Self { len, ptr }
    }
}

union DataUnion {
    x: ManuallyDrop<FatPointer>,
}

What seemed interesting to me is that neither stacked borrows nor tree borrows reports an error with the extend_pass test when run with either of:

MIRIFLAGS="-Zmiri-permissive-provenance -Zmiri-tree-borrows" cargo miri test extend_pass
MIRIFLAGS="-Zmiri-permissive-provenance" cargo miri test extend_pass

But when you try it for extend_fail with tree-borrows, it fails.

Violation

   --> /home/sayan/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/iter.rs:136:1
    |
136 | / iterator! {struct Iter -> *const T, &'a...
137 | |     fn is_sorted_by<F>(self, mut compar...
138 | |     where
139 | |         Self: Sized,
...   |
143 | |     }
144 | | }}
    | |__^ `ptr_offset_from_unsigned` called on pointers into different allocations

Meta

Tried with:

  • miri 0.1.0 (8bfcae7 2024-07-23)
  • miri 0.1.0 (7120fda 2024-07-25)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions