Question about provenance and raw pointers

consider I have a tuple tup = (u8, u8) then in two seperate code paths i do:

  1. &mut tup.1
  2. &raw mut tup.1

what provence do they have under stacked borrows? what about tree borrows?

my initial guess would be that the ref narrows the provenance to only that field, while the raw pointer maintains provenance over the entire tuple. is this correct?

I'm not sure, but my guess would be that both only have provenance over the second field. If it were instead &raw *(&raw tup).1, I believe that would retain provenance over the whole tuple, since it was directly derived from a pointer that did, without an intermediate reference. Whereas in your example, there is never any pointer to the whole tup.

1 Like

tup however is a place expression, as is *(&raw mut tup).

(btw it's either &raw mut or &raw const, raw is a soft keyword and can't appear on its own)

This mirrors my understanding; and there’s no need to be fine with mere beliefs, anyway (also @binarycat), since we do have miri, fortunately, so a simple test…

fn main() {
    unsafe {
        let mut tup = (0u8, 0u8);
        let p1 = &raw mut (*&raw mut tup).1;
        let _read = *p1;
        *p1 = 1; // and write
        let p2 = &raw mut tup.1;
        let _read = *p2;
        /* write */
        *p2 = 2; // and write
        fn project_to_outer(x: *mut u8) -> *mut (u8, u8) {
            unsafe {
                use std::mem::offset_of;
                x.byte_sub(offset_of!((u8, u8), 1)).cast()
            }
        }
        let p1 = &raw mut (*project_to_outer(p1)).0;
        let p2 = &raw mut (*project_to_outer(p2)).0;
        let _read = *p1;
        *p1 = 3; // and write
        // everything passes miri up to this point
        let _read = *p2; // this FAILS miri, UB under stacked borrows
        /* write */
        *p2 = 4; // would also fail, unsurprisingly
    }
}

(playground)

   Compiling playground v0.0.1 (/playground)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.05s
     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: attempting a read access using <1147> at alloc594[0x0], but that tag does not exist in the borrow stack for this location
  --> src/main.rs:22:21
   |
22 |         let _read = *p2; // this FAILS miri, UB under stacked borrows
   |                     ^^^
   |                     |
   |                     attempting a read access using <1147> at alloc594[0x0], but that tag does not exist in the borrow stack for this location
   |                     this error occurs as part of an access at alloc594[0x0..0x1]
   |
   = 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: <1147> was created by a SharedReadWrite retag at offsets [0x1..0x2]
  --> src/main.rs:7:18
   |
7  |         let p2 = &raw mut tup.1;
   |                  ^^^^^^^^^^^^^^
   = note: BACKTRACE (of the first span):
   = note: inside `main` at src/main.rs:22:21: 22:24

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

error: aborting due to 1 previous error

…can confirm everything.


Yes, the semantics of &raw (const|mut) is inconsistent between different kinds of place expressions. By the way, for a pointer-derived place, even &raw const … will preserve the full provenance (including mutability of the original pointer).


EDIT: Looking at the documentation, which… (unfortunately, I guess!?) still is only really to be found under their longer-stable names of std::mem::addr_of/std::mem::addr_of_mut, it actually describes that my last point might still be undecided, so miri is conservative in disallowing writes through &const variable or &const variable.field or such.

It is still an open question under which conditions writing through an addr_of! -created pointer is permitted. If the place expr evaluates to is based on a raw pointer, then the result of addr_of! inherits all permissions from that raw pointer. However, if the place is based on a reference, local variable, or static , then until all details are decided, the same rules as for shared references apply: it is UB to write through a pointer created with this operation, except for bytes located inside an UnsafeCell . Use &raw mut (or addr_of_mut) to create a raw pointer that definitely permits mutation.


Second Edit Addendum: By the way, this doesn't mean that &raw mut variable is the same as &mut variable as *mut _. While it has provenance over (only) the place it's created for, and the whole place it's created for, the basic rule that &raw mut variable does nothing unless you dereference the pointer (OR, WHEN THE EVALUATION OF THE PLACE EXPRESSION ITSELF HAS SIDE-EFFECTS, IN PARTICULAR WHEN THAT INVOLVES INTERMEDIATE REFERENCES BEING CREATED !!ESPECIALLY WITH IMPLICIT DEREF BEING INVOLVED!!), and these models such as stacked borrows operate byte-wise.

So in consequence, you could e.g. take a &raw mut tup of the whole tuple, but have that alias with a &mut tup.1, and it’s all good, as long as you only access tup.0 through the raw pointer.

1 Like

std never removes apis, so its still stable, in fact its not even deprecated!

sounds like we need to add raw to the keyword docs (I believe rustdoc will just believe you if you tell it something is a keyword, so this should be fairly easy)

Yes, that’s what I said. addr_of and addr_of_mut are the names that have been stable for longer than the &raw … syntax; and what I find unfortunate is that it can be found only under that syntax.

raw is indeed a (weak) keyword (and documented to be one); it looks like of the 5

  • 'static
  • macro_rules
  • raw
  • safe
  • union

that exist, only one is actually anywhere to be found in std docs, AFAICT (that’s union). I think this should change; next steps would be to look for existing issues on GitHub that might be tracking this already and perhaps open one if there isn’t.

1 Like

existing issue: Document the contextual keyword `raw` · Issue #134447 · rust-lang/rust · GitHub