Laundering pointers

First, the setup for this experiment, assume

type Foo; // Not really super important, just some data.
struct Forum(*mut [Foo]);
impl Forum {
    pub fn get_mut(&mut self) -> &mut [Foo];
}

The code I want to dissect now looks like this:

let mut place: Forum = todo!(); /* constructed externally */
let p = derive(place.get_mut()); // 1
modify(place.get_mut());         // 2
reuse(p);                        // 3

First, we derive a location of interest in the collection of Foos. For our dummy example, this function looks like

fn derive(p: &mut [Foo]) -> *mut Foo {
    std::ptr::from_mut(&mut p[1337]) // index 1337 is interesting indeed
}

but in reality, the index is not necessarily as simple as a single number. The main point is that I don't want to remember this index in the return value. I compute it based on user input and some other stuff, then traverse the Forum with it (which also is more tree-like than the simple array here). I finally land on a single "interesting" item that I know will still be there later.

Second, we "modify" the forum. The details of this method are not important, it merely needs to get passed a &mut [Foo] and make use of it somehow (without structurally changing it)

fn modify(p: &mut [Foo]);

Third and finally, reuse contains the important point of this post:

fn reuse(p: *mut Foo) {
    //let p = launder(p); //????
    let r = unsafe { &mut *p };
    println!("{:?}", *r);
}

I want to use the pointer to the interesting index. But miri informs me that this is forbidden by stacked-borrow rules (Playground)

that tag does not exist in the borrow stack for this location

I agree with this. The initial borrow passed to derive gets replaced by the borrow passed to modify which seems to make basically any pointer-based write access to an item invalid. The question is now to fix this?

  • Approach 1: Use RefCell to access the items? I would have to rewrite everything in derive and modify to work with an immutable ref to the Forum, take a &RefCell<Foo> into reuse and borrow it there. While most likely the safest of the option, this has considerable overhead in both restructuring and the refcells. I would like to avoid this.

  • Approach 2: Chance derive to work on a "more powerful" view of the data. Instead of getting a &mut [Foo] which reborrows from the pointer in Forum, one could use the *mut [Foo] directly to derive the "interesting" place. I would think that this thusly derived pointer would not get popped off the borrow stack with the second borrow in modify, "surviving" the modifications and could then be used in reuse.

    But it gets hairy very quickly with more complicated datastructures in Forum which all would need to take care never to derive the pointer through a (temporary, and this later invalidated) borrow but always through the use of raw pointers. Quite a bunch of data types in std would not allow this kind of access. For example there is no stable impl<T> Vec<T> { fn get_mut_raw(*mut self) -> *mut [T]; }

  • Approach 3: Pointer laundering. C++ has the delightful method std::launder, which returns takes a pointer and returns it unharmed. The user can e.g. say "there used to be an object here, there is now an object here, but it might be a different one. Please let me access it through this pointer".

    For rust, the equivalent I imagine would say "I have this pointer, it still points into the same allocation as before. I used to be able to access it. Please rederive a borrow stack that let's me access it as before". From a user without formal verification in mind, the pointer can just really be used as is. It's not 100% clear how the borrow stack would look after this operation (except that there is a tag that validates access through the returned pointer on top). One might have to explicitly enable a pointer to do this operation - where the borrow stack as it exists during derive is "captured" so that it can be compared to the current one when laundering.

  • Approach 4: ??? Ideas and comments are appreciated, maybe I'm just missing something straight-forward.

1 Like

I think, this kind of thread would be a better fit for users.rust-lang.org rather than the internals forum.

I posted in here because Approach 3 mentions an operation that does currently not exist, but I think should. (Can I move it? Feel free to)

For pointer laundering see also:

1 Like