owning-ref-rs icon indicating copy to clipboard operation
owning-ref-rs copied to clipboard

`OwningRef::map` is unsound

Open steffahn opened this issue 4 years ago • 1 comments
trafficstars

fn main() {
    let x = OwningRef::new(Box::new(()));
    let z: OwningRef<Box<()>, str>;
    {
        let s = "Hello World!".to_string();
        let s_ref: &str = &s;
        let y: OwningRef<Box<()>, &str> = x.map(|_| &s_ref);
        z = y.map(|s: &&str| *s);
        // s deallocated here
    }
    println!("{}", &*z); // printing garbage, accessing `s` after it’s freed
}

Explanation

Since

pub fn map<F, U: ?Sized>(self, f: F) -> OwningRef<O, U>
where
    O: StableAddress,
    F: FnOnce(&T) -> &U, 

supports non-'static types for U and F, we can use U == &'short T to turn

OwningRef<Box<()>, ()>

into

OwningRef<Box<()>, &'short T>

In the example code above, T == str and the call to map is .map(|_| &s_ref).

Then, on the next call to map, you only need to provide an

FnOnce(&&'short T) -> &U

i.e.

for<'a> FnOnce(&'a &'short T) -> &'a U

Since &'a &'short T comes with an implicit 'short: 'a bound, you can easily turn the

OwningRef<Box<()>, &'short T>

into

OwningRef<Box<()>, T>

in effect liberating yourself from the 'short bound. The call to map in the example is .map(|s: &&str| *s).

Another example

I also managed to create a general transmute_lifetime function:

fn transmute_lifetime<'a, 'b, T>(r: &'a T) -> &'b T {
    let r = &r;
    let x = OwningRef::new(Box::new(())).map(|_| &r).map(|r| &***r);
    &*Box::leak(Box::new(x))
}

which works like this:

OwningRef<Box<()>, ()>
  ⇓    // by OwningRef::map
OwningRef<Box<()>, &'short &'a T>
  ⇓    // by OwningRef::map
OwningRef<Box<()>, T>
  ⇓    // by Box::new
Box<OwningRef<Box<()>, T>>
  ⇓    // by Box::leak
&'b OwningRef<Box<()>, T> 
  ⇓    // dereferencing
&'b T

steffahn avatar Jan 18 '21 12:01 steffahn

A possible fix is to use

pub struct OwningRef<'t, O, T: ?Sized> {
    owner: O,
    reference: *const T,
    marker: PhantomData<&'t T>,
}

and

pub struct OwningRefMut<'t, O, T: ?Sized> {
    owner: O,
    reference: *mut T,
    marker: PhantomData<&'t T>,
}

see for example the draft PR #72.

steffahn avatar Jan 18 '21 17:01 steffahn