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: regressions due to weakness in two-phase borrows #52934

Closed
pnkfelix opened this issue Aug 1, 2018 · 17 comments
Closed

NLL: regressions due to weakness in two-phase borrows #52934

pnkfelix opened this issue Aug 1, 2018 · 17 comments
Assignees
Labels
A-NLL Area: Non-lexical lifetimes (NLL) E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. NLL-complete Working towards the "valid code works" goal T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Milestone

Comments

@pnkfelix
Copy link
Member

pnkfelix commented Aug 1, 2018

Look at rust-lang/rust-clippy#2982

It seems like some of the changes NLL is requiring were expected to be handled silently by two phase borrows

@pnkfelix pnkfelix added A-NLL Area: Non-lexical lifetimes (NLL) WG-compiler-nll NLL-complete Working towards the "valid code works" goal labels Aug 1, 2018
@estebank estebank changed the title NLL: régressions due to weakness in two-phase borrows NLL: regressions due to weakness in two-phase borrows Aug 1, 2018
@pnkfelix
Copy link
Member Author

pnkfelix commented Aug 3, 2018

(its also possible that rust-lang/rust-clippy#2982 was in fact due to this oversight in the 2018 migration mode: #52967 )

@nikomatsakis
Copy link
Contributor

Based on the contents of rust-lang/rust-clippy#2982, it appears @pnkfelix that you now believe these to be legitimate regressions (i.e., NLL-fixed-by-NLL)? I'm inclined therefore to close this issue? Or am I wrong...

@nikomatsakis nikomatsakis added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Aug 21, 2018
@nikomatsakis
Copy link
Contributor

Leaving untriaged for now.

@ghost
Copy link

ghost commented Aug 26, 2018

This is fixed. The borrow checker warnings aren't emitted with the current nightly compiler. See rust-lang/rust-clippy#3085.

@nikomatsakis nikomatsakis added this to the Rust 2018 RC milestone Aug 27, 2018
@nikomatsakis
Copy link
Contributor

Nominating for discussion — probably we can just close this? I put it on the milestone too just to help us keep things tidy

@nikomatsakis
Copy link
Contributor

Hmm, I think I would like to have some minimized reproductions of the cases in question prior to closing. I'd like to look at just what was making them fail before and convince myself that the new behavior is not going to have problems like #38899 (that particular problem does not reoccur, I tested :)

@nikomatsakis nikomatsakis added the E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. label Aug 27, 2018
@pnkfelix
Copy link
Member Author

pnkfelix commented Aug 28, 2018

@nikomatsakis its not that I thought they were legitimate regressions.

It that I believe(d) rust-lang/rust-clippy#2982 was mis-identified (by me) as arising due to a fault or weakness in 2PB, when in fact it was a much simpler problem that was subsequently fixed.

Its quite possible that we do need an issue tracking all the cases where 2PB could be improved. But I don't think it would be useful for this issue to serve that purpose.

@pnkfelix
Copy link
Member Author

Having said that, I think I agree that some minimized tests that model what was going on in rust-lang/rust-clippy#2982 may indeed be a good idea, assuming that those cases really do significantly differ from any tests that were part of PR #52967

@pnkfelix pnkfelix self-assigned this Aug 28, 2018
@pnkfelix
Copy link
Member Author

(I'll do the minimization, since I already had dipped my toe in this a month ago.)

@lqd lqd self-assigned this Aug 28, 2018
@pnkfelix
Copy link
Member Author

pnkfelix commented Sep 3, 2018

Okay here is a somewhat minimized test. (I say "somewhat" because there are trivial ways to further reduce it that would obscure its origins within clippy and librustc. I think think this minimization represents a sweet spot in terms of inferring the relationship with the original source.)

playground link

#![allow(dead_code)]

#[derive(Copy, Clone)]
struct BodyId;
struct AnonConst { body: BodyId }
enum ExprKind { Closure(BodyId), Repeat(AnonConst), Others }
struct Expr { node: ExprKind, }

struct Body { value: Expr }

struct Map;
impl Map { fn body(&self, _: BodyId) -> &Body { loop { } } }

struct SpanlessHash<'a> { cx: &'a Map, }

impl <'a> SpanlessHash<'a> {
    fn hash_expr(&mut self, e: &Expr) {
        match e.node {
            ExprKind::Closure(eid) => {
                self.hash_expr(&self.cx.body(eid).value);
            },
            ExprKind::Repeat(ref l_id) => {
                self.hash_expr(&self.cx.body(l_id.body).value);
            },
            _ => {}
        }
    }
}

struct Config;
trait LateLintPass<'a> { }
type LateLintPassObject = Box<dyn for<'a> LateLintPass<'a>>;

struct TriviallyCopyPassByRef { }
impl TriviallyCopyPassByRef { fn new(_: &Config) -> Self { loop { } } }
impl<'a> LateLintPass<'a> for TriviallyCopyPassByRef { }

struct Session { target: Config }
struct Registry<'a> { sess: &'a Session }
impl<'a> Registry<'a> {
    fn register_late_lint_pass(&mut self, _: LateLintPassObject) { loop { } }
}

fn register_plugins(reg: &mut Registry<'_>) {
    reg.register_late_lint_pass(Box::new(TriviallyCopyPassByRef::new(&reg.sess.target)));
}

fn main() { }

@pnkfelix
Copy link
Member Author

pnkfelix commented Sep 3, 2018

Now, having done the minimization, there are two cases to consider:

  1. The self.hash_expr(&self.cx.body(..).value) invocations on lines 20 and 23,
  2. The reg.register_late_lint_pass(Box::new(Thing::new(&reg.sess.target))) invocation on line 45

I think niko has already expressed a little concern about case 1 in a comment here on the clippy repo.

(I want to look a little more carefully at both cases before I add more notes...)

@pnkfelix
Copy link
Member Author

pnkfelix commented Sep 3, 2018

Result of investigating case 1: The reason that the call to self.hash_expr is allowed there, AFAICT, is that self has an immutable borrow somewhere along the path from it to the AST map it borrows, and therefore a &mut self still cannot do anything to interfere with the result of the &self.cx.body(..).value.

For example, this works: https://play.rust-lang.org/?gist=bd9009010499a92e0cfb9985d7bf1330&version=nightly&mode=debug&edition=2018

But if I change the latter to mutably borrow the AST map from SpanlessHash, it stops working: https://play.rust-lang.org/?gist=b3e05fdc13eff13093541487df2bfa06&version=nightly&mode=debug&edition=2018

This is enough to convince me that we aren't seeing evidence of unsoundness from case 1 in my previous comment.

(I'm going to dive into case 2 next.)

@pnkfelix
Copy link
Member Author

pnkfelix commented Sep 3, 2018

I think case 2 is another instance of the same phenomenon: the Registry has only an immutable reference to the Session, and so the reg.register_late_lint_pass does not interfere with the &reg.sess.target borrow.

@pnkfelix
Copy link
Member Author

pnkfelix commented Sep 3, 2018

Hmm I may have spoken too soon for case 2; unlike the previous analysis, changing to struct Registry<'a> { sess: &'a mut Session } does not cause the compiler to start rejecting the example. Investigating now.

@pnkfelix
Copy link
Member Author

pnkfelix commented Sep 3, 2018

Oh, I think this explains it: in both the original source and in my minimization, struct TriviallyCopyPassByRef carries no references (and has no lifetimes on its type). That might explain why changing the registry to have a &mut Session did not cause any errors to trigger.

But if you change to a &mut Session and switch back to the 2015 edition, it will: https://play.rust-lang.org/?gist=30fae44818a42cd3f931493180199743&version=nightly&mode=debug&edition=2015

@pnkfelix
Copy link
Member Author

pnkfelix commented Sep 4, 2018

One might reasonably wonder: "Wait, what do those examples (case 1 and 2 from my above comment) have to do with two-phase borrows."

I think the best answer to might be for me to just add two tests using revisions to illustrate the cases here, with a revision set like { borrowck=ast, borrowck=mir, borrowck=mir+two-phase-borrows }, and then that should show how the errors arise when you leave out support for two-phase-borrows.

@pnkfelix
Copy link
Member Author

pnkfelix commented Sep 4, 2018

(I have a test written up, will post a PR soon.)

kennytm added a commit to kennytm/rust that referenced this issue Sep 7, 2018
…y-test, r=wesleywiser

Minimized clippy test from when NLL disabled two-phase borrows

(Of course anyone even skimming the test will realize that this is an *expansion* of a previously posted [minimization](rust-lang#52934 (comment)).)

Fix rust-lang#52934.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-NLL Area: Non-lexical lifetimes (NLL) E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. NLL-complete Working towards the "valid code works" goal 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

3 participants