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

Don't allow Fn unboxed closures to mutate upvars #17784

Merged
merged 4 commits into from
Oct 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/librustc/middle/borrowck/check_loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,12 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
check_for_aliasability_violation(this, span, b.clone());
}

mc::cat_copied_upvar(mc::CopiedUpvar {
kind: mc::Unboxed(ty::FnUnboxedClosureKind), ..}) => {
// Prohibit writes to capture-by-move upvars in non-once closures
check_for_aliasability_violation(this, span, guarantor.clone());
}

_ => {}
}

Expand Down
13 changes: 6 additions & 7 deletions src/librustc/middle/borrowck/gather_loans/gather_moves.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,15 @@ fn check_and_get_illegal_move_origin(bccx: &BorrowckCtxt,
mc::cat_deref(_, _, mc::BorrowedPtr(..)) |
mc::cat_deref(_, _, mc::Implicit(..)) |
mc::cat_deref(_, _, mc::UnsafePtr(..)) |
mc::cat_upvar(..) | mc::cat_static_item |
mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Many, .. }) => {
mc::cat_upvar(..) | mc::cat_static_item => {
Some(cmt.clone())
}

// Can move out of captured upvars only if the destination closure
// type is 'once'. 1-shot stack closures emit the copied_upvar form
// (see mem_categorization.rs).
mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Once, .. }) => {
None
mc::cat_copied_upvar(mc::CopiedUpvar { kind: kind, .. }) => {
match kind.onceness() {
ast::Once => None,
ast::Many => Some(cmt.clone())
}
}

mc::cat_rvalue(..) |
Expand Down
11 changes: 9 additions & 2 deletions src/librustc/middle/borrowck/gather_loans/move_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,15 @@ fn report_cannot_move_out_of(bccx: &BorrowckCtxt, move_from: mc::cmt) {
mc::cat_deref(_, _, mc::BorrowedPtr(..)) |
mc::cat_deref(_, _, mc::Implicit(..)) |
mc::cat_deref(_, _, mc::UnsafePtr(..)) |
mc::cat_upvar(..) | mc::cat_static_item |
mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Many, .. }) => {
mc::cat_upvar(..) | mc::cat_static_item => {
bccx.span_err(
move_from.span,
format!("cannot move out of {}",
bccx.cmt_to_string(&*move_from)).as_slice());
}

mc::cat_copied_upvar(mc::CopiedUpvar { kind: kind, .. })
if kind.onceness() == ast::Many => {
bccx.span_err(
move_from.span,
format!("cannot move out of {}",
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/middle/borrowck/gather_loans/restrictions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
SafeIf(lp.clone(), vec![lp])
}

mc::cat_upvar(upvar_id, _) => {
mc::cat_upvar(upvar_id, _, _) => {
// R-Variable, captured into closure
let lp = Rc::new(LpUpvar(upvar_id));
SafeIf(lp.clone(), vec![lp])
Expand Down
19 changes: 15 additions & 4 deletions src/librustc/middle/borrowck/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,18 +354,22 @@ pub fn opt_loan_path(cmt: &mc::cmt) -> Option<Rc<LoanPath>> {

match cmt.cat {
mc::cat_rvalue(..) |
mc::cat_static_item |
mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Many, .. }) => {
mc::cat_static_item => {
None
}

mc::cat_copied_upvar(mc::CopiedUpvar { kind: kind, .. })
if kind.onceness() == ast::Many => {
None
}

mc::cat_local(id) => {
Some(Rc::new(LpVar(id)))
}

mc::cat_upvar(ty::UpvarId {var_id: id, closure_expr_id: proc_id}, _) |
mc::cat_upvar(ty::UpvarId {var_id: id, closure_expr_id: proc_id}, _, _) |
mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: id,
onceness: _,
kind: _,
capturing_proc: proc_id }) => {
let upvar_id = ty::UpvarId{ var_id: id, closure_expr_id: proc_id };
Some(Rc::new(LpUpvar(upvar_id)))
Expand Down Expand Up @@ -724,6 +728,13 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
format!("{} in an aliasable location",
prefix).as_slice());
}
mc::AliasableClosure(id) => {
self.tcx.sess.span_err(span,
format!("{} in a free variable from an \
immutable unboxed closure", prefix).as_slice());
span_note!(self.tcx.sess, self.tcx.map.span(id),
"consider changing this closure to take self by mutable reference");
}
mc::AliasableStatic(..) |
mc::AliasableStaticMut(..) => {
self.tcx.sess.span_err(
Expand Down
60 changes: 43 additions & 17 deletions src/librustc/middle/mem_categorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ pub enum categorization {
cat_rvalue(ty::Region), // temporary val, argument is its scope
cat_static_item,
cat_copied_upvar(CopiedUpvar), // upvar copied into proc env
cat_upvar(ty::UpvarId, ty::UpvarBorrow), // by ref upvar from stack closure
cat_upvar(ty::UpvarId, ty::UpvarBorrow,
Option<ty::UnboxedClosureKind>), // by ref upvar from stack or unboxed closure
cat_local(ast::NodeId), // local variable
cat_deref(cmt, uint, PointerKind), // deref of a ptr
cat_interior(cmt, InteriorKind), // something interior: field, tuple, etc
Expand All @@ -93,10 +94,27 @@ pub enum categorization {
// (*1) downcast is only required if the enum has more than one variant
}

#[deriving(Clone, PartialEq)]
pub enum CopiedUpvarKind {
Boxed(ast::Onceness),
Unboxed(ty::UnboxedClosureKind)
}

impl CopiedUpvarKind {
pub fn onceness(&self) -> ast::Onceness {
match *self {
Boxed(onceness) => onceness,
Unboxed(ty::FnUnboxedClosureKind) |
Unboxed(ty::FnMutUnboxedClosureKind) => ast::Many,
Unboxed(ty::FnOnceUnboxedClosureKind) => ast::Once
}
}
}

#[deriving(Clone, PartialEq)]
pub struct CopiedUpvar {
pub upvar_id: ast::NodeId,
pub onceness: ast::Onceness,
pub kind: CopiedUpvarKind,
pub capturing_proc: ast::NodeId,
}

Expand Down Expand Up @@ -571,14 +589,14 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {

};
if var_is_refd {
self.cat_upvar(id, span, var_id, fn_node_id)
self.cat_upvar(id, span, var_id, fn_node_id, None)
} else {
Ok(Rc::new(cmt_ {
id:id,
span:span,
cat:cat_copied_upvar(CopiedUpvar {
upvar_id: var_id,
onceness: closure_ty.onceness,
kind: Boxed(closure_ty.onceness),
capturing_proc: fn_node_id,
}),
mutbl: MutabilityCategory::from_local(self.tcx(), var_id),
Expand All @@ -591,20 +609,15 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
.unboxed_closures()
.borrow();
let kind = unboxed_closures.get(&closure_id).kind;
let onceness = match kind {
ty::FnUnboxedClosureKind |
ty::FnMutUnboxedClosureKind => ast::Many,
ty::FnOnceUnboxedClosureKind => ast::Once,
};
if self.typer.capture_mode(fn_node_id) == ast::CaptureByRef {
self.cat_upvar(id, span, var_id, fn_node_id)
self.cat_upvar(id, span, var_id, fn_node_id, Some(kind))
} else {
Ok(Rc::new(cmt_ {
id: id,
span: span,
cat: cat_copied_upvar(CopiedUpvar {
upvar_id: var_id,
onceness: onceness,
kind: Unboxed(kind),
capturing_proc: fn_node_id,
}),
mutbl: MutabilityCategory::from_local(self.tcx(), var_id),
Expand Down Expand Up @@ -638,7 +651,8 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
id: ast::NodeId,
span: Span,
var_id: ast::NodeId,
fn_node_id: ast::NodeId)
fn_node_id: ast::NodeId,
kind: Option<ty::UnboxedClosureKind>)
-> McResult<cmt> {
/*!
* Upvars through a closure are in fact indirect
Expand Down Expand Up @@ -666,7 +680,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
let base_cmt = Rc::new(cmt_ {
id:id,
span:span,
cat:cat_upvar(upvar_id, upvar_borrow),
cat:cat_upvar(upvar_id, upvar_borrow, kind),
mutbl:McImmutable,
ty:upvar_ty,
});
Expand Down Expand Up @@ -1233,6 +1247,7 @@ pub enum InteriorSafety {

pub enum AliasableReason {
AliasableBorrowed,
AliasableClosure(ast::NodeId), // Aliasable due to capture by unboxed closure expr
AliasableOther,
AliasableStatic(InteriorSafety),
AliasableStaticMut(InteriorSafety),
Expand Down Expand Up @@ -1287,18 +1302,29 @@ impl cmt_ {
b.freely_aliasable(ctxt)
}

cat_copied_upvar(CopiedUpvar {onceness: ast::Once, ..}) |
cat_rvalue(..) |
cat_local(..) |
cat_upvar(..) |
cat_deref(_, _, UnsafePtr(..)) => { // yes, it's aliasable, but...
None
}

cat_copied_upvar(CopiedUpvar {onceness: ast::Many, ..}) => {
Some(AliasableOther)
cat_copied_upvar(CopiedUpvar {kind: kind, capturing_proc: id, ..}) => {
match kind {
Boxed(ast::Once) |
Unboxed(ty::FnOnceUnboxedClosureKind) |
Unboxed(ty::FnMutUnboxedClosureKind) => None,
Boxed(_) => Some(AliasableOther),
Unboxed(_) => Some(AliasableClosure(id))
}
}

cat_upvar(ty::UpvarId { closure_expr_id: id, .. }, _,
Some(ty::FnUnboxedClosureKind)) => {
Some(AliasableClosure(id))
}

cat_upvar(..) => None,

cat_static_item(..) => {
let int_safe = if ty::type_interior_is_unsafe(ctxt, self.ty) {
InteriorUnsafe
Expand Down
6 changes: 3 additions & 3 deletions src/librustc/middle/typeck/check/regionck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1552,7 +1552,7 @@ fn link_reborrowed_region(rcx: &Rcx,

// Detect references to an upvar `x`:
let cause = match ref_cmt.cat {
mc::cat_upvar(ref upvar_id, _) => {
mc::cat_upvar(ref upvar_id, _, _) => {
let mut upvar_borrow_map =
rcx.fcx.inh.upvar_borrow_map.borrow_mut();
match upvar_borrow_map.find_mut(upvar_id) {
Expand Down Expand Up @@ -1686,7 +1686,7 @@ fn adjust_upvar_borrow_kind_for_mut(rcx: &Rcx,
mc::cat_deref(base, _, mc::BorrowedPtr(..)) |
mc::cat_deref(base, _, mc::Implicit(..)) => {
match base.cat {
mc::cat_upvar(ref upvar_id, _) => {
mc::cat_upvar(ref upvar_id, _, _) => {
// if this is an implicit deref of an
// upvar, then we need to modify the
// borrow_kind of the upvar to make sure it
Expand Down Expand Up @@ -1739,7 +1739,7 @@ fn adjust_upvar_borrow_kind_for_unique(rcx: &Rcx, cmt: mc::cmt) {
mc::cat_deref(base, _, mc::BorrowedPtr(..)) |
mc::cat_deref(base, _, mc::Implicit(..)) => {
match base.cat {
mc::cat_upvar(ref upvar_id, _) => {
mc::cat_upvar(ref upvar_id, _, _) => {
// if this is an implicit deref of an
// upvar, then we need to modify the
// borrow_kind of the upvar to make sure it
Expand Down
50 changes: 50 additions & 0 deletions src/test/compile-fail/issue-17780.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![feature(unboxed_closures, overloaded_calls)]

fn set(x: &mut uint) { *x = 5; }

fn main() {
// By-ref captures
{
let mut x = 0u;
let _f = |&:| x = 42;
//~^ ERROR cannot assign to data in a free
// variable from an immutable unboxed closure

let mut y = 0u;
let _g = |&:| set(&mut y);
//~^ ERROR cannot borrow data mutably in a free
// variable from an immutable unboxed closure

let mut z = 0u;
let _h = |&mut:| { set(&mut z); |&:| z = 42; };
//~^ ERROR cannot assign to data in a
// free variable from an immutable unboxed closure
}
// By-value captures
{
let mut x = 0u;
let _f = move |&:| x = 42;
//~^ ERROR cannot assign to data in a free
// variable from an immutable unboxed closure

let mut y = 0u;
let _g = move |&:| set(&mut y);
//~^ ERROR cannot borrow data mutably in a free
// variable from an immutable unboxed closure

let mut z = 0u;
let _h = move |&mut:| { set(&mut z); move |&:| z = 42; };
//~^ ERROR cannot assign to data in a free
// variable from an immutable unboxed closure
}
}
4 changes: 2 additions & 2 deletions src/test/run-pass/unboxed-closures-by-ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ fn main() {
let mut x = 0u;
let y = 2u;

call_fn(|&:| x += y);
call_fn(|&:| assert_eq!(x, 0));
call_fn_mut(|&mut:| x += y);
call_fn_once(|:| x += y);
assert_eq!(x, y * 3);
assert_eq!(x, y * 2);
}