Skip to content

Commit 1b46b00

Browse files
committed
auto merge of #17784 : bkoropoff/rust/issue-17780, r=pcwalton
This fixes a soundness problem where `Fn` unboxed closures can mutate free variables in the environment. The following presently builds: ```rust #![feature(unboxed_closures, overloaded_calls)] fn main() { let mut x = 0u; let _f = |&:| x = 42; } ``` However, this is equivalent to writing the following, which borrowck rightly rejects: ```rust struct F<'a> { x: &'a mut uint } impl<'a> Fn<(),()> for F<'a> { #[rust_call_abi_hack] fn call(&self, _: ()) { *self.x = 42; // error: cannot assign to data in a `&` reference } } fn main() { let mut x = 0u; let _f = F { x: &mut x }; } ``` This problem is unique to unboxed closures; boxed closures cannot be invoked through an immutable reference and are not subject to it. This change marks upvars of `Fn` unboxed closures as freely aliasable in mem_categorization, which causes borrowck to reject attempts to mutate or mutably borrow them. @zwarich pointed out that even with this change, there are remaining soundness issues related to regionck (issue #17403). This region issue affects boxed closures as well. Closes issue #17780
2 parents 8f96590 + 4d2ff43 commit 1b46b00

File tree

9 files changed

+135
-36
lines changed

9 files changed

+135
-36
lines changed

src/librustc/middle/borrowck/check_loans.rs

+6
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,12 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
854854
check_for_aliasability_violation(this, span, b.clone());
855855
}
856856

857+
mc::cat_copied_upvar(mc::CopiedUpvar {
858+
kind: mc::Unboxed(ty::FnUnboxedClosureKind), ..}) => {
859+
// Prohibit writes to capture-by-move upvars in non-once closures
860+
check_for_aliasability_violation(this, span, guarantor.clone());
861+
}
862+
857863
_ => {}
858864
}
859865

src/librustc/middle/borrowck/gather_loans/gather_moves.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,15 @@ fn check_and_get_illegal_move_origin(bccx: &BorrowckCtxt,
133133
mc::cat_deref(_, _, mc::BorrowedPtr(..)) |
134134
mc::cat_deref(_, _, mc::Implicit(..)) |
135135
mc::cat_deref(_, _, mc::UnsafePtr(..)) |
136-
mc::cat_upvar(..) | mc::cat_static_item |
137-
mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Many, .. }) => {
136+
mc::cat_upvar(..) | mc::cat_static_item => {
138137
Some(cmt.clone())
139138
}
140139

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

148147
mc::cat_rvalue(..) |

src/librustc/middle/borrowck/gather_loans/move_error.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,15 @@ fn report_cannot_move_out_of(bccx: &BorrowckCtxt, move_from: mc::cmt) {
115115
mc::cat_deref(_, _, mc::BorrowedPtr(..)) |
116116
mc::cat_deref(_, _, mc::Implicit(..)) |
117117
mc::cat_deref(_, _, mc::UnsafePtr(..)) |
118-
mc::cat_upvar(..) | mc::cat_static_item |
119-
mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Many, .. }) => {
118+
mc::cat_upvar(..) | mc::cat_static_item => {
119+
bccx.span_err(
120+
move_from.span,
121+
format!("cannot move out of {}",
122+
bccx.cmt_to_string(&*move_from)).as_slice());
123+
}
124+
125+
mc::cat_copied_upvar(mc::CopiedUpvar { kind: kind, .. })
126+
if kind.onceness() == ast::Many => {
120127
bccx.span_err(
121128
move_from.span,
122129
format!("cannot move out of {}",

src/librustc/middle/borrowck/gather_loans/restrictions.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
7272
SafeIf(lp.clone(), vec![lp])
7373
}
7474

75-
mc::cat_upvar(upvar_id, _) => {
75+
mc::cat_upvar(upvar_id, _, _) => {
7676
// R-Variable, captured into closure
7777
let lp = Rc::new(LpUpvar(upvar_id));
7878
SafeIf(lp.clone(), vec![lp])

src/librustc/middle/borrowck/mod.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -354,18 +354,22 @@ pub fn opt_loan_path(cmt: &mc::cmt) -> Option<Rc<LoanPath>> {
354354
355355
match cmt.cat {
356356
mc::cat_rvalue(..) |
357-
mc::cat_static_item |
358-
mc::cat_copied_upvar(mc::CopiedUpvar { onceness: ast::Many, .. }) => {
357+
mc::cat_static_item => {
358+
None
359+
}
360+
361+
mc::cat_copied_upvar(mc::CopiedUpvar { kind: kind, .. })
362+
if kind.onceness() == ast::Many => {
359363
None
360364
}
361365

362366
mc::cat_local(id) => {
363367
Some(Rc::new(LpVar(id)))
364368
}
365369

366-
mc::cat_upvar(ty::UpvarId {var_id: id, closure_expr_id: proc_id}, _) |
370+
mc::cat_upvar(ty::UpvarId {var_id: id, closure_expr_id: proc_id}, _, _) |
367371
mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: id,
368-
onceness: _,
372+
kind: _,
369373
capturing_proc: proc_id }) => {
370374
let upvar_id = ty::UpvarId{ var_id: id, closure_expr_id: proc_id };
371375
Some(Rc::new(LpUpvar(upvar_id)))
@@ -724,6 +728,13 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
724728
format!("{} in an aliasable location",
725729
prefix).as_slice());
726730
}
731+
mc::AliasableClosure(id) => {
732+
self.tcx.sess.span_err(span,
733+
format!("{} in a free variable from an \
734+
immutable unboxed closure", prefix).as_slice());
735+
span_note!(self.tcx.sess, self.tcx.map.span(id),
736+
"consider changing this closure to take self by mutable reference");
737+
}
727738
mc::AliasableStatic(..) |
728739
mc::AliasableStaticMut(..) => {
729740
self.tcx.sess.span_err(

src/librustc/middle/mem_categorization.rs

+43-17
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ pub enum categorization {
8383
cat_rvalue(ty::Region), // temporary val, argument is its scope
8484
cat_static_item,
8585
cat_copied_upvar(CopiedUpvar), // upvar copied into proc env
86-
cat_upvar(ty::UpvarId, ty::UpvarBorrow), // by ref upvar from stack closure
86+
cat_upvar(ty::UpvarId, ty::UpvarBorrow,
87+
Option<ty::UnboxedClosureKind>), // by ref upvar from stack or unboxed closure
8788
cat_local(ast::NodeId), // local variable
8889
cat_deref(cmt, uint, PointerKind), // deref of a ptr
8990
cat_interior(cmt, InteriorKind), // something interior: field, tuple, etc
@@ -93,10 +94,27 @@ pub enum categorization {
9394
// (*1) downcast is only required if the enum has more than one variant
9495
}
9596

97+
#[deriving(Clone, PartialEq)]
98+
pub enum CopiedUpvarKind {
99+
Boxed(ast::Onceness),
100+
Unboxed(ty::UnboxedClosureKind)
101+
}
102+
103+
impl CopiedUpvarKind {
104+
pub fn onceness(&self) -> ast::Onceness {
105+
match *self {
106+
Boxed(onceness) => onceness,
107+
Unboxed(ty::FnUnboxedClosureKind) |
108+
Unboxed(ty::FnMutUnboxedClosureKind) => ast::Many,
109+
Unboxed(ty::FnOnceUnboxedClosureKind) => ast::Once
110+
}
111+
}
112+
}
113+
96114
#[deriving(Clone, PartialEq)]
97115
pub struct CopiedUpvar {
98116
pub upvar_id: ast::NodeId,
99-
pub onceness: ast::Onceness,
117+
pub kind: CopiedUpvarKind,
100118
pub capturing_proc: ast::NodeId,
101119
}
102120

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

572590
};
573591
if var_is_refd {
574-
self.cat_upvar(id, span, var_id, fn_node_id)
592+
self.cat_upvar(id, span, var_id, fn_node_id, None)
575593
} else {
576594
Ok(Rc::new(cmt_ {
577595
id:id,
578596
span:span,
579597
cat:cat_copied_upvar(CopiedUpvar {
580598
upvar_id: var_id,
581-
onceness: closure_ty.onceness,
599+
kind: Boxed(closure_ty.onceness),
582600
capturing_proc: fn_node_id,
583601
}),
584602
mutbl: MutabilityCategory::from_local(self.tcx(), var_id),
@@ -591,20 +609,15 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
591609
.unboxed_closures()
592610
.borrow();
593611
let kind = unboxed_closures.get(&closure_id).kind;
594-
let onceness = match kind {
595-
ty::FnUnboxedClosureKind |
596-
ty::FnMutUnboxedClosureKind => ast::Many,
597-
ty::FnOnceUnboxedClosureKind => ast::Once,
598-
};
599612
if self.typer.capture_mode(fn_node_id) == ast::CaptureByRef {
600-
self.cat_upvar(id, span, var_id, fn_node_id)
613+
self.cat_upvar(id, span, var_id, fn_node_id, Some(kind))
601614
} else {
602615
Ok(Rc::new(cmt_ {
603616
id: id,
604617
span: span,
605618
cat: cat_copied_upvar(CopiedUpvar {
606619
upvar_id: var_id,
607-
onceness: onceness,
620+
kind: Unboxed(kind),
608621
capturing_proc: fn_node_id,
609622
}),
610623
mutbl: MutabilityCategory::from_local(self.tcx(), var_id),
@@ -638,7 +651,8 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
638651
id: ast::NodeId,
639652
span: Span,
640653
var_id: ast::NodeId,
641-
fn_node_id: ast::NodeId)
654+
fn_node_id: ast::NodeId,
655+
kind: Option<ty::UnboxedClosureKind>)
642656
-> McResult<cmt> {
643657
/*!
644658
* Upvars through a closure are in fact indirect
@@ -666,7 +680,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> {
666680
let base_cmt = Rc::new(cmt_ {
667681
id:id,
668682
span:span,
669-
cat:cat_upvar(upvar_id, upvar_borrow),
683+
cat:cat_upvar(upvar_id, upvar_borrow, kind),
670684
mutbl:McImmutable,
671685
ty:upvar_ty,
672686
});
@@ -1233,6 +1247,7 @@ pub enum InteriorSafety {
12331247

12341248
pub enum AliasableReason {
12351249
AliasableBorrowed,
1250+
AliasableClosure(ast::NodeId), // Aliasable due to capture by unboxed closure expr
12361251
AliasableOther,
12371252
AliasableStatic(InteriorSafety),
12381253
AliasableStaticMut(InteriorSafety),
@@ -1287,18 +1302,29 @@ impl cmt_ {
12871302
b.freely_aliasable(ctxt)
12881303
}
12891304

1290-
cat_copied_upvar(CopiedUpvar {onceness: ast::Once, ..}) |
12911305
cat_rvalue(..) |
12921306
cat_local(..) |
1293-
cat_upvar(..) |
12941307
cat_deref(_, _, UnsafePtr(..)) => { // yes, it's aliasable, but...
12951308
None
12961309
}
12971310

1298-
cat_copied_upvar(CopiedUpvar {onceness: ast::Many, ..}) => {
1299-
Some(AliasableOther)
1311+
cat_copied_upvar(CopiedUpvar {kind: kind, capturing_proc: id, ..}) => {
1312+
match kind {
1313+
Boxed(ast::Once) |
1314+
Unboxed(ty::FnOnceUnboxedClosureKind) |
1315+
Unboxed(ty::FnMutUnboxedClosureKind) => None,
1316+
Boxed(_) => Some(AliasableOther),
1317+
Unboxed(_) => Some(AliasableClosure(id))
1318+
}
1319+
}
1320+
1321+
cat_upvar(ty::UpvarId { closure_expr_id: id, .. }, _,
1322+
Some(ty::FnUnboxedClosureKind)) => {
1323+
Some(AliasableClosure(id))
13001324
}
13011325

1326+
cat_upvar(..) => None,
1327+
13021328
cat_static_item(..) => {
13031329
let int_safe = if ty::type_interior_is_unsafe(ctxt, self.ty) {
13041330
InteriorUnsafe

src/librustc/middle/typeck/check/regionck.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1552,7 +1552,7 @@ fn link_reborrowed_region(rcx: &Rcx,
15521552

15531553
// Detect references to an upvar `x`:
15541554
let cause = match ref_cmt.cat {
1555-
mc::cat_upvar(ref upvar_id, _) => {
1555+
mc::cat_upvar(ref upvar_id, _, _) => {
15561556
let mut upvar_borrow_map =
15571557
rcx.fcx.inh.upvar_borrow_map.borrow_mut();
15581558
match upvar_borrow_map.find_mut(upvar_id) {
@@ -1686,7 +1686,7 @@ fn adjust_upvar_borrow_kind_for_mut(rcx: &Rcx,
16861686
mc::cat_deref(base, _, mc::BorrowedPtr(..)) |
16871687
mc::cat_deref(base, _, mc::Implicit(..)) => {
16881688
match base.cat {
1689-
mc::cat_upvar(ref upvar_id, _) => {
1689+
mc::cat_upvar(ref upvar_id, _, _) => {
16901690
// if this is an implicit deref of an
16911691
// upvar, then we need to modify the
16921692
// borrow_kind of the upvar to make sure it
@@ -1739,7 +1739,7 @@ fn adjust_upvar_borrow_kind_for_unique(rcx: &Rcx, cmt: mc::cmt) {
17391739
mc::cat_deref(base, _, mc::BorrowedPtr(..)) |
17401740
mc::cat_deref(base, _, mc::Implicit(..)) => {
17411741
match base.cat {
1742-
mc::cat_upvar(ref upvar_id, _) => {
1742+
mc::cat_upvar(ref upvar_id, _, _) => {
17431743
// if this is an implicit deref of an
17441744
// upvar, then we need to modify the
17451745
// borrow_kind of the upvar to make sure it

src/test/compile-fail/issue-17780.rs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(unboxed_closures, overloaded_calls)]
12+
13+
fn set(x: &mut uint) { *x = 5; }
14+
15+
fn main() {
16+
// By-ref captures
17+
{
18+
let mut x = 0u;
19+
let _f = |&:| x = 42;
20+
//~^ ERROR cannot assign to data in a free
21+
// variable from an immutable unboxed closure
22+
23+
let mut y = 0u;
24+
let _g = |&:| set(&mut y);
25+
//~^ ERROR cannot borrow data mutably in a free
26+
// variable from an immutable unboxed closure
27+
28+
let mut z = 0u;
29+
let _h = |&mut:| { set(&mut z); |&:| z = 42; };
30+
//~^ ERROR cannot assign to data in a
31+
// free variable from an immutable unboxed closure
32+
}
33+
// By-value captures
34+
{
35+
let mut x = 0u;
36+
let _f = move |&:| x = 42;
37+
//~^ ERROR cannot assign to data in a free
38+
// variable from an immutable unboxed closure
39+
40+
let mut y = 0u;
41+
let _g = move |&:| set(&mut y);
42+
//~^ ERROR cannot borrow data mutably in a free
43+
// variable from an immutable unboxed closure
44+
45+
let mut z = 0u;
46+
let _h = move |&mut:| { set(&mut z); move |&:| z = 42; };
47+
//~^ ERROR cannot assign to data in a free
48+
// variable from an immutable unboxed closure
49+
}
50+
}

src/test/run-pass/unboxed-closures-by-ref.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ fn main() {
2828
let mut x = 0u;
2929
let y = 2u;
3030

31-
call_fn(|&:| x += y);
31+
call_fn(|&:| assert_eq!(x, 0));
3232
call_fn_mut(|&mut:| x += y);
3333
call_fn_once(|:| x += y);
34-
assert_eq!(x, y * 3);
34+
assert_eq!(x, y * 2);
3535
}

0 commit comments

Comments
 (0)