Skip to content

[borrowck] handling of drops invalidating borrows #40

Open
@arielb1

Description

@arielb1

Destructors can invalidate borrows of their contents:

struct VecWrapper<'a>(&'a mut Box<u32>);

impl<'a> Drop for VecWrapper<'a> {
    fn drop(&mut self) {
        *self.0 = Box::new(0);
    }
}

fn get_dangling<'a>(value: VecWrapper<'a>) -> &'a u32 {
    let s_inner: &'a Box<u32> = &*value.0; // Borrow (*)
    let result = &s_inner;
    result
    // the destructor of `value`, that runs here, invalidates the borrow (*)
}

Today's rustc's handling of the situation does not provide us much guidance, as can be seen by this example compiling, running, and accessing invalid memory (that is rust-lang/rust#31567).

However, we can't have any drop invalidate all interior references. In the most trivial case, drops of "trivial drop" types, like references, don't even appear in MIR. And even if they appeared, having mutable references invalidate their contents when dropped would basically break all of Rust.

Note that I don't think we have to worry about the interior of containers in the common case, at least until we implement some sort of DerefPure:

fn example_indexmut<'a>(mut v: Vec<&'a mut u32>) -> &'a mut u32 {
    &mut *v[0] //~ ERROR
}

The desugaring converts that into:

// that is equivalent to this MIR modulo panics
t0 = &'a mut v; // can't be a shorter lifetime, because of constraints
t1 = IndexMut::index_mut(t0, 0);
ret = &mut *t1;
drop(v);
storagedead v;

Here the lifetime constraint in IndexMut forces the borrow of v (for t0) to live for 'a, which already causes a borrowck error with the storagedead.

However, technically, we could have a container that "publicly" contains its contents, and which we know can't access its contents because of dropck:

struct NoisyDrop<T>(T);

impl<#[may_dangle] T> Drop for T {
    fn drop(&mut self) {
        println!("Splat!");
    }
}

fn is_actually_sound<'a>(value: NoisyDrop<&'a mut Box<u32>>) -> &'a u32 {
    let s_inner: &'a Box<u32> = &*value.0; // Borrow (*)
    let result = &s_inner;
    result
    // the destructor can't actually access the mutable reference,
    // because it is `#[may_dangle]`, and therefore this function
    // is sound.
}

I don't think that is something we have to strive to implement, at least until someone comes with up with a use-case. So I think a reasonable rule would be that a drop invalidates all contents that are reachable from a destructor. We already do this check today in order to check whether deinitialization/reinitialization would be valid.

However, the question remains - do we want Box to have a destructor, or should it be treated like any other type?

fn foo<'a, T>(x: (SomethingWithADropImpl<&'a mut T>, Box<&'a mut T>, &'a mut T)) -> &'a mut T {
    return &mut x.0; // obviously unsound unless we do dropck trickery - let's ban this
    return &mut x.1; // not sure - allowed today (of course), sound, but do we want it?
    return &mut x.2; // allowed today, probably want to support
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions