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

MIR-borrowck: immutable unique closure upvars can be mutated #46023

Closed
arielb1 opened this issue Nov 15, 2017 · 6 comments
Closed

MIR-borrowck: immutable unique closure upvars can be mutated #46023

arielb1 opened this issue Nov 15, 2017 · 6 comments
Labels
A-borrow-checker Area: The borrow checker E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
Milestone

Comments

@arielb1
Copy link
Contributor

arielb1 commented Nov 15, 2017

MIR borrowck allows for immutable unique closure upvars to be mutated, e.g.

fn main() {
    let x = 0;

    (move || {
        x = 1; // mutating immutable local, ok on MIR, bad on AST
    })()
}
@arielb1 arielb1 added A-borrow-checker Area: The borrow checker E-needs-mentor labels Nov 15, 2017
@arielb1 arielb1 added this to the NLL prototype milestone Nov 15, 2017
@nikomatsakis
Copy link
Contributor

There are two ways to fix this. We can update the TyRef variant in TypeVariants to include a 3-way mutability -- this is the Right Fix, no question. Or we can hack is_mutable to understand that a deref of a &mut which comes from an upvar of unique kind is not mutable. That is the "contained fix", but I worry about it.

@nikomatsakis nikomatsakis added the I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness label Nov 16, 2017
@nikomatsakis
Copy link
Contributor

nikomatsakis commented Nov 17, 2017

UPDATE: You can probably ignore these instructions. I think we will use the simpler approach described below for now.

So for more context:

The problem here is that the closure, when it captures x, is actually using an &uniq borrow (explanation as to why). This is something that is only used in closure desugaring. However, our type that represents references only includes two options, & or &mut:

rust/src/librustc/ty/sty.rs

Lines 122 to 124 in aabfed5

/// A reference; a pointer with an associated lifetime. Written as
/// `&'a mut T` or `&'a T`.
TyRef(Region<'tcx>, TypeAndMut<'tcx>),

You'll note that the actual & operator in MIR supports three mutabilities:

rust/src/librustc/mir/mod.rs

Lines 1326 to 1327 in aabfed5

/// &x or &mut x
Ref(Region<'tcx>, BorrowKind, Lvalue<'tcx>),

Where BorrowKind is defined here, and in particular has three variants.

If we want to do this the right way, then the first thing we should do I think is to refactor TyRef to use BorrowKind instead of TypeAndMut. I would do this in two steps, I suspect:

  1. Refactor TyRef(Region, TypeAndMut) into TyRef(Region, Type, Mutability).
  2. Refactor TyRef(Region, Type, Mutability) to replace Mutability with BorrowKind.

Once we get that done, we can continue.

@arielb1
Copy link
Contributor Author

arielb1 commented Nov 18, 2017

@nikomatsakis

Having another kind of "semi-secret" reference in the type-system that is used almost nowhere scares me - e.g., upvar OIBITs can be implemented for &T and &mut T, missing &uniq T.

I would prefer a spot change to permission checking - at least error reporting already has to detect dereferences of upvars, so it can check whether an upvar is supposed to be immutable, and in that case treat it as immutable.

@nikomatsakis
Copy link
Contributor

@arielb1 I'm of two minds. On the one hand, I think that trying to make the type system more honest would reveal a lot of interesting things, and I still think it's something we ought to do at some point, precisely because it will reveal interactions like the OIBIT one that you mentioned, which I think are important for us to understand and work through. However, now may not be that time.

That said, certainly, a spot change will be easier. =) If we want to do that, this is what I think we have to do:

We need to extend the UpvarDecl struct to record whether this was a &uniq borrow or what. I don't see that this information is readily available now. I would change the by_ref: bool field to by_ref: Option<BorrowKind>, with a comment that None means by-value.

Then we have to alter MIR construction to update the field for its new type. This is the code in question:

let by_ref = match capture {
ty::UpvarCapture::ByValue => false,
ty::UpvarCapture::ByRef(..) => true
};
let mut decl = UpvarDecl {
debug_name: keywords::Invalid.name(),
by_ref,
};

It should be fairly easy to extract the mode from there, it's found in the UpvarCapture data (along with some other stuff we don't want to put in the MIR, which is why we have a new type).

Finally, we have to modify the is_mutable function in the borrow checker (the "permissions check"). Currently, for a deref like *x, it examines only the type of x:

ty::TyRef(_, tnm) => {
match tnm.mutbl {
// Shared borrowed data is never mutable
hir::MutImmutable => Err(lvalue),
// Mutably borrowed data is mutable, but only if we have a
// unique path to the `&mut`
hir::MutMutable => self.is_unique(&proj.base),
}
},

It needs to also have a check whether this is the field of an upvar. The helper function introduced in #46087 would be most useful here, so maybe we wait for that to land or else build on it. Then you could do is_upvar_field_projection(&proj.base) and -- if so -- check the upvar decl (self.mir.upvar_decls[var_index].by_ref) to get the reference mode.

@arielb1
Copy link
Contributor Author

arielb1 commented Nov 19, 2017

@nikomatsakis

My model used to be that immutable locals are basically a lint - i.e. we already know they have no soundness implication, instead we just check them for the user's convenience, and for that we do not need to add another type. A closure captures an immutable local by &mut - it's just that the lint can "see through" closure captures (while the lint can't "see through" normal captures into structs).

I just think that adding a new kind of reference to the type-system itself would raise questions, and if we are actually adding new kinds of references, it's for the better if they are user-visible instead of being a hidden compiler feature.

@davidtwco
Copy link
Member

I've started work on this locally, will open a WIP PR when I've got something.

@arielb1 arielb1 added E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. and removed E-needs-mentor labels Nov 25, 2017
bors added a commit that referenced this issue Dec 1, 2017
MIR-borrowck: immutable unique closure upvars can be mutated

Fixes #46023 and #46160 (see [this comment](#46236 (comment))).
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 E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
Projects
None yet
Development

No branches or pull requests

3 participants