Skip to content

borrowck allows use-after-free with weird struct A{foo: Option<~fn(&mut A)>} construction #9853

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

Closed
ben0x539 opened this issue Oct 14, 2013 · 3 comments
Labels
A-lifetimes Area: Lifetimes / regions

Comments

@ben0x539
Copy link
Contributor

Here's a bit of a verbose test case for a situation where I get away with accessing a value in a ~-closure environment after its destructor has run. valgrind also complains. Not sure whether this falls under a known bug, I didn't really know what terms to search for.

struct A {
    foo : Option<~fn(&mut A)>
}

impl A {
    fn call(&mut self) {
        (*self.foo.get_ref())(self); // this is probably the broken bit
    }
}

struct S {
    msg: ~str
}

impl Drop for S {
    fn drop(&mut self) {
        println!("drop {:?}", *self);
        self.msg = ~"dropped";
    }
}

fn main() {
    let x = S { msg: ~"ok" };
    let f: ~fn(&mut A) = |a| { a.foo = None; println!("{:?}", x); }; // this frees closure environment via `a`, then accesses it
    let mut a = A { foo: Some(f) };
    a.call();
}

output:

drop S{msg: ~"ok"}
S{msg: ~"dropped"}
@MicahChalmer
Copy link
Contributor

This seems similar to the recursive closure problem that @nikomatsakis mentioned in a blog post. It's another case where an alias inside a closure environment is not considered by the borrower checker.

Here's a version without the Option that shows the same problem. This version, like the original, compiles (even though it shouldn't) and gives the same use-after-free result as above:

struct A {
    foo : ~fn(&mut A)
}

impl A {
    fn call(&mut self) {
        (self.foo)(self)
    }
}

struct S {
    msg: ~str
}

impl Drop for S {
    fn drop(&mut self) {
        println!("drop {:?}", *self);
        self.msg = ~"dropped";
    }
}

fn main() {
    let x = S { msg: ~"ok" };
    let f: ~fn(&mut A) = |a| { a.foo = |_| {}; println!("{:?}", x); }; // this frees closure environment via `a`, then accesses it
    let mut a = A { foo: f };
    a.call();
}

If you take out the owned closure and insert an equivalent owned struct instead, the borrow checker is no longer fooled. The compiler correctly rejects this one:

struct A {
    foo : ~NotAClosure
}

impl A {
    fn call(&mut self) {
        self.foo.call(self);
    }
}

struct NotAClosure {
    s: Option<S>
}

impl NotAClosure {
    fn call(&self, a:&mut A) {
        a.foo.s = None;
        println!("{:?}", self);
    }
}

struct S {
    msg: ~str
}

impl Drop for S {
    fn drop(&mut self) {
        println!("drop {:?}", *self);
        self.msg = ~"dropped";
    }
}

fn main() {
    let mut a = A{ foo: ~NotAClosure { s: Some(S { msg: ~"ok" }) } };
    a.call();
}

The error message points out the problematic line:

wtf3.rs:7:22: 7:27 error: cannot borrow `*self` as mutable because it is also borrowed as immutable
wtf3.rs:7         self.foo.call(self);
                                ^~~~~
wtf3.rs:7:8: 7:16 note: second borrow of `*self` occurs here
wtf3.rs:7         self.foo.call(self);
                  ^~~~~~~~

With the upcoming change of ~fn to proc, this bug might inherently be fixed...

@nikomatsakis
Copy link
Contributor

cc me

@alexcrichton
Copy link
Member

Closing, ~fn() no longer exists as a type and borrowing semantics around closures have changed significantly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lifetimes Area: Lifetimes / regions
Projects
None yet
Development

No branches or pull requests

4 participants