-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lift internal assumption is false #14
Comments
I believe that this is unsound, because then in the I didn't manage to cause any concrete unsoundness or to cause miri to catch it. Here's some speculation on what may actually happen in practice: Note that It will then also "leak" the I'm guessing that's because it will end up copying the logically uninitialized value in |
You are definitely correct about the internal assumption, but I do not think there is a soundness issue. As far as I know there is no notion of "logically" uninitialized value in Rust. The In fact, should there be "logically" uninitialized values, the entire I see multiple axes of improvement:
Thoughts? |
To clarify, by "logically uninitialized" I mean it in the sense that a variable that has been moved out from, still exists as a variable, but behaves like it's uninitialized, since no one can use it. You can see this term in the rustonomicon Specifically, in this case, pub fn lift<F, R>(root: R, fun: F) -> R
where
F: for<'a> FnOnce(&'a R) -> &'a mut R,
{
let root = ManuallyDrop::new(root);
let slot = fun(&root) as *mut R as *mut ManuallyDrop<R>;
// Assume that they do point to the same point, and that we're in release mode
debug_assert_ne!(slot as *const _, &root as *const _);
unsafe {
replace(slot, /* root is moved here */ root)
// `slot` now points to a moved-from, (logically) uninitialized place.
}
}
unsafe fn replace<T>(dest: *mut ManuallyDrop<T>, src: ManuallyDrop<T>) -> T {
// Now `dest` points to the `lift` function's `root`, which is uninitialized, and different than this functions `src`.
// This reads from an uninitialized place
let result = ptr::read(dest);
ptr::copy(/* doesn't necessarilly point to the same position as `dest` */ &src as *const _, dest, 1);
ManuallyDrop::into_inner(result)
} I believe that rustc would be justified in marking However, miri doesn't report UB. Remember though, that miri isn't guaranteed to catch UB. pub fn lift<F, R>(root: R, fun: F) -> R
where
F: for<'a> FnOnce(&'a R) -> &'a mut R,
{
let root = ManuallyDrop::new(root);
let slot = fun(&root) as *mut R as *mut ManuallyDrop<R>;
// Assume that they do point to the same point, and that we're in release mode
debug_assert_ne!(slot as *const _, &root as *const _);
unsafe {
let slot_ptr = slot as *const _;
let result = ptr::read(slot_ptr);
ptr::copy(/* This now points to the same place as `slot` */ &root as *const _, slot_ptr, 1);
ManuallyDrop::into_inner(result)
}
} In the inlined version, you temporarily get two copies of the same value, which is then immediately forgotten. Since panicking cannot happen during that time, it seems okay. In fact, I think inlining the call is probably a good way to solve this issue, and I propose to solve it this way. |
Inlining is indeed a possibility, but it's a code-block that'd have to be repeated 3 times, which makes it unpalatable. The reason that inlining works is that |
* Changes: - Remove debug assertions that the user-provided function cannot return a reference to root in the lift functions. - Remove useless move of root variable, to avoid muddying waters with regard to references to it. - Add tests for self-lifting. - Clarify safety comments, moving from initialized to valid, as ptr::read and ptr::copy are defined in terms of validity. * Motivation: @noamtashma demonstrated in #14 that it was possible -- using GhostCell or similar -- for the user-provided function to return an alias to the root, so ensure this usecase is supported correctly.
Fixed in v0.6.1. |
In
lift.rs
, in the implementation of the lift function, there is this line:slot, &root
presumably can't point to the same location becauseroot
owns a value whereasslot
is a mutable reference to a value (of the same type), and both of them being the same would violate Rust's aliasing guarantees.However, this is false in this example:
Here, a
&GhostCell<T>
is safely aliasing a&mut GhostCell<T>
. This happens by the chain of&GhostCell<T> -> &mut T -> &mut GhostCell<T>
, which can be called in regular code without using thelift
function.To my understanding, this is safe because
GhostCell
ensures these references cannot be used at the same time, by the use of the token.GhostCell
is/contains anUnsafeCell
which causes some things to be allowable, both in terms of stacked borrows and in terms of rustc assuming less things.Therefore, the code of
lift
is wrong to assume that this can never happen.The text was updated successfully, but these errors were encountered: