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
ghost opened this issue Nov 9, 2015 · 31 comments
Closed
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@ghost
Copy link

ghost 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 }
@ghost
Copy link
Author

ghost 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 eddyb added the A-borrow-checker Area: The borrow checker label Nov 9, 2015
@eddyb
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
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
Copy link
Contributor

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

@brson brson added I-nominated T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 9, 2015
@arielb1
Copy link
Contributor

arielb1 commented Nov 9, 2015

Wait can you move in guards?

@Manishearth
Copy link
Member

@arielb1 you shouldn't be able to

@Aatch
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
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 bstrie added the I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness label Nov 10, 2015
@bstrie
Copy link
Contributor

bstrie commented Nov 10, 2015

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

@ghost
Copy link
Author

ghost commented Nov 10, 2015

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

@Manishearth
Copy link
Member

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

@eddyb
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
Copy link
Contributor

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
Copy link
Contributor

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 High priority and removed I-nominated labels Nov 12, 2015
@Aatch
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
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 Medium priority and removed P-high High priority labels Nov 19, 2015
@pnkfelix pnkfelix added the A-MIR Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html label Nov 19, 2015
@pnkfelix
Copy link
Member

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

@brson brson added the E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot. label Aug 4, 2016
@nikomatsakis nikomatsakis removed the A-MIR Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html label Aug 4, 2016
@KiChjang
Copy link
Member

This is still relevant on the latest nightly.

@arielb1
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
Copy link
Member

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
Copy link
Contributor

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

@pnkfelix
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 High priority and removed P-medium Medium priority labels Jan 9, 2018
@nikomatsakis
Copy link
Contributor

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

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

@nikomatsakis nikomatsakis added E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. and removed E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot. labels Jan 18, 2018
@nikomatsakis
Copy link
Contributor

triage: P-medium

@rust-highfive rust-highfive added P-medium Medium priority and removed P-high High priority labels Jan 18, 2018
@Manishearth
Copy link
Member

#47549

@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 as completed in 101f1e1 Jan 23, 2018
@ghost
Copy link
Author

ghost commented Jan 23, 2018

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

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jan 3, 2023
…wck-test, r=Nilstrieb

Test the borrowck behavior of if-let guards

Add some tests to make sure that if-let guards behave the same as if guards with respect to borrow-checking. Most of them are a naive adaptation, replacing an `if` guard with `if let Some(())`.
This includes regression tests for notable issues that arose for if guards (rust-lang#24535, rust-lang#27282, rust-lang#29723, rust-lang#31287) as suggested in rust-lang#51114 (comment).

cc `@pnkfelix` are there any other tests that you would want to see?
cc tracking issue rust-lang#51114
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests