owning-ref-rs
owning-ref-rs copied to clipboard
`OwningRef::map` is unsound
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
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.