Why is Miri claiming Unique retagging with raw pointer?

I'm running Mini on tests for the first time and after reading multiple other topics here on the forum and various GitHub issues still don't really understand what is the issue here:

test basic ... error: Undefined Behavior: attempting a read access using <316299> at alloc169530[0x10], but that tag does not exist in the borrow stack for this location
   --> abundance/crates/contracts/ab-contracts-executor/src/context/ffi_call.rs:128:45
    |
128 |                     let data_ptr = unsafe { data_ptr.as_ptr().read().cast_const() };
    |                                             ^^^^^^^^^^^^^^^^^^^^^^^^
    |                                             |
    |                                             attempting a read access using <316299> at alloc169530[0x10], but that tag does not exist in the borrow stack for this location
    |                                             this error occurs as part of an access at alloc169530[0x10..0x18]
    |
    = 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: <316299> was created by a SharedReadWrite retag at offsets [0x0..0x80]
   --> abundance/crates/contracts/ab-contracts-executor/src/context/ffi_call.rs:227:54
    |
227 |             NonNull::<MaybeUninit<*mut c_void>>::new(internal_args.as_mut_ptr())
    |                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^
help: <316299> was later invalidated at offsets [0x0..0x80] by a Unique retag
   --> abundance/crates/contracts/ab-contracts-executor/src/context/ffi_call.rs:669:54
    |
669 |             NonNull::<MaybeUninit<*mut c_void>>::new(self.internal_args.as_mut_ptr())
    |                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Essentially I allocate some memory and create an AliasableBox (from crates.io: Rust Package Registry), then write some pointers to data there. Afterwards I call a function with a pointer to that memory so the function can read/write data and later read potentially modified data later.

Here is the full file: abundance/crates/contracts/ab-contracts-executor/src/context/ffi_call.rs at 1e4381b67d140510b7e7e043d47e0e5acb5b45d9 · nazar-pc/abundance · GitHub

Since I'm switching to AliasableBox and work with raw pointers, why does highlighted line 669 above results in "Unique retag"? I thought it would remain aliasable since I just copy and cast the pointer.

You’re calling <[MaybeUninit<*mut c_void>]>::as_mut_ptr.

impl<T> [T] {
pub const fn as_mut_ptr(&mut self) -> *mut T;
}

Doing this method call starts by creating a &mut [MaybeUninit<*mut c_void>] reference by DerefMut on the AliasableBox. Mutable references have unaliased access, so all existing pointers to the same slice are invalidated.

1 Like

I didn't check the full code, but the first error:

this error basically means the pointer is out of the range of its provenance. it's the same error like this example (playground):


EDIT:

never mind. the error can also means the ptr is invalidated.

IMO, your example is significantly different; and the error message is different, too, as far as the help messages are concerned:

your out-of-range example says

  • attempting a read access using <1462> at alloc683[0x4]
    and
  • <1462> was created by a SharedReadOnly retag at offsets [0x0..0x4]

so it’s clearly out of range (0x4) isn’t in 0x0..0x4 (these ranges have exclusive right bounds).


Whereas the OP’s code says

  • attempting a read access using <316299> at alloc169530[0x10]
    and
  • <316299> was created by a SharedReadWrite retag at offsets [0x0..0x80]

which is completely in-bounds, and read access isn’t an issue either, but the issue is the next part of

  • <316299> was later invalidated at offsets [0x0..0x80] by a Unique retag

Maybe you didn’t read beyond up to the help message – you also speak of “the first error” when only a single error was shared (and miri stops executing after a single error, anyway). :wink:

If you want to still use AliasableBox (which is reasonable if you want to be able to move the box around while holding references into it) you could consider e.g. combining it with UnsafeCell.

I also feel like the use of Pin in your code is almost definitely[1] completely redundant.


  1. I haven’t read or tried to understand the code, but there’s multiple indicators I can see that Pin isn’t used for its intended purpose ↩︎

thanks for pointing out, my bad, I just saw the "error", didn't pay attention to the "help".

Hm... that method is actually provided by [T] impl, not by AliasableBox itself. So it dereferences internal pointer to create a slice and then takes a pointer to a slice.

Though I'm not exactly sure how AliasableBox is useful then if it has no methods on its own :thinking: And I'm even more confused by AliasableVec in the same crate for the same reason.

That is my goal and I used Pin to indicate that it shouldn't be reallocated after Box creation, though I guess it isn't particularly helpful in that context.

I’ve only seen it used in self-referencing structs so far. Which yours arguably is AFAICT, so that’s still usefull. The use case that AliasableBox supports and Box doesn’t is something like

let mut box1 = Box::new(42);
let ptr: *const i32 = &mut *box1; // or *const and &*box, same story;
// now move the box
let box2 = box1;
// now use the pointer
unsafe {
    println!("{}", *ptr);
}

(playground)

moving around a whole Box by value – in particular also passing it to some other function - can assert unique access to its contents (through that box) and invalidate ptr.

To create multiple references to the target however, it looks like one would generally have to involve shared mutable access via UnsafeCell (or similar types that are wrapping it), at least given the current API of AliasableBox where you really only can do Deref/DerefMut besides the by-value conversions from/to ordinary Box. Maybe that isn’t a terrible idea anyway, at least with UnsafeCell added, the target type becomes invariant.

Actually yes, it is self-referential. Thinking more about this, I can actually work around at least some of issues by storing pointer offset instead of pointers itself, though I'd rather not and I do have other types that may not be able to get away with just offsets.

UnsafeCell is something I have not used yet anywhere.

Right, it shouldn’t cost much effort just to try it out. You can replace your thing with

internal_args: AliasableBox<UnsafeCell<[MaybeUninit<*mut c_void>]>>

and then internal_args.get() always gives you a *mut [MaybeUninit<*mut c_void>] raw pointer through which you can have mutable access, without invalidating any other copies of the pointer created from such get() calls (as long as you don’t have unsynchronized access to the same place, or create other references from it that overlap&alias, etc…)

1 Like

Hm... combination of AliasableBox and UnsafeCell for inner values worked indeed.

I don't think I fully understand why though. Is that because once things reach to UnsafeCell stacked borrows rules no longer apply with what comes out of it?

If a &Type reference points to some Type with some UnsafeCell in fields, then all data/bytes that lives within an UnsafeCell actually offers shared mutable access.

It isn’t that UnsafeCell “disables” stacked-borrows rules, but the exact interaction with / behavior of UnsafeCell is a core part of stacked borrow rules.

3 Likes

I really appreciate all the responses, I learned a lot today and make Miri happy in the process, thank you!