Skip to content

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

Closed

Description

@pnkfelix

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

Activity

  1. changed the title [-]NLL: régressions due to weakness in two-phase borrows [/-] [+]NLL: regressions due to weakness in two-phase borrows [/+] on Aug 1, 2018
  2. pnkfelix commented on Aug 3, 2018

    @pnkfelix
    ContributorAuthor

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

  3. nikomatsakis commented on Aug 21, 2018

    @nikomatsakis
    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...

  4. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Aug 21, 2018
  5. nikomatsakis commented on Aug 21, 2018

    @nikomatsakis
    Contributor

    Leaving untriaged for now.

  6. added this to the Rust 2018 RC milestone on Aug 27, 2018
  7. nikomatsakis commented on Aug 27, 2018

    @nikomatsakis
    Contributor

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

  8. nikomatsakis commented on Aug 27, 2018

    @nikomatsakis
    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 :)

  9. added
    E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.
    on Aug 27, 2018
  10. 4 remaining items

  11. self-assigned this
    on Aug 28, 2018
  12. pnkfelix commented on Sep 3, 2018

    @pnkfelix
    ContributorAuthor

    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() { }
  13. pnkfelix commented on Sep 3, 2018

    @pnkfelix
    ContributorAuthor

    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...)

  14. pnkfelix commented on Sep 3, 2018

    @pnkfelix
    ContributorAuthor

    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.)

  15. pnkfelix commented on Sep 3, 2018

    @pnkfelix
    ContributorAuthor

    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.

  16. pnkfelix commented on Sep 3, 2018

    @pnkfelix
    ContributorAuthor

    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.

  17. pnkfelix commented on Sep 3, 2018

    @pnkfelix
    ContributorAuthor

    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

  18. pnkfelix commented on Sep 4, 2018

    @pnkfelix
    ContributorAuthor

    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.

  19. pnkfelix commented on Sep 4, 2018

    @pnkfelix
    ContributorAuthor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-NLLArea: Non-lexical lifetimes (NLL)E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.NLL-completeWorking towards the "valid code works" goalT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions