/*! * A classic liveness analysis based on dataflow over the AST. Computes, * for each local variable in a function, whether that variable is live * at a given point. Program execution points are identified by their * id. * * # Basic idea * * The basic model is that each local variable is assigned an index. We * represent sets of local variables using a vector indexed by this * index. The value in the vector is either 0, indicating the variable * is dead, or the id of an expression that uses the variable. * * We conceptually walk over the AST in reverse execution order. If we * find a use of a variable, we add it to the set of live variables. If * we find an assignment to a variable, we remove it from the set of live * variables. When we have to merge two flows, we take the union of * those two flows---if the variable is live on both paths, we simply * pick one id. In the event of loops, we continue doing this until a * fixed point is reached. * * ## Checking initialization * * At the function entry point, all variables must be dead. If this is * not the case, we can report an error using the id found in the set of * live variables, which identifies a use of the variable which is not * dominated by an assignment. * * ## Checking moves * * After each explicit move, the variable must be dead. * * ## Computing last uses * * Any use of the variable where the variable is dead afterwards is a * last use. * * # Extension to handle constructors * * Each field is assigned an index just as with local variables. A use of * `self` is considered a use of all fields. A use of `self.f` is just a use * of `f`. * * # Implementation details * * The actual implementation contains two (nested) walks over the AST. * The outer walk has the job of building up the ir_maps instance for the * enclosing function. On the way down the tree, it identifies those AST * nodes and variable IDs that will be needed for the liveness analysis * and assigns them contiguous IDs. The liveness id for an AST node is * called a `live_node` (it's a newtype'd uint) and the id for a variable * is called a `variable` (another newtype'd uint). * * On the way back up the tree, as we are about to exit from a function * declaration we allocate a `liveness` instance. Now that we know * precisely how many nodes and variables we need, we can allocate all * the various arrays that we will need to precisely the right size. We then * perform the actual propagation on the `liveness` instance. * * This propagation is encoded in the various `propagate_through_*()` * methods. It effectively does a reverse walk of the AST; whenever we * reach a loop node, we iterate until a fixed point is reached. * * ## The `users` struct * * At each live node `N`, we track three pieces of information for each * variable `V` (these are encapsulated in the `users` struct): * * - `reader`: the `live_node` ID of some node which will read the value * that `V` holds on entry to `N`. Formally: a node `M` such * that there exists a path `P` from `N` to `M` where `P` does not * write `V`. If the `reader` is `invalid_node()`, then the current * value will never be read (the variable is dead, essentially). * * - `writer`: the `live_node` ID of some node which will write the * variable `V` and which is reachable from `N`. Formally: a node `M` * such that there exists a path `P` from `N` to `M` and `M` writes * `V`. If the `writer` is `invalid_node()`, then there is no writer * of `V` that follows `N`. * * - `used`: a boolean value indicating whether `V` is *used*. We * distinguish a *read* from a *use* in that a *use* is some read that * is not just used to generate a new value. For example, `x += 1` is * a read but not a use. This is used to generate better warnings. * * ## Special Variables * * We generate various special variables for various, well, special purposes. * These are described in the `specials` struct: * * - `exit_ln`: a live node that is generated to represent every 'exit' from * the function, whether it be by explicit return, fail, or other means. * * - `fallthrough_ln`: a live node that represents a fallthrough * * - `no_ret_var`: a synthetic variable that is only 'read' from, the * fallthrough node. This allows us to detect functions where we fail * to return explicitly. * * - `self_var`: a variable representing 'self' */ import dvec::{dvec, extensions}; import std::map::{hashmap, int_hash, str_hash, box_str_hash}; import syntax::{visit, ast_util}; import syntax::print::pprust::{expr_to_str}; import visit::vt; import syntax::codemap::span; import syntax::ast::*; import driver::session::session; import io::writer_util; import capture::{cap_move, cap_drop, cap_copy, cap_ref}; export check_crate; export last_use_map; // Maps from an expr id to a list of variable ids for which this expr // is the last use. Typically, the expr is a path and the node id is // the local/argument/etc that the path refers to. However, it also // possible for the expr to be a closure, in which case the list is a // list of closed over variables that can be moved into the closure. // // Very subtle (#2633): borrowck will remove entries from this table // if it detects an outstanding loan (that is, the addr is taken). type last_use_map = hashmap>; enum variable = uint; enum live_node = uint; enum live_node_kind { lnk_freevar(span), lnk_expr(span), lnk_vdef(span), lnk_exit } fn check_crate(tcx: ty::ctxt, method_map: typeck::method_map, crate: @crate) -> last_use_map { let visitor = visit::mk_vt(@{ visit_fn: visit_fn, visit_local: visit_local, visit_expr: visit_expr with *visit::default_visitor() }); let last_use_map = int_hash(); let initial_maps = @ir_maps(tcx, method_map, last_use_map); visit::visit_crate(*crate, initial_maps, visitor); tcx.sess.abort_if_errors(); ret last_use_map; } impl of to_str::to_str for live_node { fn to_str() -> str { #fmt["ln(%u)", *self] } } impl of to_str::to_str for variable { fn to_str() -> str { #fmt["v(%u)", *self] } } // ______________________________________________________________________ // Creating ir_maps // // This is the first pass and the one that drives the main // computation. It walks up and down the IR once. On the way down, // we count for each function the number of variables as well as // liveness nodes. A liveness node is basically an expression or // capture clause that does something of interest: either it has // interesting control flow or it uses/defines a local variable. // // On the way back up, at each function node we create liveness sets // (we now know precisely how big to make our various vectors and so // forth) and then do the data-flow propagation to compute the set // of live variables at each program point. // // Finally, we run back over the IR one last time and, using the // computed liveness, check various safety conditions. For example, // there must be no live nodes at the definition site for a variable // unless it has an initializer. Similarly, each non-mutable local // variable must not be assigned if there is some successor // assignment. And so forth. impl methods for live_node { pure fn is_valid() -> bool { *self != uint::max_value } } fn invalid_node() -> live_node { live_node(uint::max_value) } enum relevant_def { rdef_var(node_id), rdef_self } type capture_info = {ln: live_node, is_move: bool, rv: relevant_def}; enum var_kind { vk_arg(node_id, ident, rmode), vk_local(node_id, ident), vk_field(ident), vk_self, vk_implicit_ret } fn relevant_def(def: def) -> option { alt def { def_self(_) {some(rdef_self)} def_arg(nid, _) | def_local(nid, _) {some(rdef_var(nid))} _ {none} } } class ir_maps { let tcx: ty::ctxt; let method_map: typeck::method_map; let last_use_map: last_use_map; let mut num_live_nodes: uint; let mut num_vars: uint; let live_node_map: hashmap; let variable_map: hashmap; let field_map: hashmap; let capture_map: hashmap; let mut var_kinds: ~[var_kind]; let mut lnks: ~[live_node_kind]; new(tcx: ty::ctxt, method_map: typeck::method_map, last_use_map: last_use_map) { self.tcx = tcx; self.method_map = method_map; self.last_use_map = last_use_map; self.num_live_nodes = 0u; self.num_vars = 0u; self.live_node_map = int_hash(); self.variable_map = int_hash(); self.capture_map = int_hash(); self.field_map = box_str_hash(); self.var_kinds = ~[]; self.lnks = ~[]; } fn add_live_node(lnk: live_node_kind) -> live_node { let ln = live_node(self.num_live_nodes); vec::push(self.lnks, lnk); self.num_live_nodes += 1u; #debug["%s is of kind %?", ln.to_str(), lnk]; ln } fn add_live_node_for_node(node_id: node_id, lnk: live_node_kind) { let ln = self.add_live_node(lnk); self.live_node_map.insert(node_id, ln); #debug["%s is node %d", ln.to_str(), node_id]; } fn add_variable(vk: var_kind) -> variable { let v = variable(self.num_vars); vec::push(self.var_kinds, vk); self.num_vars += 1u; alt vk { vk_local(node_id, _) | vk_arg(node_id, _, _) { self.variable_map.insert(node_id, v); } vk_field(name) { self.field_map.insert(name, v); } vk_self | vk_implicit_ret { } } #debug["%s is %?", v.to_str(), vk]; v } fn variable(node_id: node_id, span: span) -> variable { alt self.variable_map.find(node_id) { some(var) {var} none { self.tcx.sess.span_bug( span, #fmt("No variable registered for id %d", node_id)); } } } fn variable_name(var: variable) -> ident { alt self.var_kinds[*var] { vk_local(_, name) | vk_arg(_, name, _) {name} vk_field(name) {@("self." + *name)} vk_self {@"self"} vk_implicit_ret {@""} } } fn set_captures(node_id: node_id, +cs: ~[capture_info]) { self.capture_map.insert(node_id, @cs); } fn captures(expr: @expr) -> @~[capture_info] { alt self.capture_map.find(expr.id) { some(caps) {caps} none { self.tcx.sess.span_bug(expr.span, "no registered caps"); } } } fn lnk(ln: live_node) -> live_node_kind { self.lnks[*ln] } fn add_last_use(expr_id: node_id, var: variable) { let vk = self.var_kinds[*var]; #debug["Node %d is a last use of variable %?", expr_id, vk]; alt vk { vk_arg(id, name, by_move) | vk_arg(id, name, by_copy) | vk_local(id, name) { let v = alt self.last_use_map.find(expr_id) { some(v) { v } none { let v = @dvec(); self.last_use_map.insert(expr_id, v); v } }; (*v).push(id); } vk_arg(_, _, by_ref) | vk_arg(_, _, by_mutbl_ref) | vk_arg(_, _, by_val) | vk_self | vk_field(_) | vk_implicit_ret { #debug["--but it is not owned"]; } } } } fn visit_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, sp: span, id: node_id, &&self: @ir_maps, v: vt<@ir_maps>) { #debug["visit_fn: id=%d", id]; let _i = util::common::indenter(); // swap in a new set of IR maps for this function body: let fn_maps = @ir_maps(self.tcx, self.method_map, self.last_use_map); #debug["creating fn_maps: %x", ptr::addr_of(*fn_maps) as uint]; for decl.inputs.each |arg| { #debug["adding argument %d", arg.id]; let mode = ty::resolved_mode(self.tcx, arg.mode); (*fn_maps).add_variable(vk_arg(arg.id, arg.ident, mode)); }; // gather up the various local variables, significant expressions, // and so forth: visit::visit_fn(fk, decl, body, sp, id, fn_maps, v); alt fk { visit::fk_ctor(_, _, _, class_did) { add_class_fields(fn_maps, class_did); } _ {} } // Special nodes and variables: // - exit_ln represents the end of the fn, either by ret or fail // - implicit_ret_var is a pseudo-variable that represents // an implicit return let specials = { exit_ln: (*fn_maps).add_live_node(lnk_exit), fallthrough_ln: (*fn_maps).add_live_node(lnk_exit), no_ret_var: (*fn_maps).add_variable(vk_implicit_ret), self_var: (*fn_maps).add_variable(vk_self) }; // compute liveness let lsets = @liveness(fn_maps, specials); let entry_ln = (*lsets).compute(decl, body); // check for various error conditions let check_vt = visit::mk_vt(@{ visit_fn: check_fn, visit_local: check_local, visit_expr: check_expr with *visit::default_visitor() }); check_vt.visit_block(body, lsets, check_vt); lsets.check_ret(id, sp, fk, entry_ln); lsets.check_fields(sp, entry_ln); lsets.warn_about_unused_args(sp, decl, entry_ln); } fn add_class_fields(self: @ir_maps, did: def_id) { for ty::lookup_class_fields(self.tcx, did).each |field_ty| { assert field_ty.id.crate == local_crate; let var = (*self).add_variable(vk_field(field_ty.ident)); self.field_map.insert(field_ty.ident, var); } } fn visit_local(local: @local, &&self: @ir_maps, vt: vt<@ir_maps>) { let def_map = self.tcx.def_map; do pat_util::pat_bindings(def_map, local.node.pat) |p_id, sp, path| { #debug["adding local variable %d", p_id]; let name = ast_util::path_to_ident(path); (*self).add_live_node_for_node(p_id, lnk_vdef(sp)); (*self).add_variable(vk_local(p_id, name)); } visit::visit_local(local, self, vt); } fn visit_expr(expr: @expr, &&self: @ir_maps, vt: vt<@ir_maps>) { alt expr.node { // live nodes required for uses or definitions of variables: expr_path(_) { let def = self.tcx.def_map.get(expr.id); #debug["expr %d: path that leads to %?", expr.id, def]; if relevant_def(def).is_some() { (*self).add_live_node_for_node(expr.id, lnk_expr(expr.span)); } visit::visit_expr(expr, self, vt); } expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) { // Make a live_node for each captured variable, with the span // being the location that the variable is used. This results // in better error messages than just pointing at the closure // construction site. let proto = ty::ty_fn_proto(ty::expr_ty(self.tcx, expr)); let cvs = capture::compute_capture_vars(self.tcx, expr.id, proto, cap_clause); let mut call_caps = ~[]; for cvs.each |cv| { alt relevant_def(cv.def) { some(rv) { let cv_ln = (*self).add_live_node(lnk_freevar(cv.span)); let is_move = alt cv.mode { cap_move | cap_drop {true} // var must be dead afterwards cap_copy | cap_ref {false} // var can still be used }; vec::push(call_caps, {ln: cv_ln, is_move: is_move, rv: rv}); } none {} } } (*self).set_captures(expr.id, call_caps); visit::visit_expr(expr, self, vt); } // live nodes required for interesting control flow: expr_if_check(*) | expr_if(*) | expr_alt(*) | expr_while(*) | expr_loop(*) { (*self).add_live_node_for_node(expr.id, lnk_expr(expr.span)); visit::visit_expr(expr, self, vt); } expr_binary(op, _, _) if ast_util::lazy_binop(op) { (*self).add_live_node_for_node(expr.id, lnk_expr(expr.span)); visit::visit_expr(expr, self, vt); } // otherwise, live nodes are not required: expr_index(*) | expr_field(*) | expr_vstore(*) | expr_vec(*) | expr_rec(*) | expr_call(*) | expr_tup(*) | expr_new(*) | expr_log(*) | expr_binary(*) | expr_assert(*) | expr_check(*) | expr_addr_of(*) | expr_copy(*) | expr_loop_body(*) | expr_do_body(*) | expr_cast(*) | expr_unary(*) | expr_fail(*) | expr_break | expr_again | expr_lit(_) | expr_ret(*) | expr_block(*) | expr_move(*) | expr_assign(*) | expr_swap(*) | expr_assign_op(*) | expr_mac(*) { visit::visit_expr(expr, self, vt); } } } // ______________________________________________________________________ // Computing liveness sets // // Actually we compute just a bit more than just liveness, but we use // the same basic propagation framework in all cases. type users = { reader: live_node, writer: live_node, used: bool }; fn invalid_users() -> users { {reader: invalid_node(), writer: invalid_node(), used: false} } type specials = { exit_ln: live_node, fallthrough_ln: live_node, no_ret_var: variable, self_var: variable }; const ACC_READ: uint = 1u; const ACC_WRITE: uint = 2u; const ACC_USE: uint = 4u; class liveness { let tcx: ty::ctxt; let ir: @ir_maps; let s: specials; let successors: ~[mut live_node]; let users: ~[mut users]; let mut break_ln: live_node; let mut cont_ln: live_node; new(ir: @ir_maps, specials: specials) { self.ir = ir; self.tcx = ir.tcx; self.s = specials; self.successors = vec::to_mut( vec::from_elem(self.ir.num_live_nodes, invalid_node())); self.users = vec::to_mut( vec::from_elem(self.ir.num_live_nodes * self.ir.num_vars, invalid_users())); self.break_ln = invalid_node(); self.cont_ln = invalid_node(); } // _______________________________________________________________________ fn live_node(node_id: node_id, span: span) -> live_node { alt self.ir.live_node_map.find(node_id) { some(ln) {ln} none { // This must be a mismatch between the ir_map construction // above and the propagation code below; the two sets of // code have to agree about which AST nodes are worth // creating liveness nodes for. self.tcx.sess.span_bug( span, #fmt["No live node registered for node %d", node_id]); } } } fn variable_from_rdef(rv: relevant_def, span: span) -> variable { alt rv { rdef_self {self.s.self_var} rdef_var(nid) {self.variable(nid, span)} } } fn variable_from_path(expr: @expr) -> option { alt expr.node { expr_path(_) { let def = self.tcx.def_map.get(expr.id); relevant_def(def).map( |rdef| self.variable_from_rdef(rdef, expr.span) ) } _ {none} } } fn variable(node_id: node_id, span: span) -> variable { (*self.ir).variable(node_id, span) } fn variable_from_def_map(node_id: node_id, span: span) -> option { alt self.tcx.def_map.find(node_id) { some(def) { relevant_def(def).map( |rdef| self.variable_from_rdef(rdef, span) ) } none { self.tcx.sess.span_bug( span, "Not present in def map") } } } fn pat_bindings(pat: @pat, f: fn(live_node, variable, span)) { let def_map = self.tcx.def_map; do pat_util::pat_bindings(def_map, pat) |p_id, sp, _n| { let ln = self.live_node(p_id, sp); let var = self.variable(p_id, sp); f(ln, var, sp); } } fn idx(ln: live_node, var: variable) -> uint { *ln * self.ir.num_vars + *var } fn live_on_entry(ln: live_node, var: variable) -> option { assert ln.is_valid(); let reader = self.users[self.idx(ln, var)].reader; if reader.is_valid() {some((*self.ir).lnk(reader))} else {none} } fn live_on_exit(ln: live_node, var: variable) -> option { self.live_on_entry(copy self.successors[*ln], var) } fn used_on_entry(ln: live_node, var: variable) -> bool { assert ln.is_valid(); self.users[self.idx(ln, var)].used } fn assigned_on_entry(ln: live_node, var: variable) -> option { assert ln.is_valid(); let writer = self.users[self.idx(ln, var)].writer; if writer.is_valid() {some((*self.ir).lnk(writer))} else {none} } fn assigned_on_exit(ln: live_node, var: variable) -> option { self.assigned_on_entry(copy self.successors[*ln], var) } fn indices(ln: live_node, op: fn(uint)) { let node_base_idx = self.idx(ln, variable(0u)); for uint::range(0u, self.ir.num_vars) |var_idx| { op(node_base_idx + var_idx) } } fn indices2(ln: live_node, succ_ln: live_node, op: fn(uint, uint)) { let node_base_idx = self.idx(ln, variable(0u)); let succ_base_idx = self.idx(succ_ln, variable(0u)); for uint::range(0u, self.ir.num_vars) |var_idx| { op(node_base_idx + var_idx, succ_base_idx + var_idx); } } fn write_vars(wr: io::writer, ln: live_node, test: fn(uint) -> live_node) { let node_base_idx = self.idx(ln, variable(0u)); for uint::range(0u, self.ir.num_vars) |var_idx| { let idx = node_base_idx + var_idx; if test(idx).is_valid() { wr.write_str(" "); wr.write_str(variable(var_idx).to_str()); } } } fn ln_str(ln: live_node) -> str { do io::with_str_writer |wr| { wr.write_str("[ln("); wr.write_uint(*ln); wr.write_str(") of kind "); wr.write_str(#fmt["%?", copy self.ir.lnks[*ln]]); wr.write_str(" reads"); self.write_vars(wr, ln, |idx| self.users[idx].reader ); wr.write_str(" writes"); self.write_vars(wr, ln, |idx| self.users[idx].writer ); wr.write_str(" "); wr.write_str(" precedes "); wr.write_str((copy self.successors[*ln]).to_str()); wr.write_str("]"); } } fn init_empty(ln: live_node, succ_ln: live_node) { self.successors[*ln] = succ_ln; // It is not necessary to initialize the // values to empty because this is the value // they have when they are created, and the sets // only grow during iterations. // // self.indices(ln) { |idx| // self.users[idx] = invalid_users(); // } } fn init_from_succ(ln: live_node, succ_ln: live_node) { // more efficient version of init_empty() / merge_from_succ() self.successors[*ln] = succ_ln; self.indices2(ln, succ_ln, |idx, succ_idx| { self.users[idx] = self.users[succ_idx] }); #debug["init_from_succ(ln=%s, succ=%s)", self.ln_str(ln), self.ln_str(succ_ln)]; } fn merge_from_succ(ln: live_node, succ_ln: live_node, first_merge: bool) -> bool { if ln == succ_ln { ret false; } let mut changed = false; do self.indices2(ln, succ_ln) |idx, succ_idx| { changed |= copy_if_invalid(copy self.users[succ_idx].reader, self.users[idx].reader); changed |= copy_if_invalid(copy self.users[succ_idx].writer, self.users[idx].writer); if self.users[succ_idx].used && !self.users[idx].used { self.users[idx].used = true; changed = true; } } #debug["merge_from_succ(ln=%s, succ=%s, first_merge=%b, changed=%b)", ln.to_str(), self.ln_str(succ_ln), first_merge, changed]; ret changed; fn copy_if_invalid(src: live_node, &dst: live_node) -> bool { if src.is_valid() { if !dst.is_valid() { dst = src; ret true; } } ret false; } } // Indicates that a local variable was *defined*; we know that no // uses of the variable can precede the definition (resolve checks // this) so we just clear out all the data. fn define(writer: live_node, var: variable) { let idx = self.idx(writer, var); self.users[idx].reader = invalid_node(); self.users[idx].writer = invalid_node(); #debug["%s defines %s (idx=%u): %s", writer.to_str(), var.to_str(), idx, self.ln_str(writer)]; } // Either read, write, or both depending on the acc bitset fn acc(ln: live_node, var: variable, acc: uint) { let idx = self.idx(ln, var); let user = &mut self.users[idx]; if (acc & ACC_WRITE) != 0u { user.reader = invalid_node(); user.writer = ln; } // Important: if we both read/write, must do read second // or else the write will override. if (acc & ACC_READ) != 0u { user.reader = ln; } if (acc & ACC_USE) != 0u { self.users[idx].used = true; } #debug["%s accesses[%x] %s: %s", ln.to_str(), acc, var.to_str(), self.ln_str(ln)]; } // _______________________________________________________________________ fn compute(decl: fn_decl, body: blk) -> live_node { // if there is a `break` or `cont` at the top level, then it's // effectively a return---this only occurs in `for` loops, // where the body is really a closure. let entry_ln: live_node = self.with_loop_nodes(self.s.exit_ln, self.s.exit_ln, || { self.propagate_through_fn_block(decl, body) }); // hack to skip the loop unless #debug is enabled: #debug["^^ liveness computation results for body %d (entry=%s)", { for uint::range(0u, self.ir.num_live_nodes) |ln_idx| { #debug["%s", self.ln_str(live_node(ln_idx))]; } body.node.id }, entry_ln.to_str()]; entry_ln } fn propagate_through_fn_block(decl: fn_decl, blk: blk) -> live_node { // inputs passed by & mode should be considered live on exit: for decl.inputs.each |arg| { alt ty::resolved_mode(self.tcx, arg.mode) { by_mutbl_ref | by_ref | by_val { // These are "non-owned" modes, so register a read at // the end. This will prevent us from moving out of // such variables but also prevent us from registering // last uses and so forth. let var = self.variable(arg.id, blk.span); self.acc(self.s.exit_ln, var, ACC_READ); } by_move | by_copy { // These are owned modes. If we don't use the // variable, nobody will. } } } // as above, the "self" variable is a non-owned variable self.acc(self.s.exit_ln, self.s.self_var, ACC_READ); // in a ctor, there is an implicit use of self.f for all fields f: for self.ir.field_map.each_value |var| { self.acc(self.s.exit_ln, var, ACC_READ|ACC_USE); } // the fallthrough exit is only for those cases where we do not // explicitly return: self.init_from_succ(self.s.fallthrough_ln, self.s.exit_ln); if blk.node.expr.is_none() { self.acc(self.s.fallthrough_ln, self.s.no_ret_var, ACC_READ) } self.propagate_through_block(blk, self.s.fallthrough_ln) } fn propagate_through_block(blk: blk, succ: live_node) -> live_node { let succ = self.propagate_through_opt_expr(blk.node.expr, succ); do blk.node.stmts.foldr(succ) |stmt, succ| { self.propagate_through_stmt(stmt, succ) } } fn propagate_through_stmt(stmt: @stmt, succ: live_node) -> live_node { alt stmt.node { stmt_decl(decl, _) { ret self.propagate_through_decl(decl, succ); } stmt_expr(expr, _) | stmt_semi(expr, _) { ret self.propagate_through_expr(expr, succ); } } } fn propagate_through_decl(decl: @decl, succ: live_node) -> live_node { alt decl.node { decl_local(locals) { do locals.foldr(succ) |local, succ| { self.propagate_through_local(local, succ) } } decl_item(_) { succ } } } fn propagate_through_local(local: @local, succ: live_node) -> live_node { // Note: we mark the variable as defined regardless of whether // there is an initializer. Initially I had thought to only mark // the live variable as defined if it was initialized, and then we // could check for uninit variables just by scanning what is live // at the start of the function. But that doesn't work so well for // immutable variables defined in a loop: // loop { let x; x = 5; } // because the "assignment" loops back around and generates an error. // // So now we just check that variables defined w/o an // initializer are not live at the point of their // initialization, which is mildly more complex than checking // once at the func header but otherwise equivalent. let opt_init = local.node.init.map(|i| i.expr ); let mut succ = self.propagate_through_opt_expr(opt_init, succ); do self.pat_bindings(local.node.pat) |ln, var, _sp| { self.init_from_succ(ln, succ); self.define(ln, var); succ = ln; } succ } fn propagate_through_exprs(exprs: ~[@expr], succ: live_node) -> live_node { do exprs.foldr(succ) |expr, succ| { self.propagate_through_expr(expr, succ) } } fn propagate_through_opt_expr(opt_expr: option<@expr>, succ: live_node) -> live_node { do opt_expr.foldl(succ) |succ, expr| { self.propagate_through_expr(expr, succ) } } fn propagate_through_expr(expr: @expr, succ: live_node) -> live_node { alt expr.node { // Interesting cases with control flow or which gen/kill expr_path(_) { self.access_path(expr, succ, ACC_READ | ACC_USE) } expr_field(e, nm, _) { // If this is a reference to `self.f` inside of a ctor, // then we treat it as a read of that variable. // Otherwise, we ignore it and just propagate down to // process `e`. alt self.as_self_field(e, nm) { some((ln, var)) { self.init_from_succ(ln, succ); self.acc(ln, var, ACC_READ | ACC_USE); ln } none { self.propagate_through_expr(e, succ) } } } expr_fn(*) | expr_fn_block(*) { // the construction of a closure itself is not important, // but we have to consider the closed over variables. let caps = (*self.ir).captures(expr); do (*caps).foldr(succ) |cap, succ| { self.init_from_succ(cap.ln, succ); let var = self.variable_from_rdef(cap.rv, expr.span); self.acc(cap.ln, var, ACC_READ | ACC_USE); cap.ln } } expr_if_check(cond, then, els) | expr_if(cond, then, els) { // // (cond) // | // v // (expr) // / \ // | | // v v // (then)(els) // | | // v v // ( succ ) // let else_ln = self.propagate_through_opt_expr(els, succ); let then_ln = self.propagate_through_block(then, succ); let ln = self.live_node(expr.id, expr.span); self.init_from_succ(ln, else_ln); self.merge_from_succ(ln, then_ln, false); self.propagate_through_expr(cond, ln) } expr_while(cond, blk) { self.propagate_through_loop(expr, some(cond), blk, succ) } expr_loop(blk) { self.propagate_through_loop(expr, none, blk, succ) } expr_alt(e, arms, _) { // // (e) // | // v // (expr) // / | \ // | | | // v v v // (..arms..) // | | | // v v v // ( succ ) // // let ln = self.live_node(expr.id, expr.span); self.init_empty(ln, succ); let mut first_merge = true; for arms.each |arm| { let arm_succ = self.propagate_through_opt_expr( arm.guard, self.propagate_through_block(arm.body, succ)); self.merge_from_succ(ln, arm_succ, first_merge); first_merge = false; }; self.propagate_through_expr(e, ln) } expr_ret(o_e) | expr_fail(o_e) { // ignore succ and subst exit_ln: self.propagate_through_opt_expr(o_e, self.s.exit_ln) } expr_break { if !self.break_ln.is_valid() { self.tcx.sess.span_bug( expr.span, "break with invalid break_ln"); } self.break_ln } expr_again { if !self.cont_ln.is_valid() { self.tcx.sess.span_bug( expr.span, "cont with invalid cont_ln"); } self.cont_ln } expr_move(l, r) | expr_assign(l, r) { // see comment on lvalues in // propagate_through_lvalue_components() let succ = self.write_lvalue(l, succ, ACC_WRITE); let succ = self.propagate_through_lvalue_components(l, succ); self.propagate_through_expr(r, succ) } expr_swap(l, r) { // see comment on lvalues in // propagate_through_lvalue_components() // I count swaps as `used` cause it might be something like: // foo.bar <-> x // and I am too lazy to distinguish this case from // y <-> x // (where both x, y are unused) just for a warning. let succ = self.write_lvalue(r, succ, ACC_WRITE|ACC_READ|ACC_USE); let succ = self.write_lvalue(l, succ, ACC_WRITE|ACC_READ|ACC_USE); let succ = self.propagate_through_lvalue_components(r, succ); self.propagate_through_lvalue_components(l, succ) } expr_assign_op(_, l, r) { // see comment on lvalues in // propagate_through_lvalue_components() let succ = self.write_lvalue(l, succ, ACC_WRITE|ACC_READ); let succ = self.propagate_through_expr(r, succ); self.propagate_through_lvalue_components(l, succ) } // Uninteresting cases: just propagate in rev exec order expr_vstore(expr, _) { self.propagate_through_expr(expr, succ) } expr_vec(exprs, _) { self.propagate_through_exprs(exprs, succ) } expr_rec(fields, with_expr) { let succ = self.propagate_through_opt_expr(with_expr, succ); do fields.foldr(succ) |field, succ| { self.propagate_through_expr(field.node.expr, succ) } } expr_call(f, args, _) { // calling a fn with bot return type means that the fn // will fail, and hence the successors can be ignored let t_ret = ty::ty_fn_ret(ty::expr_ty(self.tcx, f)); let succ = if ty::type_is_bot(t_ret) {self.s.exit_ln} else {succ}; let succ = self.propagate_through_exprs(args, succ); self.propagate_through_expr(f, succ) } expr_tup(exprs) { self.propagate_through_exprs(exprs, succ) } expr_binary(op, l, r) if ast_util::lazy_binop(op) { let r_succ = self.propagate_through_expr(r, succ); let ln = self.live_node(expr.id, expr.span); self.init_from_succ(ln, succ); self.merge_from_succ(ln, r_succ, false); self.propagate_through_expr(l, ln) } expr_new(l, _, r) | expr_log(_, l, r) | expr_index(l, r) | expr_binary(_, l, r) { self.propagate_through_exprs(~[l, r], succ) } expr_assert(e) | expr_check(_, e) | expr_addr_of(_, e) | expr_copy(e) | expr_loop_body(e) | expr_do_body(e) | expr_cast(e, _) | expr_unary(_, e) { self.propagate_through_expr(e, succ) } expr_lit(*) { succ } expr_block(blk) { self.propagate_through_block(blk, succ) } expr_mac(*) { self.tcx.sess.span_bug(expr.span, "unexpanded macro"); } } } fn propagate_through_lvalue_components(expr: @expr, succ: live_node) -> live_node { // # Lvalues // // In general, the full flow graph structure for an // assignment/move/etc can be handled in one of two ways, // depending on whether what is being assigned is a "tracked // value" or not. A tracked value is basically a local variable // or argument, or a self-field (`self.f`) in a ctor. // // The two kinds of graphs are: // // Tracked lvalue Untracked lvalue // ----------------------++----------------------- // || // | || | // v || v // (rvalue) || (rvalue) // | || | // v || v // (write of lvalue) || (lvalue components) // | || | // v || v // (succ) || (succ) // || // ----------------------++----------------------- // // I will cover the two cases in turn: // // # Tracked lvalues // // A tracked lvalue is either a local variable/argument `x` or // else it is a self-field `self.f` in a constructor. In // these cases, the link_node where the write occurs is linked // to node id of `x` or `self`, respectively. The // `write_lvalue()` routine generates the contents of this // node. There are no subcomponents to consider. // // # Non-tracked lvalues // // These are lvalues like `x[5]` or `x.f`. In that case, we // basically ignore the value which is written to but generate // reads for the components---`x` in these two examples. The // components reads are generated by // `propagate_through_lvalue_components()` (this fn). // // # Illegal lvalues // // It is still possible to observe assignments to non-lvalues; // these errors are detected in the later pass borrowck. We // just ignore such cases and treat them as reads. alt expr.node { expr_path(_) { succ } expr_field(e, nm, _) { alt self.as_self_field(e, nm) { some(_) {succ} none {self.propagate_through_expr(e, succ)} } } _ { self.propagate_through_expr(expr, succ) } } } // see comment on propagate_through_lvalue() fn write_lvalue(expr: @expr, succ: live_node, acc: uint) -> live_node { alt expr.node { expr_path(_) { self.access_path(expr, succ, acc) } expr_field(e, nm, _) { alt self.as_self_field(e, nm) { some((ln, var)) { self.init_from_succ(ln, succ); self.acc(ln, var, acc); ln } none { succ } } } // We do not track other lvalues, so just propagate through // to their subcomponents. Also, it may happen that // non-lvalues occur here, because those are detected in the // later pass borrowck. _ {succ} } } fn access_path(expr: @expr, succ: live_node, acc: uint) -> live_node { let def = self.tcx.def_map.get(expr.id); alt relevant_def(def) { some(rdef_self) { // Accessing `self` is like accessing every field of // the current object. This allows something like // `self = ...;` (it will be considered a write to // every field, sensibly enough), though the borrowck // pass will reject it later on. // // Also, note that, within a ctor at least, an // expression like `self.f` is "shortcircuiting" // before it reaches this point by the code for // expr_field. let ln = self.live_node(expr.id, expr.span); if acc != 0u { self.init_from_succ(ln, succ); for self.ir.field_map.each_value |var| { self.acc(ln, var, acc); } } ln } some(rdef_var(nid)) { let ln = self.live_node(expr.id, expr.span); if acc != 0u { self.init_from_succ(ln, succ); let var = self.variable(nid, expr.span); self.acc(ln, var, acc); } ln } none { succ } } } fn as_self_field(expr: @expr, fld: ident) -> option<(live_node,variable)> { // If we checking a constructor, then we treat self.f as a // variable. we use the live_node id that will be assigned to // the reference to self but the variable id for `f`. alt expr.node { expr_path(_) { let def = self.tcx.def_map.get(expr.id); alt def { def_self(_) { // Note: the field_map is empty unless we are in a ctor ret self.ir.field_map.find(fld).map(|var| { let ln = self.live_node(expr.id, expr.span); (ln, var) }); } _ { ret none; } } } _ { ret none; } } } fn propagate_through_loop(expr: @expr, cond: option<@expr>, body: blk, succ: live_node) -> live_node { /* We model control flow like this: (cond) <--+ | | v | +-- (expr) | | | | | v | | (body) ---+ | | v (succ) */ // first iteration: let mut first_merge = true; let ln = self.live_node(expr.id, expr.span); self.init_empty(ln, succ); if cond.is_some() { // if there is a condition, then it's possible we bypass // the body altogether. otherwise, the only way is via a // break in the loop body. self.merge_from_succ(ln, succ, first_merge); first_merge = false; } let cond_ln = self.propagate_through_opt_expr(cond, ln); let body_ln = self.with_loop_nodes(succ, ln, || { self.propagate_through_block(body, cond_ln) }); // repeat until fixed point is reached: while self.merge_from_succ(ln, body_ln, first_merge) { first_merge = false; assert cond_ln == self.propagate_through_opt_expr(cond, ln); assert body_ln == self.with_loop_nodes(succ, ln, || { self.propagate_through_block(body, cond_ln) }); } cond_ln } fn with_loop_nodes(break_ln: live_node, cont_ln: live_node, f: fn() -> R) -> R { let bl = self.break_ln, cl = self.cont_ln; self.break_ln = break_ln; self.cont_ln = cont_ln; let r <- f(); self.break_ln = bl; self.cont_ln = cl; ret r; } } // _______________________________________________________________________ // Checking for error conditions fn check_local(local: @local, &&self: @liveness, vt: vt<@liveness>) { alt local.node.init { some({op: op, expr: expr}) { // Initializer: alt op { init_move {self.check_move_from_expr(expr, vt)} init_assign {} } self.warn_about_unused_or_dead_vars_in_pat(local.node.pat); if !local.node.is_mutbl { self.check_for_reassignments_in_pat(local.node.pat); } } none { // No initializer: the variable might be unused; if not, it // should not be live at this point. #debug["check_local() with no initializer"]; do (*self).pat_bindings(local.node.pat) |ln, var, sp| { if !self.warn_about_unused(sp, ln, var) { alt (*self).live_on_exit(ln, var) { none { /* not live: good */ } some(lnk) { self.report_illegal_read( local.span, lnk, var, possibly_uninitialized_variable); } } } } } } visit::visit_local(local, self, vt); } fn check_expr(expr: @expr, &&self: @liveness, vt: vt<@liveness>) { alt expr.node { expr_path(_) { for (*self).variable_from_def_map(expr.id, expr.span).each |var| { let ln = (*self).live_node(expr.id, expr.span); self.consider_last_use(expr, ln, var); } visit::visit_expr(expr, self, vt); } expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) { let caps = (*self.ir).captures(expr); for (*caps).each |cap| { let var = (*self).variable_from_rdef(cap.rv, expr.span); self.consider_last_use(expr, cap.ln, var); if cap.is_move { self.check_move_from_var(expr.span, cap.ln, var); } } visit::visit_expr(expr, self, vt); } expr_assign(l, r) { self.check_lvalue(l, vt); vt.visit_expr(r, self, vt); visit::visit_expr(expr, self, vt); } expr_move(l, r) { self.check_lvalue(l, vt); self.check_move_from_expr(r, vt); visit::visit_expr(expr, self, vt); } expr_assign_op(_, l, _) { self.check_lvalue(l, vt); visit::visit_expr(expr, self, vt); } expr_call(f, args, _) { let targs = ty::ty_fn_args(ty::expr_ty(self.tcx, f)); vt.visit_expr(f, self, vt); do vec::iter2(args, targs) |arg_expr, arg_ty| { alt ty::resolved_mode(self.tcx, arg_ty.mode) { by_val | by_copy | by_ref | by_mutbl_ref{ vt.visit_expr(arg_expr, self, vt); } by_move { self.check_move_from_expr(arg_expr, vt); } } } } // no correctness conditions related to liveness expr_if_check(*) | expr_if(*) | expr_alt(*) | expr_while(*) | expr_loop(*) | expr_index(*) | expr_field(*) | expr_vstore(*) | expr_vec(*) | expr_rec(*) | expr_tup(*) | expr_new(*) | expr_log(*) | expr_binary(*) | expr_assert(*) | expr_check(*) | expr_copy(*) | expr_loop_body(*) | expr_do_body(*) | expr_cast(*) | expr_unary(*) | expr_fail(*) | expr_ret(*) | expr_break | expr_again | expr_lit(_) | expr_block(*) | expr_swap(*) | expr_mac(*) | expr_addr_of(*) { visit::visit_expr(expr, self, vt); } } } fn check_fn(_fk: visit::fn_kind, _decl: fn_decl, _body: blk, _sp: span, _id: node_id, &&_self: @liveness, _v: vt<@liveness>) { // do not check contents of nested fns } enum read_kind { possibly_uninitialized_variable, possibly_uninitialized_field, moved_variable } impl check_methods for @liveness { fn check_fields(sp: span, entry_ln: live_node) { for self.ir.field_map.each |nm, var| { alt (*self).live_on_entry(entry_ln, var) { none { /* ok */ } some(lnk_exit) { self.tcx.sess.span_err( sp, #fmt["field `self.%s` is never initialized", *nm]); } some(lnk) { self.report_illegal_read( sp, lnk, var, possibly_uninitialized_field); } } } } fn check_ret(id: node_id, sp: span, fk: visit::fn_kind, entry_ln: live_node) { if (*self).live_on_entry(entry_ln, self.s.no_ret_var).is_some() { // if no_ret_var is live, then we fall off the end of the // function without any kind of return expression: let t_ret = ty::ty_fn_ret(ty::node_id_to_type(self.tcx, id)); if ty::type_is_nil(t_ret) { // for nil return types, it is ok to not return a value expl. } else if ty::type_is_bot(t_ret) { // for bot return types, not ok. Function should fail. self.tcx.sess.span_err( sp, "some control paths may return"); } else { alt fk { visit::fk_ctor(*) { // ctors are written as though they are unit. } _ { self.tcx.sess.span_err( sp, "not all control paths return a value"); } } } } } fn check_move_from_var(span: span, ln: live_node, var: variable) { #debug["check_move_from_var(%s, %s)", ln.to_str(), var.to_str()]; alt (*self).live_on_exit(ln, var) { none { } some(lnk) { self.report_illegal_move(span, lnk, var); } } } fn consider_last_use(expr: @expr, ln: live_node, var: variable) { alt (*self).live_on_exit(ln, var) { some(_) {} none { (*self.ir).add_last_use(expr.id, var); } } } fn check_move_from_expr(expr: @expr, vt: vt<@liveness>) { #debug["check_move_from_expr(node %d: %s)", expr.id, expr_to_str(expr)]; if self.ir.method_map.contains_key(expr.id) { // actually an rvalue, since this calls a method ret vt.visit_expr(expr, self, vt); } alt expr.node { expr_path(_) { alt (*self).variable_from_path(expr) { some(var) { let ln = (*self).live_node(expr.id, expr.span); self.check_move_from_var(expr.span, ln, var); } none {} } } expr_field(base, _, _) { // Moving from x.y is allowed if x is never used later. // (Note that the borrowck guarantees that anything // being moved from is uniquely tied to the stack frame) self.check_move_from_expr(base, vt); } expr_index(base, idx) { // Moving from x[y] is allowed if x is never used later. // (Note that the borrowck guarantees that anything // being moved from is uniquely tied to the stack frame) self.check_move_from_expr(base, vt); vt.visit_expr(idx, self, vt); } _ { // For other kinds of lvalues, no checks are required, // and any embedded expressions are actually rvalues vt.visit_expr(expr, self, vt); } } } fn check_lvalue(expr: @expr, vt: vt<@liveness>) { alt expr.node { expr_path(_) { alt self.tcx.def_map.get(expr.id) { def_local(nid, false) { // Assignment to an immutable variable or argument: // only legal if there is no later assignment. let ln = (*self).live_node(expr.id, expr.span); let var = (*self).variable(nid, expr.span); self.check_for_reassignment(ln, var, expr.span); self.warn_about_dead_assign(expr.span, ln, var); } def { alt relevant_def(def) { some(rdef_var(nid)) { let ln = (*self).live_node(expr.id, expr.span); let var = (*self).variable(nid, expr.span); self.warn_about_dead_assign(expr.span, ln, var); } some(rdef_self) {} none {} } } } } _ { // For other kinds of lvalues, no checks are required, // and any embedded expressions are actually rvalues visit::visit_expr(expr, self, vt); } } } fn check_for_reassignments_in_pat(pat: @pat) { do (*self).pat_bindings(pat) |ln, var, sp| { self.check_for_reassignment(ln, var, sp); } } fn check_for_reassignment(ln: live_node, var: variable, orig_span: span) { alt (*self).assigned_on_exit(ln, var) { some(lnk_expr(span)) { self.tcx.sess.span_err( span, "re-assignment of immutable variable"); self.tcx.sess.span_note( orig_span, "prior assignment occurs here"); } some(lnk) { self.tcx.sess.span_bug( orig_span, #fmt["illegal writer: %?", lnk]); } none {} } } fn report_illegal_move(move_span: span, lnk: live_node_kind, var: variable) { // the only time that it is possible to have a moved variable // used by lnk_exit would be arguments or fields in a ctor. // we give a slightly different error message in those cases. if lnk == lnk_exit { let vk = self.ir.var_kinds[*var]; alt vk { vk_arg(_, name, _) { self.tcx.sess.span_err( move_span, #fmt["illegal move from argument `%s`, which is not \ copy or move mode", *name]); ret; } vk_field(name) { self.tcx.sess.span_err( move_span, #fmt["illegal move from field `%s`", *name]); ret; } vk_self { self.tcx.sess.span_err( move_span, "illegal move from self (cannot move out of a field of \ self)"); ret; } vk_local(*) | vk_implicit_ret { self.tcx.sess.span_bug( move_span, #fmt["illegal reader (%?) for `%?`", lnk, vk]); } } } self.report_illegal_read(move_span, lnk, var, moved_variable); self.tcx.sess.span_note( move_span, "move of variable occurred here"); } fn report_illegal_read(chk_span: span, lnk: live_node_kind, var: variable, rk: read_kind) { let msg = alt rk { possibly_uninitialized_variable {"possibly uninitialized variable"} possibly_uninitialized_field {"possibly uninitialized field"} moved_variable {"moved variable"} }; let name = (*self.ir).variable_name(var); alt lnk { lnk_freevar(span) { self.tcx.sess.span_err( span, #fmt["capture of %s: `%s`", msg, *name]); } lnk_expr(span) { self.tcx.sess.span_err( span, #fmt["use of %s: `%s`", msg, *name]); } lnk_exit | lnk_vdef(_) { self.tcx.sess.span_bug( chk_span, #fmt["illegal reader: %?", lnk]); } } } fn should_warn(var: variable) -> option { let name = (*self.ir).variable_name(var); if (*name)[0] == ('_' as u8) {none} else {some(name)} } fn warn_about_unused_args(sp: span, decl: fn_decl, entry_ln: live_node) { for decl.inputs.each |arg| { let var = (*self).variable(arg.id, arg.ty.span); alt ty::resolved_mode(self.tcx, arg.mode) { by_mutbl_ref { // for mutable reference arguments, something like // x = 1; // is not worth warning about, as it has visible // side effects outside the fn. alt (*self).assigned_on_entry(entry_ln, var) { some(_) { /*ok*/ } none { // but if it is not written, it ought to be used self.warn_about_unused(sp, entry_ln, var); } } } by_val | by_ref | by_move | by_copy { self.warn_about_unused(sp, entry_ln, var); } } } } fn warn_about_unused_or_dead_vars_in_pat(pat: @pat) { do (*self).pat_bindings(pat) |ln, var, sp| { if !self.warn_about_unused(sp, ln, var) { self.warn_about_dead_assign(sp, ln, var); } } } fn warn_about_unused(sp: span, ln: live_node, var: variable) -> bool { if !(*self).used_on_entry(ln, var) { for self.should_warn(var).each |name| { // annoying: for parameters in funcs like `fn(x: int) // {ret}`, there is only one node, so asking about // assigned_on_exit() is not meaningful. let is_assigned = if ln == self.s.exit_ln { false } else { (*self).assigned_on_exit(ln, var).is_some() }; if is_assigned { self.tcx.sess.span_warn( sp, #fmt["variable `%s` is assigned to, \ but never used", *name]); } else { self.tcx.sess.span_warn( sp, #fmt["unused variable: `%s`", *name]); } } ret true; } ret false; } fn warn_about_dead_assign(sp: span, ln: live_node, var: variable) { if (*self).live_on_exit(ln, var).is_none() { for self.should_warn(var).each |name| { self.tcx.sess.span_warn( sp, #fmt["value assigned to `%s` is never read", *name]); } } } }