Aliasing of raw pointers

I assumed that raw pointers never have any aliasing requirements. The references do, and creating a pointer from a reference can propagate certain aliasing requirements, but if I create raw pointers directly, then no aliasing is assumed. Also, I have always believed that the ptr::addr_of!/addr_of_mut! and the recently stabilized &raw const/&raw mut operators were specifically designed to allow creating pointers to places without a detour through references. While Stacked and Tree borrows may differ in their treatment of references and ref-to-ptr conversions, I have assumed that using addr_of_mut! should be unconditionally valid.

However, this example produces an error in Miri:

error: Undefined Behavior: attempting a read access using <2056> at alloc1107[0x0], but that tag does not exist in the borrow stack for this location
  --> src/main.rs:4:17
   |
4  |         let r = *v1; // #2
   |                 ^^^
   |                 |
   |                 attempting a read access using <2056> at alloc1107[0x0], but that tag does not exist in the borrow stack for this location
   |                 this error occurs as part of an access at alloc1107[0x0..0x4]
   |
   = 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: <2056> was created by a SharedReadOnly retag at offsets [0x0..0x4]
  --> src/main.rs:13:26
   |
13 |     let v2: *const i32 = &raw const i;
   |                          ^^^^^^^^^^^^
help: <2056> was later invalidated at offsets [0x0..0x4] by a write access
  --> src/main.rs:3:9
   |
3  |         *v = 1; // #1
   |         ^^^^^^
   = note: BACKTRACE (of the first span):
   = note: inside `foo` at src/main.rs:4:17: 4:20

I'd like a clarification on the aliasing requirements for raw pointers. Is this a bug in Miri? If not, what is the correct way to create aliasing raw pointers?

2 Likes

I thought you had to use read/write rather than pointer dereference in this case, but that doesn't fix it either. Huh.

Raw pointers are allowed to alias arbitrarily with each other. But in your example, there's not just raw pointers -- there's also direct use of the local variable i, which Stacked Borrows treats like a unique (mutable) reference. Every time you write &raw [mut] i, you create a new "raw pointer bubble" that allows arbitrary aliasing within the bubble, but not across multiple bubbles.

Tree Borrows relaxes the rules here, and would accept your program.

That sounds strange. After all, a variable isn't a reference. What are the exact rules here? E.g. does it mean that creating multiple pointers (via independent &raw mut invocations) to a field of a struct on the stack is invalid? What if the struct is on the heap? What if I just have a pointer, and I don't know whether it points to a local variable on the stack or a heap allocation? What if I create a pointer to a variable live over .await inside of an async function?

It doesn't seem to be quite that strict, since if you change the &raw const into &raw mut, the program is accepted by Miri.

2 Likes

To a first order of approximation, I believe (but am not 100% confident) that with Stacked Borrows, you can/should treat &raw const local as being functionally equivalent to &raw const *&local, i.e. creating a shared reference and thus invalidating any writable provenance to that local. To any other place that isn't local (i.e. one which is based on a pointer), provenance is unchanged.

The specifics are a bit more permissive than that, but ultimately the point of this approximation is that Stacked Borrows considers &raw const local (for a local place) to be a pointer to frozen (as for a shared reference) place, and invalidates its provenance if mutation happens.

That this clashes with otherwise seemingly reasonable intuition and code patterns, along with the behavioral difference between local and pointer-derived places, is a large contributing factor to why the Tree Borrows model treats creating a pointer to a place differently than Stacked Borrows.

1 Like

No, but it's also a place expression, and in Stacked Borrows we treat it a lot like Box and &mut in that we expect that place to be unique.

The exact rules of Stacked Borrows are spelled out here.

The key point is that when you create a raw pointer from a reference or local variable, that creates a new "raw pointer bubble" which has aliasing constraints with all other "bubbles". When you create a raw pointer from an existing raw pointer, it becomes part of the same "bubble" and hence is allowed to arbitrarily alias with everything in that "bubble".

Also see this related issue.

4 Likes