Skip to content
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

Variables moved from in match guards are still accessible in other match arms. #29723

Closed
Toby-S opened this Issue Nov 9, 2015 · 31 comments

Comments

Projects
None yet
@Toby-S
Copy link
Contributor

Toby-S commented Nov 9, 2015

The following code segfaults at runtime on stable, beta, and nightly:

fn main() {
    let x = 10;
    let s = Custom { val: Vec::new() };

    match x {
        10 if test(s) => println!("oops"),
        _ => println!("eek {:?}", s.val),
    }
}

#[derive(Debug)]
pub struct Custom {
    val: Vec<u8>,
}

impl Drop for Custom {
    fn drop(&mut self) { println!("Dropping"); }    
}

#[allow(unused_variables)]
fn test(s: Custom) -> bool { false }
@Toby-S

This comment has been minimized.

Copy link
Contributor Author

Toby-S commented Nov 9, 2015

Possibly aided by #3478?

@eddyb eddyb changed the title Moving values in match guards can lead to runtime segfaults Variables moved from in match guards are still accessible in other match arms. Nov 9, 2015

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Nov 9, 2015

Shorter testcase:

fn main() {
    let s = String::new();
    let _s = match 0 {
        0 if { drop(s); false } => String::from("oops"),
        _ => {
            // This should trigger an error,
            // s could have been moved from.
            s
        }
    };
}

CFG for this testcase:
flowgraph=main

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Nov 9, 2015

cc @rust-lang/compiler

This would be solved by the MIR, right?
The way match guards would be desugared in the MIR, they would be potential ancestors of following match arms, and a MIR borrowck wouldn't be able to miss them.

@LLBlumire

This comment has been minimized.

Copy link

LLBlumire commented Nov 9, 2015

Bringing mine and @eddyb 's IRC conversation into here for readability.

The issue is that the path on the left in the image above does not chain into the path on the right, meaning that drop(s) can be followed by { s }.

This seems to be a configuration issue in https://github.com/rust-lang/rust/blob/master/src/librustc/middle/cfg/construct.rs#L429-L529

@arielb1

This comment has been minimized.

Copy link
Contributor

arielb1 commented Nov 9, 2015

Wait can you move in guards?

@Manishearth

This comment has been minimized.

Copy link
Member

Manishearth commented Nov 10, 2015

@arielb1 you shouldn't be able to

@Aatch

This comment has been minimized.

Copy link
Contributor

Aatch commented Nov 10, 2015

Hmm, looks like I missed this case when I fixed a very similar issue (moving in multiple guard expressions). The issue is that the way the final guard is handled means that it acts as if it can't fail. Wiring up the contents of prev_guards to arm exit in the case where the arm has no guard and no bindings (including wildcard) should fix it. It should also do the same at the very end, adding edges from the remaining guards to the exit of the match expression.

@Manishearth I tried disallowing it, but there's some code around that moves in guards. Its also a breaking change.

@Aatch

This comment has been minimized.

Copy link
Contributor

Aatch commented Nov 10, 2015

Ok, in trying to fix this I ran into an issue that is probably why I didn't handle this case last time. Borrows due to by-ref bindings in matches are scoped the match expression itself, meaning that something like:

match foo_opt {
  Some(ref foo) if cond => { ... }
  None => { foo_opt.val = bar; }
}

will complain that you can't assign to foo_opt.val because it's already borrowed (which is clearly not true). It's essentially the canonical example for the kind of thing SEME will solve, except worse.

@bstrie

This comment has been minimized.

Copy link
Contributor

bstrie commented Nov 10, 2015

@eddyb How did you get rustc to spit out the CFG like that?

@Toby-S

This comment has been minimized.

Copy link
Contributor Author

Toby-S commented Nov 10, 2015

@bstrie rustc file.rs -Z unstable-options --unpretty flowgraph=main > out.dot

@Manishearth

This comment has been minimized.

Copy link
Member

Manishearth commented Nov 10, 2015

Oh, it's unpretty now. I knew it used to be --pretty and was wondering why it wasn't working.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Nov 10, 2015

@bstrie What @Toby-S said, followed by dot -O -Tpng out.dot (I would do SVG instead, which tends to be nicer to browse, since you can easily Ctrl+F in it, but I don't know how to host it).

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Nov 12, 2015

Key question is whether it's worth trying to fix this now, or just accelerate efforts to port borrowck to MIR. There are already a number of these sorts of issues that would be fixed by such a thing. @Aatch what efforts did you make to fix this?

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Nov 12, 2015

triage: P-high

High because soundness but maybe we should downgrade to medium or otherwise try to solve via MIR.

@rust-highfive rust-highfive added P-high and removed I-nominated labels Nov 12, 2015

@Aatch

This comment has been minimized.

Copy link
Contributor

Aatch commented Nov 17, 2015

@nikomatsakis I tried to change the CFG to represent the control flow more accurately but ran into the issue I described above. I think there needs to be a change to the regions around match arms, but I'm less familiar with the code there and not sure what to change or how.

@nrc

This comment has been minimized.

Copy link
Member

nrc commented Nov 19, 2015

triage: P-medium

Since we're basically waiting for MIR to fix this, it's not really P-high

@rust-highfive rust-highfive added P-medium and removed P-high labels Nov 19, 2015

@pnkfelix pnkfelix added the A-mir label Nov 19, 2015

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Nov 19, 2015

(tagging with A-mir since we are waiting for MIR to fix this)

@brson brson added the E-hard label Aug 4, 2016

@nikomatsakis nikomatsakis removed the A-mir label Aug 4, 2016

@KiChjang

This comment has been minimized.

Copy link
Member

KiChjang commented Nov 21, 2016

This is still relevant on the latest nightly.

@sp-1234

This comment has been minimized.

Copy link

sp-1234 commented Oct 3, 2017

Is there an ETA for this or something?
This is defeated memory safety in the safe Rust subset so I guess this must be the top importance…

@bstrie

This comment has been minimized.

Copy link
Contributor

bstrie commented Oct 9, 2017

@sp-1234 Regarding an ETA, the plan seems to be that this will be fixed by the act of migrating the borrow-checker to later in the pipeline of compiler passes. This has been in the works for quite a while now and should be ready by the end of this year. This will also fix several other soundness bugs of the same nature.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Oct 10, 2017

@bstrie "later"? Are you referring to MIR borrowck? That has nothing to do with "earlier" or "later" (in fact, at least until recently, borrowck has always ran second-to-last, just before lints, which probably still are the last thing in the analysis passes, before translation), but rather the underlying abstraction on which the the checking is performed - with MIR, complex language-level constructs do not require as complex of handling, sometimes even being subsumed by the generality of MIR dataflow.

@arielb1

This comment has been minimized.

Copy link
Contributor

arielb1 commented Oct 31, 2017

Turns out we need either NLL (the conditionally-canceled borrow part) or having differently-typed guard binding and body binding variables to make this work:

use std::cell::RefCell;

fn assign<'a, 'b>(x: &RefCell<Option<&'a &'b mut u32>>, y: &'a &'b mut u32) {
    *x.borrow_mut() = Some(y);
}

fn main() {
    let (mut t, mut ten);
    ten = 10;
    t = Some(&mut ten);
    unsound(&mut t);
}

fn unsound<'a>(opt: &'a mut Option<&'a mut u32>) -> Option<&'a mut u32> {
    let store: RefCell<Option<&&mut u32>> = RefCell::new(None);
    match *opt {
        #[cfg(segfault)]
        Some(ref mut x) if {
            // this (making `x` escape from the arm) should be disallowed
            // - `x` shouldn't be `&'a mut &'a mut u32` here
            assign(&store, x);
            false
        } => {
           None
        }
        #[cfg(not(segfault))]
        Some(ref mut x) if { false } => {
            // but just using `x` should be fine: `x` has the type `&'a mut &'a mut u32` here
            Some(x)
        }
        ref mut x => {
            *x = None;
            println!("{:?}", store.borrow());
            None
        }
    }
}
@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Dec 20, 2017

The current status of MIR-borrowck indicates that we can address the first example and the second example. We also correctly reject the cfg(segfault) case in the third example; but it unfortunately also rejects the cfg(not(segfault)) case in that same third example.

My inclination is to land regression tests, alongside feature(nll), for the three cases that segfault under AST borrowck. We obviously can also deploy feature(nll) as long as it is opt-in while the cfg(not(segfault)) case continues to be rejected. At that time, we should file a new ticket to investigate extending MIR-borrowck to accept the cfg(not_segfault)) scenario outlined in the third example.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Dec 20, 2017

FWIW, testing with ariel's example, NLL + MIR borrowck seems to accept cfg(not(segfault)) and reject cfg(segfault).

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Jan 9, 2018

I'm increasing priority on this because we should make sure we address it and other related issues as part of migration to NLL.

@pnkfelix pnkfelix added P-high and removed P-medium labels Jan 9, 2018

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Jan 11, 2018

@nikomatsakis nikomatsakis added this to the NLL Future Compat Warnings milestone Jan 11, 2018

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Jan 18, 2018

Added to #47366 -- this is basically E-needstest now

@nikomatsakis nikomatsakis added E-needstest and removed E-hard labels Jan 18, 2018

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Jan 18, 2018

triage: P-medium

@rust-highfive rust-highfive added P-medium and removed P-high labels Jan 18, 2018

Manishearth added a commit to Manishearth/rust that referenced this issue Jan 18, 2018

@Manishearth

This comment has been minimized.

Copy link
Member

Manishearth commented Jan 18, 2018

@nikomatsakis nikomatsakis removed this from the NLL run-pass without ICEs milestone Jan 19, 2018

kennytm added a commit to kennytm/rust that referenced this issue Jan 23, 2018

@bors bors closed this in 101f1e1 Jan 23, 2018

@Toby-S

This comment has been minimized.

Copy link
Contributor Author

Toby-S commented Jan 23, 2018

Awesome! Thanks to everyone who played a part in resolving this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.