Skip to content

Commit

Permalink
librustc: Disallow mutation and assignment in pattern guards, and modify
Browse files Browse the repository at this point in the history
the CFG for match statements.

There were two bugs in issue rust-lang#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 rust-lang#14684.

[breaking-change]
  • Loading branch information
pcwalton committed Jul 25, 2014
1 parent 66a0b52 commit b2eb888
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 271 deletions.
9 changes: 7 additions & 2 deletions src/libfmt_macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,13 @@ impl<'a> Parser<'a> {
None => {
let tmp = self.cur.clone();
match self.word() {
word if word.len() > 0 && self.consume('$') => {
CountIsName(word)
word if word.len() > 0 => {
if self.consume('$') {
CountIsName(word)
} else {
self.cur = tmp;
CountImplied
}
}
_ => {
self.cur = tmp;
Expand Down
41 changes: 23 additions & 18 deletions src/librustc/middle/cfg/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,32 +333,37 @@ impl<'a> CFGBuilder<'a> {
// [discr]
// |
// v 2
// [guard1]
// [cond1]
// / \
// | \
// v 3 |
// [pat1] |
// |
// v 4 |
// [body1] v
// | [guard2]
// | / \
// | [body2] \
// | | ...
// | | |
// v 5 v v
// [....expr....]
// v 3 \
// [pat1] \
// | |
// v 4 |
// [guard1] |
// | |
// | |
// v 5 v
// [body1] [cond2]
// | / \
// | ... ...
// | | |
// v 6 v v
// [.....expr.....]
//
let discr_exit = self.expr(discr.clone(), pred); // 1

let expr_exit = self.add_node(expr.id, []);
let mut guard_exit = discr_exit;
let mut cond_exit = discr_exit;
for arm in arms.iter() {
guard_exit = self.opt_expr(arm.guard, guard_exit); // 2
cond_exit = self.add_dummy_node([cond_exit]); // 2
let pats_exit = self.pats_any(arm.pats.as_slice(),
guard_exit); // 3
let body_exit = self.expr(arm.body.clone(), pats_exit); // 4
self.add_contained_edge(body_exit, expr_exit); // 5
cond_exit); // 3
let guard_exit = self.opt_expr(arm.guard,
pats_exit); // 4
let body_exit = self.expr(arm.body.clone(),
guard_exit); // 5
self.add_contained_edge(body_exit, expr_exit); // 6
}
expr_exit
}
Expand Down
65 changes: 64 additions & 1 deletion src/librustc/middle/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
use middle::const_eval::{compare_const_vals, const_bool, const_float, const_nil, const_val};
use middle::const_eval::{const_expr_to_pat, eval_const_expr, lookup_const_by_id};
use middle::def::*;
use middle::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Init};
use middle::expr_use_visitor::{JustWrite, LoanCause, MutateMode};
use middle::expr_use_visitor::{WriteAndRead};
use middle::mem_categorization::cmt;
use middle::pat_util::*;
use middle::ty::*;
use middle::ty;
Expand Down Expand Up @@ -143,7 +147,16 @@ fn check_expr(cx: &mut MatchCheckCtxt, ex: &Expr) {
arm.pats.as_slice());
}

// Second, check for unreachable arms.
// Second, if there is a guard on each arm, make sure it isn't
// assigning or borrowing anything mutably.
for arm in arms.iter() {
match arm.guard {
Some(guard) => check_for_mutation_in_guard(cx, &*guard),
None => {}
}
}

// Third, check for unreachable arms.
check_arms(cx, arms.as_slice());

// Finally, check if the whole match expression is exhaustive.
Expand Down Expand Up @@ -903,3 +916,53 @@ fn check_legality_of_move_bindings(cx: &MatchCheckCtxt,
});
}
}

/// Ensures that a pattern guard doesn't borrow by mutable reference or
/// assign.
fn check_for_mutation_in_guard<'a>(cx: &'a MatchCheckCtxt<'a>, guard: &Expr) {
let mut checker = MutationChecker {
cx: cx,
};
let mut visitor = ExprUseVisitor::new(&mut checker, checker.cx.tcx);
visitor.walk_expr(guard);
}

struct MutationChecker<'a> {
cx: &'a MatchCheckCtxt<'a>,
}

impl<'a> Delegate for MutationChecker<'a> {
fn consume(&mut self, _: NodeId, _: Span, _: cmt, _: ConsumeMode) {}
fn consume_pat(&mut self, _: &Pat, _: cmt, _: ConsumeMode) {}
fn borrow(&mut self,
_: NodeId,
span: Span,
_: cmt,
_: Region,
kind: BorrowKind,
_: LoanCause) {
match kind {
MutBorrow => {
self.cx
.tcx
.sess
.span_err(span,
"cannot mutably borrow in a pattern guard")
}
ImmBorrow | UniqueImmBorrow => {}
}
}
fn decl_without_init(&mut self, _: NodeId, _: Span) {}
fn mutate(&mut self, _: NodeId, span: Span, _: cmt, mode: MutateMode) {
match mode {
JustWrite | WriteAndRead => {
self.cx
.tcx
.sess
.span_err(span, "cannot assign in a pattern guard")
}
Init => {}
}
}
}

2 changes: 1 addition & 1 deletion src/librustc/middle/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ impl<'d,'t,TYPER:mc::Typer> ExprUseVisitor<'d,'t,TYPER> {
self.walk_expr(expr)
}

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

self.walk_adjustment(expr);
Expand Down
22 changes: 12 additions & 10 deletions src/librustdoc/html/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1730,17 +1730,19 @@ fn item_struct(w: &mut fmt::Formatter, it: &clean::Item,
}
}).peekable();
match s.struct_type {
doctree::Plain if fields.peek().is_some() => {
try!(write!(w, "<h2 class='fields'>Fields</h2>\n<table>"));
for field in fields {
try!(write!(w, "<tr><td id='structfield.{name}'>\
{stab}<code>{name}</code></td><td>",
stab = ConciseStability(&field.stability),
name = field.name.get_ref().as_slice()));
try!(document(w, field));
try!(write!(w, "</td></tr>"));
doctree::Plain => {
if fields.peek().is_some() {
try!(write!(w, "<h2 class='fields'>Fields</h2>\n<table>"));
for field in fields {
try!(write!(w, "<tr><td id='structfield.{name}'>\
{stab}<code>{name}</code></td><td>",
stab = ConciseStability(&field.stability),
name = field.name.get_ref().as_slice()));
try!(document(w, field));
try!(write!(w, "</td></tr>"));
}
try!(write!(w, "</table>"));
}
try!(write!(w, "</table>"));
}
_ => {}
}
Expand Down
Loading

1 comment on commit b2eb888

@pnkfelix
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r+

Please sign in to comment.