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 #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]
  • 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
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
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
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
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
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

5 comments on commit b2eb888

@bors
Copy link
Contributor

@bors bors commented on b2eb888 Jul 29, 2014

Choose a reason for hiding this comment

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

saw approval from pnkfelix
at pcwalton@b2eb888

@bors
Copy link
Contributor

@bors bors commented on b2eb888 Jul 29, 2014

Choose a reason for hiding this comment

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

merging pcwalton/rust/borrowck-pattern-guards = b2eb888 into auto

@bors
Copy link
Contributor

@bors bors commented on b2eb888 Jul 29, 2014

Choose a reason for hiding this comment

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

pcwalton/rust/borrowck-pattern-guards = b2eb888 merged ok, testing candidate = 6635fe7

@bors
Copy link
Contributor

@bors bors commented on b2eb888 Jul 29, 2014

Choose a reason for hiding this comment

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

fast-forwarding master to auto = 6635fe7

Please sign in to comment.