Question about provenance

I wonder why in rust a *mut T can refer to a slice of T. Wouldn't that have to be a *mut [T]?

I ask because the following works

#[repr(C)]
pub struct Array<T> {
    len: usize,
    ptr: *mut T,
}

impl<T: Copy> Array<T> {
    fn new(val: T, len: usize) -> Self {
        let data = vec![val; len].into_boxed_slice();

        Self {
            len: len as usize,
            ptr: Box::into_raw(data).cast(),
        }
    }
}

Why can I do Box::into_raw(data).cast() casting from a *mut [T] to a *mut T without losing provenance over the elements following the first one? If I do something similar with a reference I get UB.

fn main() {
    let mut a = [0; 4];
    let p0 = &mut a[0];
    unsafe{
        let ptr0 = p0 as *mut i32;
        let ptr2 = ptr0.add(2);
        *ptr2 = 12;
    }

    dbg!(a);
}
error: Undefined Behavior: attempting a write access using <1616> at alloc918[0x8], but that tag does not exist in the borrow stack for this location
 --> src/main.rs:7:9
  |
7 |         *ptr2 = 12;
  |         ^^^^^^^^^^
  |         |
  |         attempting a write access using <1616> at alloc918[0x8], but that tag does not exist in the borrow stack for this location
  |         this error occurs as part of an access at alloc918[0x8..0xc]
  |
  = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
  = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <1616> was created by a SharedReadWrite retag at offsets [0x0..0x4]
 --> src/main.rs:5:20
  |
5 |         let ptr0 = p0 as *mut i32;
  |                    ^^
  = note: BACKTRACE (of the first span):
  = note: inside `main` at src/main.rs:7:9: 7:19

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

error: aborting due to previous error

The type doesn't affect provenance. The key is whether you already had a pointer to the entire allocation or only to the 1st element in the first place.

Box::into_raw() gives you a pointer to the contents of the box, which is the entire slice if you had a box-of-slice. In contrast, &mut a[0] only ever points to the first element of the slice, and not to the entire slice. You probably meant a.as_mut_ptr() instead.

The pointee types are a red herring; you can cast raw pointers through an arbitrary chain of types without affecting provenance, and you'll be safe as long as the type is correct when you actually dereference the pointer (or turn it into a reference).

5 Likes

The difference between your test cases is that under Stacked Borrows the &mut a[0]; creates a reference which only has permission to access that single element. It doesn't have anything to do with the type, it's just that you performed an operation which intentionally "throws away" permission to the rest of the array by taking a reference to the first element.

6 Likes

Ok, so the rules for references and raw pointers are just different then.

No. Again, the point is exactly that the difference is not whether you have a raw pointer or a reference. If Box<[T]> had a method for obtaining a raw pointer to the first element, then using that for accessing other elements would still be UB.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.