Skip to content

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

Closed
@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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions