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