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

NLL: need to decide match guard static control-flow semantics #47295

Closed
pnkfelix opened this issue Jan 9, 2018 · 10 comments
Closed

NLL: need to decide match guard static control-flow semantics #47295

pnkfelix opened this issue Jan 9, 2018 · 10 comments
Assignees
Labels
A-borrow-checker Area: The borrow checker A-MIR Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html A-NLL Area: Non-lexical lifetimes (NLL) C-enhancement Category: An issue proposing an enhancement or a PR with one. NLL-sound Working towards the "invalid code does not compile" goal

Comments

@pnkfelix
Copy link
Member

pnkfelix commented Jan 9, 2018

Here is a test that already exists in the run-pass suite:

fn main() {
    let x: Box<_> = box 1;

    let v = (1, 2);

    match v {
        (2, 1) if take(x) => (),
        (1, 2) if take(x) => (),
        _ => (),
    }
}

fn take<T>(_: T) -> bool { false }

The above is accepted under AST-borrowck. Unfortunately, it is rejected by MIR-borrowck:

% ./build/x86_64-unknown-linux-gnu/stage1/bin/rustc -Z borrowck=compare ../src/test/run-pass/move-guard-const.rs
error[E0382]: use of moved value: `x` (Mir)
  --> /home/pnkfelix/Dev/Mozilla/rust.git/src/test/run-pass/move-guard-const.rs:22:24
   |
21 |         (2, 1) if take(x) => (),
   |                        - value moved here
22 |         (1, 2) if take(x) => (),
   |                        ^ value used here after move
   |
   = note: move occurs because `x` has type `std::boxed::Box<i32>`, which does not implement the `Copy` trait

error: aborting due to previous error

So here's the issue: We were hoping, in MIR-borrowck, to apply a pretty conservative semantics to model the control-flow of match arms, to maximize freedom in choosing future match-desugaring strategies.

But clearly we already allow moves in match guards in a way that we cannot just use the current naive "every guard might be executed regardless of its associated pattern" ... at least, not without marking it as a breaking-change to the language.

It might not be that hard to encode some amount of intelligence into the control-flow, in terms of modelling pattern disjointness in the arms. But its not what we're currently doing, so we need to figure out if doing that is a blocker for deploying MIR-borrowck (and thus NLL).

@pnkfelix pnkfelix added A-borrow-checker Area: The borrow checker A-MIR Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html A-NLL Area: Non-lexical lifetimes (NLL) WG-compiler-nll labels Jan 9, 2018
@pnkfelix pnkfelix changed the title NLL: need to decide semantics of move guards NLL: need to decide static control-flow semantics of move guards Jan 9, 2018
@pnkfelix pnkfelix changed the title NLL: need to decide static control-flow semantics of move guards NLL: need to decide move guard static control-flow semantics Jan 9, 2018
@pnkfelix pnkfelix changed the title NLL: need to decide move guard static control-flow semantics NLL: need to decide match guard static control-flow semantics Jan 9, 2018
@nikomatsakis
Copy link
Contributor

It seems that the reason we accept this program is because of a very narrow exception in the CFG generation that specifically targets "all constant" arms:

// If both this pattern and the previous pattern
// were free of bindings, they must consist only
// of "constant" patterns. Note we cannot match an
// all-constant pattern, fail the guard, and then
// match *another* all-constant pattern. This is
// because if the previous pattern matches, then
// we *cannot* match this one, unless all the
// constants are the same (which is rejected by
// `check_match`).
//
// We can use this to be smarter about the flow
// along guards. If the previous pattern matched,
// then we know we will not visit the guard in
// this one (whether or not the guard succeeded),
// if the previous pattern failed, then we know
// the guard for that pattern will not have been
// visited. Thus, it is not possible to visit both
// the previous guard and the current one when
// both patterns consist only of constant
// sub-patterns.
//
// However, if the above does not hold, then all
// previous guards need to be wired to visit the
// current guard pattern.
if prev_has_bindings || this_has_bindings {
while let Some(prev) = prev_guards.pop() {
self.add_contained_edge(prev, guard_start);
}
}

@petrochenkov
Copy link
Contributor

The "all constant" rule seems very wrong.

First, the "all the constants are the same" condition is no longer "rejected by check_match".

    match v {
        (1, 2) => (),
        (1, 2) => (), // OK, only a warning
        _ => (),
    }

Second, it was never (since 1.0) rejected in presence of if guards, e.g. this compiles on Rust 1.0

fn main() {
    let x = Box::new(1);

    let v = (1, 2);

    match v {
        (1, 2) if rand(x) => (),
        (1, 2) if rand(x) => (), // May consume `x` twice
        _ => (),
    }
}

fn rand<T>(_: T) -> bool { false /* or true */}

https://godbolt.org/g/5tdLJk

@nikomatsakis
Copy link
Contributor

@petrochenkov I'm not a big fan of the rule, but then it may be we need to keep some variant of it for backwards compatibility. Still, that's a pretty interesting example -- is there a bug filed for that? Maybe we can use it as justification to simplify the code and remove this silly rule. It looks to have been put in place just to satisfy a particular run-pass test, and hopefully isn't relied upon much in crater (have to test).

@nikomatsakis
Copy link
Contributor

Maybe we should whip up a branch just for cratering purposes that removes that relevant code from the CFG.

@pnkfelix
Copy link
Member Author

@nikomatsakis Nice! I say that @petrochenkov 's example lets us file this under "we can issue breaking changes to fix soundness holes", because that certainly is a soundness hole, right?

@nikomatsakis
Copy link
Contributor

@pnkfelix seems like it

@nikomatsakis
Copy link
Contributor

The main question is how much real-world code relies on this. It's hard to imagine there being much.

@nikomatsakis nikomatsakis added the NLL-sound Working towards the "invalid code does not compile" goal label Mar 14, 2018
pnkfelix added a commit to pnkfelix/rust that referenced this issue Mar 28, 2018
An old fix for moves-in-guards had a hack for adjacent all-const match arms.

The hack was explained in a comment, which you can see here:
https://github.com/rust-lang/rust/pull/22580/files#diff-402a0fa4b3c6755c5650027c6d4cf1efR497

But hack was incomplete (and thus unsound), as pointed out here:
rust-lang#47295 (comment)

Plus, it is likely to be at least tricky to reimplement this hack in
the new NLL borrowck.

So rather than try to preserve the hack, we want to try to just remove
it outright. (At least to see the results of a crater run.)

[breaking-change]

This is a breaking-change, but our hope is that no one is actually
relying on such an extreme special case. (We hypothesize the hack was
originally added to accommodate a file in our own test suite, not code
in the wild.)
bors added a commit that referenced this issue Apr 3, 2018
…komatsakis

Remove adjacent all-const match arm hack.

An old fix for moves-in-guards had a hack for adjacent all-const match arms.

The hack was explained in a comment, which you can see here:
https://github.com/rust-lang/rust/pull/22580/files#diff-402a0fa4b3c6755c5650027c6d4cf1efR497

But hack was incomplete (and thus unsound), as pointed out here:
#47295 (comment)

Plus, it is likely to be at least tricky to reimplement this hack in
the new NLL borrowck.

So rather than try to preserve the hack, we want to try to just remove
it outright. (At least to see the results of a crater run.)

[breaking-change]

This is a breaking-change, but our hope is that no one is actually
relying on such an extreme special case. (We hypothesize the hack was
originally added to accommodate a file in our own test suite, not code
in the wild.)
@pnkfelix
Copy link
Member Author

pnkfelix commented Apr 3, 2018

#49447 got merged, so its possible we may be able to close this once we've confirmed that no one complains about the effect of #49447

@XAMPPRocky XAMPPRocky added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Apr 10, 2018
@KamilaBorowska
Copy link
Contributor

This issue seems to be solved?

@pnkfelix
Copy link
Member Author

@xfix ah, yes; for some reason in my head I was lumping this in with #27282, but it is clearly a distinct bug and it does seem to be resolved, given that I have not seen any complaints about #49447.

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 A-MIR Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html A-NLL Area: Non-lexical lifetimes (NLL) C-enhancement Category: An issue proposing an enhancement or a PR with one. NLL-sound Working towards the "invalid code does not compile" goal
Projects
None yet
Development

No branches or pull requests

5 participants