-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Hi there, interesting little API you have here. It seems this crate works as intended w.r.t. the operational behavior (I suppose you probably have probably already tested it with miri yourself, too.) but there are 2 API soundness issues that you should fix:
I’m looking only at MutCursor now, but wouldn’t be surprised that similar issues might be present in the other versions, too.
1. lifetimes
the only time you can hand out a 'root-lifetime reference to any user code soundly is when the MutCursor is for sure going to be consumed. Otherwise, that lifetime is wrong. So into_mut is correct, top_mut correctly only gives a shorter-lived one; try_map_into_mut correctly requires a higher-order closure (for<'r> …), but advance is incorrect.
let mut x = 1;
let mut c = MutCursor::<_, 1>::new(&mut x);
let mut r1 = None;
c.advance(|r| {
r1 = Some(r);
None
});
let mut r2 = None;
c.advance(|r| {
r2 = Some(r);
None
});
let [r1, r2]: [&mut i32; 2] = [r1.unwrap(), r2.unwrap()];
// now we have aliasing mutable references!you should change advance so it works with for<'r> FnOnce(&'r mut T) -> Option<&'r mut T> as well.
(which you can equivalently write as FnOnce(&mut T) -> Option<&mut T>)
2. variance
Your cursor type semantically contains a mutable borrow, and as such T must be invariant. With neither NonNull<T> nor the PhantomData<&'a T>, you will actually achieve this. Instead, you should use PhantomData<&'a mut T>.
let mut dummy = String::new();
let mut dummy_ref = &mut dummy;
let mut c = MutCursor::<_, 1>::new(&mut dummy_ref);
{
let mut hello = String::from("hello!");
// covariance allows turning `MutCursor<'a, &'long mut String, 1>` into
// `MutCursor<'a, &'short mut String, 1>`, where the `'short` lifetime now
// can be shorter than the original lifetime of `dummy_ref`
//
// (I believe the coercion actually already happens when `c` gets assigned above,
// but even without that, you could imagine inserting a `let c = c as _;` step
// that makes this more explicit)
*c.top_mut() = &mut hello; // this sets `dummy_ref` to point to `hello`
println!("{}", dummy_ref); // hello!
}
println!("{}", dummy_ref); // <garbage> (use-after-free)3. thread safety (no issue here, improvement suggestion instead)
you currently (conservatively) require &mut T: Send + Sync for all your Send or Sync implementations of MutCursor. But since MutCursor is giving the same kind of exclusive access that &mut T gives, you can relax the restrictions here. You only need T: Send (equivalent to &mut T: Send) for MutCursor<'_, T, N>: Send; and you only need T: Sync (equivalent to &mut T: Sync) for MutCursor<'_, T, N>: Sync