about summary refs log tree commit diff
path: root/src/rustc/middle/alias.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/rustc/middle/alias.rs')
-rw-r--r--src/rustc/middle/alias.rs715
1 files changed, 715 insertions, 0 deletions
diff --git a/src/rustc/middle/alias.rs b/src/rustc/middle/alias.rs
new file mode 100644
index 00000000000..cb0785b4df1
--- /dev/null
+++ b/src/rustc/middle/alias.rs
@@ -0,0 +1,715 @@
+
+import syntax::{ast, ast_util};
+import ast::{ident, fn_ident, node_id};
+import syntax::codemap::span;
+import syntax::visit;
+import visit::vt;
+import std::list;
+import std::util::unreachable;
+import option::is_none;
+import list::list;
+import driver::session::session;
+import pat_util::*;
+import util::ppaux::ty_to_str;
+
+// This is not an alias-analyser (though it would merit from becoming one, or
+// getting input from one, to be more precise). It is a pass that checks
+// whether aliases are used in a safe way.
+
+enum copied { not_allowed, copied, not_copied, }
+enum invalid_reason { overwritten, val_taken, }
+type invalid = {reason: invalid_reason,
+                node_id: node_id,
+                sp: span, path: @ast::path};
+
+enum unsafe_ty { contains(ty::t), mutbl_contains(ty::t), }
+
+type binding = @{node_id: node_id,
+                 span: span,
+                 root_var: option<node_id>,
+                 local_id: uint,
+                 unsafe_tys: [unsafe_ty],
+                 mutable copied: copied};
+
+// FIXME it may be worthwhile to use a linked list of bindings instead
+type scope = {bs: [binding],
+              invalid: @mutable list<@invalid>};
+
+fn mk_binding(cx: ctx, id: node_id, span: span, root_var: option<node_id>,
+              unsafe_tys: [unsafe_ty]) -> binding {
+    alt root_var {
+      some(r_id) { cx.ref_map.insert(id, r_id); }
+      _ {}
+    }
+    ret @{node_id: id, span: span, root_var: root_var,
+          local_id: local_id_of_node(cx, id),
+          unsafe_tys: unsafe_tys,
+          mutable copied: not_copied};
+}
+
+enum local_info { local(uint), }
+
+type copy_map = std::map::hashmap<node_id, ()>;
+type ref_map = std::map::hashmap<node_id, node_id>;
+
+type ctx = {tcx: ty::ctxt,
+            copy_map: copy_map,
+            ref_map: ref_map,
+            mutable silent: bool};
+
+fn check_crate(tcx: ty::ctxt, crate: @ast::crate) -> (copy_map, ref_map) {
+    // Stores information about function arguments that's otherwise not easily
+    // available.
+    let cx = @{tcx: tcx,
+               copy_map: std::map::new_int_hash(),
+               ref_map: std::map::new_int_hash(),
+               mutable silent: false};
+    let v = @{visit_fn: bind visit_fn(cx, _, _, _, _, _, _, _),
+              visit_expr: bind visit_expr(cx, _, _, _),
+              visit_block: bind visit_block(cx, _, _, _)
+              with *visit::default_visitor::<scope>()};
+    let sc = {bs: [], invalid: @mutable list::nil};
+    visit::visit_crate(*crate, sc, visit::mk_vt(v));
+    tcx.sess.abort_if_errors();
+    ret (cx.copy_map, cx.ref_map);
+}
+
+fn visit_fn(cx: @ctx, _fk: visit::fn_kind, decl: ast::fn_decl,
+            body: ast::blk, sp: span,
+            id: ast::node_id, sc: scope, v: vt<scope>) {
+    visit::visit_fn_decl(decl, sc, v);
+    let fty = ty::node_id_to_type(cx.tcx, id);
+    let args = ty::ty_fn_args(fty);
+    for arg in args {
+        alt ty::resolved_mode(cx.tcx, arg.mode) {
+          ast::by_val if ty::type_has_dynamic_size(cx.tcx, arg.ty) {
+            err(*cx, sp, "can not pass a dynamically-sized type by value");
+          }
+          _ { /* fallthrough */ }
+        }
+    }
+
+    // Blocks need to obey any restrictions from the enclosing scope, and may
+    // be called multiple times.
+    let proto = ty::ty_fn_proto(fty);
+    alt proto {
+      ast::proto_block | ast::proto_any {
+        check_loop(*cx, sc) {|| v.visit_block(body, sc, v);}
+      }
+      ast::proto_box | ast::proto_uniq | ast::proto_bare {
+        let sc = {bs: [], invalid: @mutable list::nil};
+        v.visit_block(body, sc, v);
+      }
+    }
+}
+
+fn visit_expr(cx: @ctx, ex: @ast::expr, sc: scope, v: vt<scope>) {
+    let handled = true;
+    alt ex.node {
+      ast::expr_call(f, args, _) {
+        check_call(*cx, sc, f, args);
+        handled = false;
+      }
+      ast::expr_alt(input, arms, _) { check_alt(*cx, input, arms, sc, v); }
+      ast::expr_for(decl, seq, blk) {
+        visit_expr(cx, seq, sc, v);
+        check_loop(*cx, sc) {|| check_for(*cx, decl, seq, blk, sc, v); }
+      }
+      ast::expr_path(pt) {
+        check_var(*cx, ex, pt, ex.id, false, sc);
+        handled = false;
+      }
+      ast::expr_swap(lhs, rhs) {
+        check_lval(cx, lhs, sc, v);
+        check_lval(cx, rhs, sc, v);
+        handled = false;
+      }
+      ast::expr_move(dest, src) {
+        visit_expr(cx, src, sc, v);
+        check_lval(cx, dest, sc, v);
+        check_lval(cx, src, sc, v);
+      }
+      ast::expr_assign(dest, src) | ast::expr_assign_op(_, dest, src) {
+        visit_expr(cx, src, sc, v);
+        check_lval(cx, dest, sc, v);
+      }
+      ast::expr_if(c, then, els) { check_if(c, then, els, sc, v); }
+      ast::expr_while(_, _) | ast::expr_do_while(_, _) {
+        check_loop(*cx, sc) {|| visit::visit_expr(ex, sc, v); }
+      }
+      _ { handled = false; }
+    }
+    if !handled { visit::visit_expr(ex, sc, v); }
+}
+
+fn visit_block(cx: @ctx, b: ast::blk, sc: scope, v: vt<scope>) {
+    let sc = sc;
+    for stmt in b.node.stmts {
+        alt stmt.node {
+          ast::stmt_decl(@{node: ast::decl_item(it), _}, _) {
+            v.visit_item(it, sc, v);
+          }
+          ast::stmt_decl(@{node: ast::decl_local(locs), _}, _) {
+            for loc in locs {
+                alt loc.node.init {
+                  some(init) {
+                    if init.op == ast::init_move {
+                        check_lval(cx, init.expr, sc, v);
+                    } else {
+                        visit_expr(cx, init.expr, sc, v);
+                    }
+                  }
+                  none { }
+                }
+            }
+          }
+          ast::stmt_expr(ex, _) | ast::stmt_semi(ex, _) {
+            v.visit_expr(ex, sc, v);
+          }
+        }
+    }
+    visit::visit_expr_opt(b.node.expr, sc, v);
+}
+
+fn add_bindings_for_let(cx: ctx, &bs: [binding], loc: @ast::local) {
+    alt loc.node.init {
+      some(init) {
+        if init.op == ast::init_move {
+            err(cx, loc.span, "can not move into a by-reference binding");
+        }
+        let root = expr_root(cx, init.expr, false);
+        let root_var = path_def_id(cx, root.ex);
+        if is_none(root_var) {
+            err(cx, loc.span, "a reference binding can't be \
+                               rooted in a temporary");
+        }
+        for proot in pattern_roots(cx.tcx, root.mutbl, loc.node.pat) {
+            let bnd = mk_binding(cx, proot.id, proot.span, root_var,
+                                 unsafe_set(proot.mutbl));
+            // Don't implicitly copy explicit references
+            bnd.copied = not_allowed;
+            bs += [bnd];
+        }
+      }
+      _ {
+        err(cx, loc.span, "by-reference bindings must be initialized");
+      }
+    }
+}
+
+
+fn cant_copy(cx: ctx, b: binding) -> bool {
+    alt b.copied {
+      not_allowed { ret true; }
+      copied { ret false; }
+      not_copied {}
+    }
+    let ty = ty::node_id_to_type(cx.tcx, b.node_id);
+    if ty::type_allows_implicit_copy(cx.tcx, ty) {
+        b.copied = copied;
+        cx.copy_map.insert(b.node_id, ());
+        if copy_is_expensive(cx.tcx, ty) {
+            cx.tcx.sess.span_warn(b.span,
+                                  "inserting an implicit copy for type " +
+                                  util::ppaux::ty_to_str(cx.tcx, ty));
+        }
+        ret false;
+    } else { ret true; }
+}
+
+fn check_call(cx: ctx, sc: scope, f: @ast::expr, args: [@ast::expr])
+    -> [binding] {
+    let fty = ty::expr_ty(cx.tcx, f);
+    let arg_ts = ty::ty_fn_args(fty);
+    let mut_roots: [{arg: uint, node: node_id}] = [];
+    let bindings = [];
+    let i = 0u;
+    for arg_t: ty::arg in arg_ts {
+        let arg = args[i];
+        let root = expr_root(cx, arg, false);
+        alt ty::resolved_mode(cx.tcx, arg_t.mode) {
+          ast::by_mutbl_ref {
+            alt path_def(cx, arg) {
+              some(def) {
+                let dnum = ast_util::def_id_of_def(def).node;
+                mut_roots += [{arg: i, node: dnum}];
+              }
+              _ { }
+            }
+          }
+          ast::by_ref | ast::by_val | ast::by_move | ast::by_copy { }
+        }
+        let root_var = path_def_id(cx, root.ex);
+        let arg_copied = alt ty::resolved_mode(cx.tcx, arg_t.mode) {
+          ast::by_move | ast::by_copy { copied }
+          ast::by_mutbl_ref { not_allowed }
+          ast::by_ref | ast::by_val { not_copied }
+        };
+        bindings += [@{node_id: arg.id,
+                       span: arg.span,
+                       root_var: root_var,
+                       local_id: 0u,
+                       unsafe_tys: unsafe_set(root.mutbl),
+                       mutable copied: arg_copied}];
+        i += 1u;
+    }
+    let f_may_close =
+        alt f.node {
+          ast::expr_path(_) { def_is_local_or_self(cx.tcx.def_map.get(f.id)) }
+          _ { true }
+        };
+    if f_may_close {
+        let i = 0u;
+        for b in bindings {
+            let unsfe = vec::len(b.unsafe_tys) > 0u;
+            alt b.root_var {
+              some(rid) {
+                for o in sc.bs {
+                    if o.node_id == rid && vec::len(o.unsafe_tys) > 0u {
+                        unsfe = true; break;
+                    }
+                }
+              }
+              _ {}
+            }
+            if unsfe && cant_copy(cx, b) {
+                err(cx, f.span, #fmt["function may alias with argument \
+                                     %u, which is not immutably rooted", i]);
+            }
+            i += 1u;
+        }
+    }
+    let j = 0u;
+    for b in bindings {
+        for unsafe_ty in b.unsafe_tys {
+            let i = 0u;
+            for arg_t: ty::arg in arg_ts {
+                let mut_alias =
+                    (ast::by_mutbl_ref == ty::arg_mode(cx.tcx, arg_t));
+                if i != j &&
+                       ty_can_unsafely_include(cx, unsafe_ty, arg_t.ty,
+                                               mut_alias) &&
+                       cant_copy(cx, b) {
+                    err(cx, args[i].span,
+                        #fmt["argument %u may alias with argument %u, \
+                             which is not immutably rooted", i, j]);
+                }
+                i += 1u;
+            }
+        }
+        j += 1u;
+    }
+    // Ensure we're not passing a root by mutable alias.
+
+    for {node: node, arg: arg} in mut_roots {
+        let i = 0u;
+        for b in bindings {
+            if i != arg {
+                alt b.root_var {
+                  some(root) {
+                    if node == root && cant_copy(cx, b) {
+                        err(cx, args[arg].span,
+                            "passing a mutable reference to a \
+                             variable that roots another reference");
+                        break;
+                    }
+                  }
+                  none { }
+                }
+            }
+            i += 1u;
+        }
+    }
+    ret bindings;
+}
+
+fn check_alt(cx: ctx, input: @ast::expr, arms: [ast::arm], sc: scope,
+             v: vt<scope>) {
+    v.visit_expr(input, sc, v);
+    let orig_invalid = *sc.invalid;
+    let all_invalid = orig_invalid;
+    let root = expr_root(cx, input, true);
+    for a: ast::arm in arms {
+        let new_bs = sc.bs;
+        let root_var = path_def_id(cx, root.ex);
+        let pat_id_map = pat_util::pat_id_map(cx.tcx.def_map, a.pats[0]);
+        type info = {
+            id: node_id,
+            mutable unsafe_tys: [unsafe_ty],
+            span: span};
+        let binding_info: [info] = [];
+        for pat in a.pats {
+            for proot in pattern_roots(cx.tcx, root.mutbl, pat) {
+                let canon_id = pat_id_map.get(proot.name);
+                alt vec::find(binding_info, {|x| x.id == canon_id}) {
+                  some(s) { s.unsafe_tys += unsafe_set(proot.mutbl); }
+                  none {
+                      binding_info += [
+                          {id: canon_id,
+                           mutable unsafe_tys: unsafe_set(proot.mutbl),
+                           span: proot.span}];
+                  }
+                }
+            }
+        }
+        for info in binding_info {
+            new_bs += [mk_binding(cx, info.id, info.span, root_var,
+                                  copy info.unsafe_tys)];
+        }
+        *sc.invalid = orig_invalid;
+        visit::visit_arm(a, {bs: new_bs with sc}, v);
+        all_invalid = join_invalid(all_invalid, *sc.invalid);
+    }
+    *sc.invalid = all_invalid;
+}
+
+fn check_for(cx: ctx, local: @ast::local, seq: @ast::expr, blk: ast::blk,
+             sc: scope, v: vt<scope>) {
+    let root = expr_root(cx, seq, false);
+
+    // If this is a mutable vector, don't allow it to be touched.
+    let seq_t = ty::expr_ty(cx.tcx, seq);
+    let cur_mutbl = root.mutbl;
+    alt ty::get(seq_t).struct {
+      ty::ty_vec(mt) {
+        if mt.mutbl != ast::m_imm {
+            cur_mutbl = some(contains(seq_t));
+        }
+      }
+      _ {}
+    }
+    let root_var = path_def_id(cx, root.ex);
+    let new_bs = sc.bs;
+    for proot in pattern_roots(cx.tcx, cur_mutbl, local.node.pat) {
+        new_bs += [mk_binding(cx, proot.id, proot.span, root_var,
+                              unsafe_set(proot.mutbl))];
+    }
+    visit::visit_block(blk, {bs: new_bs with sc}, v);
+}
+
+fn check_var(cx: ctx, ex: @ast::expr, p: @ast::path, id: ast::node_id,
+             assign: bool, sc: scope) {
+    let def = cx.tcx.def_map.get(id);
+    if !def_is_local_or_self(def) { ret; }
+    let my_defnum = ast_util::def_id_of_def(def).node;
+    let my_local_id = local_id_of_node(cx, my_defnum);
+    let var_t = ty::expr_ty(cx.tcx, ex);
+    for b in sc.bs {
+        // excludes variables introduced since the alias was made
+        if my_local_id < b.local_id {
+            for unsafe_ty in b.unsafe_tys {
+                if ty_can_unsafely_include(cx, unsafe_ty, var_t, assign) {
+                    let inv = @{reason: val_taken, node_id: b.node_id,
+                                sp: ex.span, path: p};
+                    *sc.invalid = list::cons(inv, @*sc.invalid);
+                }
+            }
+        } else if b.node_id == my_defnum {
+            test_scope(cx, sc, b, p);
+        }
+    }
+}
+
+fn check_lval(cx: @ctx, dest: @ast::expr, sc: scope, v: vt<scope>) {
+    alt dest.node {
+      ast::expr_path(p) {
+        let def = cx.tcx.def_map.get(dest.id);
+        let dnum = ast_util::def_id_of_def(def).node;
+        for b in sc.bs {
+            if b.root_var == some(dnum) {
+                let inv = @{reason: overwritten, node_id: b.node_id,
+                            sp: dest.span, path: p};
+                *sc.invalid = list::cons(inv, @*sc.invalid);
+            }
+        }
+      }
+      _ { visit_expr(cx, dest, sc, v); }
+    }
+}
+
+fn check_if(c: @ast::expr, then: ast::blk, els: option<@ast::expr>,
+            sc: scope, v: vt<scope>) {
+    v.visit_expr(c, sc, v);
+    let orig_invalid = *sc.invalid;
+    v.visit_block(then, sc, v);
+    let then_invalid = *sc.invalid;
+    *sc.invalid = orig_invalid;
+    visit::visit_expr_opt(els, sc, v);
+    *sc.invalid = join_invalid(*sc.invalid, then_invalid);
+}
+
+fn check_loop(cx: ctx, sc: scope, checker: fn()) {
+    let orig_invalid = filter_invalid(*sc.invalid, sc.bs);
+    checker();
+    let new_invalid = filter_invalid(*sc.invalid, sc.bs);
+    // Have to check contents of loop again if it invalidated an alias
+    if list::len(orig_invalid) < list::len(new_invalid) {
+        let old_silent = cx.silent;
+        cx.silent = true;
+        checker();
+        cx.silent = old_silent;
+    }
+    *sc.invalid = new_invalid;
+}
+
+fn test_scope(cx: ctx, sc: scope, b: binding, p: @ast::path) {
+    let prob = find_invalid(b.node_id, *sc.invalid);
+    alt b.root_var {
+      some(dn) {
+        for other in sc.bs {
+            if !is_none(prob) { break; }
+            if other.node_id == dn {
+                prob = find_invalid(other.node_id, *sc.invalid);
+            }
+        }
+      }
+      _ {}
+    }
+    if !is_none(prob) && cant_copy(cx, b) {
+        let i = option::get(prob);
+        let msg = alt i.reason {
+          overwritten { "overwriting " + ast_util::path_name(i.path) }
+          val_taken { "taking the value of " + ast_util::path_name(i.path) }
+        };
+        err(cx, i.sp, msg + " will invalidate reference " +
+            ast_util::path_name(p) + ", which is still used");
+    }
+}
+
+fn path_def(cx: ctx, ex: @ast::expr) -> option<ast::def> {
+    ret alt ex.node {
+          ast::expr_path(_) { some(cx.tcx.def_map.get(ex.id)) }
+          _ { none }
+        }
+}
+
+fn path_def_id(cx: ctx, ex: @ast::expr) -> option<ast::node_id> {
+    alt ex.node {
+      ast::expr_path(_) {
+        ret some(ast_util::def_id_of_def(cx.tcx.def_map.get(ex.id)).node);
+      }
+      _ { ret none; }
+    }
+}
+
+fn ty_can_unsafely_include(cx: ctx, needle: unsafe_ty, haystack: ty::t,
+                           mutbl: bool) -> bool {
+    fn get_mutbl(cur: bool, mt: ty::mt) -> bool {
+        ret cur || mt.mutbl != ast::m_imm;
+    }
+    fn helper(tcx: ty::ctxt, needle: unsafe_ty, haystack: ty::t, mutbl: bool)
+        -> bool {
+        if alt needle {
+          contains(ty) { ty == haystack }
+          mutbl_contains(ty) { mutbl && ty == haystack }
+        } { ret true; }
+        alt ty::get(haystack).struct {
+          ty::ty_enum(_, ts) {
+            for t: ty::t in ts {
+                if helper(tcx, needle, t, mutbl) { ret true; }
+            }
+            ret false;
+          }
+          ty::ty_box(mt) | ty::ty_ptr(mt) | ty::ty_uniq(mt) {
+            ret helper(tcx, needle, mt.ty, get_mutbl(mutbl, mt));
+          }
+          ty::ty_rec(fields) {
+            for f: ty::field in fields {
+                if helper(tcx, needle, f.mt.ty, get_mutbl(mutbl, f.mt)) {
+                    ret true;
+                }
+            }
+            ret false;
+          }
+          ty::ty_tup(ts) {
+            for t in ts { if helper(tcx, needle, t, mutbl) { ret true; } }
+            ret false;
+          }
+          ty::ty_fn({proto: ast::proto_bare, _}) { ret false; }
+          // These may contain anything.
+          ty::ty_fn(_) | ty::ty_iface(_, _) { ret true; }
+          // A type param may include everything, but can only be
+          // treated as opaque downstream, and is thus safe unless we
+          // saw mutable fields, in which case the whole thing can be
+          // overwritten.
+          ty::ty_param(_, _) { ret mutbl; }
+          _ { ret false; }
+        }
+    }
+    ret helper(cx.tcx, needle, haystack, mutbl);
+}
+
+fn def_is_local_or_self(d: ast::def) -> bool {
+    alt d {
+      ast::def_local(_, _) | ast::def_arg(_, _) | ast::def_binding(_) |
+      ast::def_upvar(_, _, _) | ast::def_self(_) { true }
+      _ { false }
+    }
+}
+
+fn local_id_of_node(cx: ctx, id: node_id) -> uint {
+    alt cx.tcx.items.find(id) {
+      some(ast_map::node_arg(_, id)) | some(ast_map::node_local(id)) { id }
+      _ { 0u }
+    }
+}
+
+// Heuristic, somewhat random way to decide whether to warn when inserting an
+// implicit copy.
+fn copy_is_expensive(tcx: ty::ctxt, ty: ty::t) -> bool {
+    fn score_ty(tcx: ty::ctxt, ty: ty::t) -> uint {
+        ret alt ty::get(ty).struct {
+          ty::ty_nil | ty::ty_bot | ty::ty_bool | ty::ty_int(_) |
+          ty::ty_uint(_) | ty::ty_float(_) | ty::ty_type |
+          ty::ty_ptr(_) { 1u }
+          ty::ty_box(_) | ty::ty_iface(_, _) { 3u }
+          ty::ty_constr(t, _) | ty::ty_res(_, t, _) { score_ty(tcx, t) }
+          ty::ty_fn(_) { 4u }
+          ty::ty_str | ty::ty_vec(_) | ty::ty_param(_, _) { 50u }
+          ty::ty_uniq(mt) { 1u + score_ty(tcx, mt.ty) }
+          ty::ty_enum(_, ts) | ty::ty_tup(ts) {
+            let sum = 0u;
+            for t in ts { sum += score_ty(tcx, t); }
+            sum
+          }
+          ty::ty_rec(fs) {
+            let sum = 0u;
+            for f in fs { sum += score_ty(tcx, f.mt.ty); }
+            sum
+          }
+          _ {
+            tcx.sess.warn(#fmt("score_ty: unexpected type %s",
+               ty_to_str(tcx, ty)));
+            1u // ???
+          }
+        };
+    }
+    ret score_ty(tcx, ty) > 8u;
+}
+
+type pattern_root = {id: node_id,
+                     name: ident,
+                     mutbl: option<unsafe_ty>,
+                     span: span};
+
+fn pattern_roots(tcx: ty::ctxt, mutbl: option<unsafe_ty>, pat: @ast::pat)
+    -> [pattern_root] {
+    fn walk(tcx: ty::ctxt, mutbl: option<unsafe_ty>, pat: @ast::pat,
+            &set: [pattern_root]) {
+        alt pat.node {
+          ast::pat_ident(nm, sub)
+          if !pat_util::pat_is_variant(tcx.def_map, pat) {
+            set += [{id: pat.id, name: path_to_ident(nm), mutbl: mutbl,
+                        span: pat.span}];
+            alt sub { some(p) { walk(tcx, mutbl, p, set); } _ {} }
+          }
+          ast::pat_wild | ast::pat_lit(_) | ast::pat_range(_, _) |
+          ast::pat_ident(_, _) {}
+          ast::pat_enum(_, ps) | ast::pat_tup(ps) {
+            for p in ps { walk(tcx, mutbl, p, set); }
+          }
+          ast::pat_rec(fs, _) {
+            let ty = ty::node_id_to_type(tcx, pat.id);
+            for f in fs {
+                let m = ty::get_field(ty, f.ident).mt.mutbl != ast::m_imm,
+                    c = if m { some(contains(ty)) } else { mutbl };
+                walk(tcx, c, f.pat, set);
+            }
+          }
+          ast::pat_box(p) {
+            let ty = ty::node_id_to_type(tcx, pat.id);
+            let m = alt ty::get(ty).struct {
+              ty::ty_box(mt) { mt.mutbl != ast::m_imm }
+              _ { tcx.sess.span_bug(pat.span, "box pat has non-box type"); }
+            },
+                c = if m  {some(contains(ty)) } else { mutbl };
+            walk(tcx, c, p, set);
+          }
+          ast::pat_uniq(p) {
+            let ty = ty::node_id_to_type(tcx, pat.id);
+            let m = alt ty::get(ty).struct {
+              ty::ty_uniq(mt) { mt.mutbl != ast::m_imm }
+              _ { tcx.sess.span_bug(pat.span, "uniq pat has non-uniq type"); }
+            },
+                c = if m { some(contains(ty)) } else { mutbl };
+            walk(tcx, c, p, set);
+          }
+        }
+    }
+    let set = [];
+    walk(tcx, mutbl, pat, set);
+    ret set;
+}
+
+// Wraps the expr_root in mutbl.rs to also handle roots that exist through
+// return-by-reference
+fn expr_root(cx: ctx, ex: @ast::expr, autoderef: bool)
+    -> {ex: @ast::expr, mutbl: option<unsafe_ty>} {
+    let base_root = mutbl::expr_root(cx.tcx, ex, autoderef);
+    let unsafe_ty = none;
+    for d in *base_root.ds {
+        if d.mutbl { unsafe_ty = some(contains(d.outer_t)); break; }
+    }
+    ret {ex: base_root.ex, mutbl: unsafe_ty};
+}
+
+fn unsafe_set(from: option<unsafe_ty>) -> [unsafe_ty] {
+    alt from { some(t) { [t] } _ { [] } }
+}
+
+fn find_invalid(id: node_id, lst: list<@invalid>)
+    -> option<@invalid> {
+    let cur = lst;
+    while true {
+        alt cur {
+          list::nil { break; }
+          list::cons(head, tail) {
+            if head.node_id == id { ret some(head); }
+            cur = *tail;
+          }
+        }
+    }
+    ret none;
+}
+
+fn join_invalid(a: list<@invalid>, b: list<@invalid>) -> list<@invalid> {
+    let result = a;
+    list::iter(b) {|elt|
+        let found = false;
+        list::iter(a) {|e| if e == elt { found = true; } }
+        if !found { result = list::cons(elt, @result); }
+    }
+    result
+}
+
+fn filter_invalid(src: list<@invalid>, bs: [binding]) -> list<@invalid> {
+    let out = list::nil, cur = src;
+    while cur != list::nil {
+        alt cur {
+          list::cons(head, tail) {
+            let p = vec::position(bs, {|b| b.node_id == head.node_id});
+            if !is_none(p) { out = list::cons(head, @out); }
+            cur = *tail;
+          }
+          list::nil {
+            // typestate would help...
+            unreachable();
+          }
+        }
+    }
+    ret out;
+}
+
+fn err(cx: ctx, sp: span, err: str) {
+    if !cx.silent || !cx.tcx.sess.has_errors() {
+        cx.tcx.sess.span_err(sp, err);
+    }
+}
+
+// Local Variables:
+// mode: rust
+// fill-column: 78;
+// indent-tabs-mode: nil
+// c-basic-offset: 4
+// buffer-file-coding-system: utf-8-unix
+// End: