Skip to content

Commit 6635fe7

Browse files
committed
auto merge of #15989 : pcwalton/rust/borrowck-pattern-guards, r=pnkfelix
the CFG for match statements. There were two bugs in issue #14684. One was simply that the borrow check didn't know about the correct CFG for match statements: the pattern must be a predecessor of the guard. This disallows the bad behavior if there are bindings in the pattern. But it isn't enough to prevent the memory safety problem, because of wildcards; thus, this patch introduces a more restrictive rule, which disallows assignments and mutable borrows inside guards outright. I discussed this with Niko and we decided this was the best plan of action. This breaks code that performs mutable borrows in pattern guards. Most commonly, the code looks like this: impl Foo { fn f(&mut self, ...) {} fn g(&mut self, ...) { match bar { Baz if self.f(...) => { ... } _ => { ... } } } } Change this code to not use a guard. For example: impl Foo { fn f(&mut self, ...) {} fn g(&mut self, ...) { match bar { Baz => { if self.f(...) { ... } else { ... } } _ => { ... } } } } Sometimes this can result in code duplication, but often it illustrates a hidden memory safety problem. Closes #14684. [breaking-change] r? @pnkfelix
2 parents b92536f + b2eb888 commit 6635fe7

File tree

10 files changed

+383
-271
lines changed

10 files changed

+383
-271
lines changed

src/libfmt_macros/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,13 @@ impl<'a> Parser<'a> {
352352
None => {
353353
let tmp = self.cur.clone();
354354
match self.word() {
355-
word if word.len() > 0 && self.consume('$') => {
356-
CountIsName(word)
355+
word if word.len() > 0 => {
356+
if self.consume('$') {
357+
CountIsName(word)
358+
} else {
359+
self.cur = tmp;
360+
CountImplied
361+
}
357362
}
358363
_ => {
359364
self.cur = tmp;

src/librustc/middle/cfg/construct.rs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -333,32 +333,37 @@ impl<'a> CFGBuilder<'a> {
333333
// [discr]
334334
// |
335335
// v 2
336-
// [guard1]
336+
// [cond1]
337337
// / \
338338
// | \
339-
// v 3 |
340-
// [pat1] |
341-
// |
342-
// v 4 |
343-
// [body1] v
344-
// | [guard2]
345-
// | / \
346-
// | [body2] \
347-
// | | ...
348-
// | | |
349-
// v 5 v v
350-
// [....expr....]
339+
// v 3 \
340+
// [pat1] \
341+
// | |
342+
// v 4 |
343+
// [guard1] |
344+
// | |
345+
// | |
346+
// v 5 v
347+
// [body1] [cond2]
348+
// | / \
349+
// | ... ...
350+
// | | |
351+
// v 6 v v
352+
// [.....expr.....]
351353
//
352354
let discr_exit = self.expr(discr.clone(), pred); // 1
353355

354356
let expr_exit = self.add_node(expr.id, []);
355-
let mut guard_exit = discr_exit;
357+
let mut cond_exit = discr_exit;
356358
for arm in arms.iter() {
357-
guard_exit = self.opt_expr(arm.guard, guard_exit); // 2
359+
cond_exit = self.add_dummy_node([cond_exit]); // 2
358360
let pats_exit = self.pats_any(arm.pats.as_slice(),
359-
guard_exit); // 3
360-
let body_exit = self.expr(arm.body.clone(), pats_exit); // 4
361-
self.add_contained_edge(body_exit, expr_exit); // 5
361+
cond_exit); // 3
362+
let guard_exit = self.opt_expr(arm.guard,
363+
pats_exit); // 4
364+
let body_exit = self.expr(arm.body.clone(),
365+
guard_exit); // 5
366+
self.add_contained_edge(body_exit, expr_exit); // 6
362367
}
363368
expr_exit
364369
}

src/librustc/middle/check_match.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
use middle::const_eval::{compare_const_vals, const_bool, const_float, const_nil, const_val};
1212
use middle::const_eval::{const_expr_to_pat, eval_const_expr, lookup_const_by_id};
1313
use middle::def::*;
14+
use middle::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Init};
15+
use middle::expr_use_visitor::{JustWrite, LoanCause, MutateMode};
16+
use middle::expr_use_visitor::{WriteAndRead};
17+
use middle::mem_categorization::cmt;
1418
use middle::pat_util::*;
1519
use middle::ty::*;
1620
use middle::ty;
@@ -143,7 +147,16 @@ fn check_expr(cx: &mut MatchCheckCtxt, ex: &Expr) {
143147
arm.pats.as_slice());
144148
}
145149

146-
// Second, check for unreachable arms.
150+
// Second, if there is a guard on each arm, make sure it isn't
151+
// assigning or borrowing anything mutably.
152+
for arm in arms.iter() {
153+
match arm.guard {
154+
Some(guard) => check_for_mutation_in_guard(cx, &*guard),
155+
None => {}
156+
}
157+
}
158+
159+
// Third, check for unreachable arms.
147160
check_arms(cx, arms.as_slice());
148161

149162
// Finally, check if the whole match expression is exhaustive.
@@ -903,3 +916,53 @@ fn check_legality_of_move_bindings(cx: &MatchCheckCtxt,
903916
});
904917
}
905918
}
919+
920+
/// Ensures that a pattern guard doesn't borrow by mutable reference or
921+
/// assign.
922+
fn check_for_mutation_in_guard<'a>(cx: &'a MatchCheckCtxt<'a>, guard: &Expr) {
923+
let mut checker = MutationChecker {
924+
cx: cx,
925+
};
926+
let mut visitor = ExprUseVisitor::new(&mut checker, checker.cx.tcx);
927+
visitor.walk_expr(guard);
928+
}
929+
930+
struct MutationChecker<'a> {
931+
cx: &'a MatchCheckCtxt<'a>,
932+
}
933+
934+
impl<'a> Delegate for MutationChecker<'a> {
935+
fn consume(&mut self, _: NodeId, _: Span, _: cmt, _: ConsumeMode) {}
936+
fn consume_pat(&mut self, _: &Pat, _: cmt, _: ConsumeMode) {}
937+
fn borrow(&mut self,
938+
_: NodeId,
939+
span: Span,
940+
_: cmt,
941+
_: Region,
942+
kind: BorrowKind,
943+
_: LoanCause) {
944+
match kind {
945+
MutBorrow => {
946+
self.cx
947+
.tcx
948+
.sess
949+
.span_err(span,
950+
"cannot mutably borrow in a pattern guard")
951+
}
952+
ImmBorrow | UniqueImmBorrow => {}
953+
}
954+
}
955+
fn decl_without_init(&mut self, _: NodeId, _: Span) {}
956+
fn mutate(&mut self, _: NodeId, span: Span, _: cmt, mode: MutateMode) {
957+
match mode {
958+
JustWrite | WriteAndRead => {
959+
self.cx
960+
.tcx
961+
.sess
962+
.span_err(span, "cannot assign in a pattern guard")
963+
}
964+
Init => {}
965+
}
966+
}
967+
}
968+

src/librustc/middle/expr_use_visitor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl<'d,'t,TYPER:mc::Typer> ExprUseVisitor<'d,'t,TYPER> {
292292
self.walk_expr(expr)
293293
}
294294

295-
fn walk_expr(&mut self, expr: &ast::Expr) {
295+
pub fn walk_expr(&mut self, expr: &ast::Expr) {
296296
debug!("walk_expr(expr={})", expr.repr(self.tcx()));
297297

298298
self.walk_adjustment(expr);

src/librustdoc/html/render.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,17 +1734,19 @@ fn item_struct(w: &mut fmt::Formatter, it: &clean::Item,
17341734
}
17351735
}).peekable();
17361736
match s.struct_type {
1737-
doctree::Plain if fields.peek().is_some() => {
1738-
try!(write!(w, "<h2 class='fields'>Fields</h2>\n<table>"));
1739-
for field in fields {
1740-
try!(write!(w, "<tr><td id='structfield.{name}'>\
1741-
{stab}<code>{name}</code></td><td>",
1742-
stab = ConciseStability(&field.stability),
1743-
name = field.name.get_ref().as_slice()));
1744-
try!(document(w, field));
1745-
try!(write!(w, "</td></tr>"));
1737+
doctree::Plain => {
1738+
if fields.peek().is_some() {
1739+
try!(write!(w, "<h2 class='fields'>Fields</h2>\n<table>"));
1740+
for field in fields {
1741+
try!(write!(w, "<tr><td id='structfield.{name}'>\
1742+
{stab}<code>{name}</code></td><td>",
1743+
stab = ConciseStability(&field.stability),
1744+
name = field.name.get_ref().as_slice()));
1745+
try!(document(w, field));
1746+
try!(write!(w, "</td></tr>"));
1747+
}
1748+
try!(write!(w, "</table>"));
17461749
}
1747-
try!(write!(w, "</table>"));
17481750
}
17491751
_ => {}
17501752
}

0 commit comments

Comments
 (0)