| // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT |
| // file at the top-level directory of this distribution and at |
| // http://rust-lang.org/COPYRIGHT. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| //! 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. |
| //! |
| //! # 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 usize) and the id for a variable |
| //! is called a `variable` (another newtype'd usize). |
| //! |
| //! 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 `LiveNode` 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 `LiveNode` 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, panic, 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. |
| //! - `clean_exit_var`: a synthetic variable that is only 'read' from the |
| //! fallthrough node. It is only live if the function could converge |
| //! via means other than an explicit `return` expression. That is, it is |
| //! only dead if the end of the function's block can never be reached. |
| //! It is the responsibility of typeck to ensure that there are no |
| //! `return` expressions in a function declared as diverging. |
| use self::LoopKind::*; |
| use self::LiveNodeKind::*; |
| use self::VarKind::*; |
| |
| use dep_graph::DepNode; |
| use hir::def::*; |
| use hir::pat_util; |
| use ty::{self, TyCtxt, ParameterEnvironment}; |
| use traits::{self, ProjectionMode}; |
| use ty::subst::Subst; |
| use lint; |
| use util::nodemap::NodeMap; |
| |
| use std::{fmt, usize}; |
| use std::io::prelude::*; |
| use std::io; |
| use std::rc::Rc; |
| use syntax::ast::{self, NodeId}; |
| use syntax::codemap::original_sp; |
| use syntax::parse::token::keywords; |
| use syntax::ptr::P; |
| use syntax_pos::{BytePos, Span}; |
| |
| use hir::Expr; |
| use hir; |
| use hir::print::{expr_to_string, block_to_string}; |
| use hir::intravisit::{self, Visitor, FnKind}; |
| |
| /// For use with `propagate_through_loop`. |
| enum LoopKind<'a> { |
| /// An endless `loop` loop. |
| LoopLoop, |
| /// A `while` loop, with the given expression as condition. |
| WhileLoop(&'a Expr), |
| } |
| |
| #[derive(Copy, Clone, PartialEq)] |
| struct Variable(usize); |
| |
| #[derive(Copy, PartialEq)] |
| struct LiveNode(usize); |
| |
| impl Variable { |
| fn get(&self) -> usize { let Variable(v) = *self; v } |
| } |
| |
| impl LiveNode { |
| fn get(&self) -> usize { let LiveNode(v) = *self; v } |
| } |
| |
| impl Clone for LiveNode { |
| fn clone(&self) -> LiveNode { |
| LiveNode(self.get()) |
| } |
| } |
| |
| #[derive(Copy, Clone, PartialEq, Debug)] |
| enum LiveNodeKind { |
| FreeVarNode(Span), |
| ExprNode(Span), |
| VarDefNode(Span), |
| ExitNode |
| } |
| |
| fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt) -> String { |
| let cm = tcx.sess.codemap(); |
| match lnk { |
| FreeVarNode(s) => { |
| format!("Free var node [{}]", cm.span_to_string(s)) |
| } |
| ExprNode(s) => { |
| format!("Expr node [{}]", cm.span_to_string(s)) |
| } |
| VarDefNode(s) => { |
| format!("Var def node [{}]", cm.span_to_string(s)) |
| } |
| ExitNode => "Exit node".to_string(), |
| } |
| } |
| |
| impl<'a, 'tcx, 'v> Visitor<'v> for IrMaps<'a, 'tcx> { |
| fn visit_fn(&mut self, fk: FnKind<'v>, fd: &'v hir::FnDecl, |
| b: &'v hir::Block, s: Span, id: NodeId) { |
| visit_fn(self, fk, fd, b, s, id); |
| } |
| fn visit_local(&mut self, l: &hir::Local) { visit_local(self, l); } |
| fn visit_expr(&mut self, ex: &Expr) { visit_expr(self, ex); } |
| fn visit_arm(&mut self, a: &hir::Arm) { visit_arm(self, a); } |
| } |
| |
| pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) { |
| let _task = tcx.dep_graph.in_task(DepNode::Liveness); |
| tcx.map.krate().visit_all_items(&mut IrMaps::new(tcx)); |
| tcx.sess.abort_if_errors(); |
| } |
| |
| impl fmt::Debug for LiveNode { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "ln({})", self.get()) |
| } |
| } |
| |
| impl fmt::Debug for Variable { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| write!(f, "v({})", self.get()) |
| } |
| } |
| |
| // ______________________________________________________________________ |
| // 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 LiveNode { |
| fn is_valid(&self) -> bool { |
| self.get() != usize::MAX |
| } |
| } |
| |
| fn invalid_node() -> LiveNode { LiveNode(usize::MAX) } |
| |
| struct CaptureInfo { |
| ln: LiveNode, |
| var_nid: NodeId |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| struct LocalInfo { |
| id: NodeId, |
| name: ast::Name |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| enum VarKind { |
| Arg(NodeId, ast::Name), |
| Local(LocalInfo), |
| ImplicitRet, |
| CleanExit |
| } |
| |
| struct IrMaps<'a, 'tcx: 'a> { |
| tcx: TyCtxt<'a, 'tcx, 'tcx>, |
| |
| num_live_nodes: usize, |
| num_vars: usize, |
| live_node_map: NodeMap<LiveNode>, |
| variable_map: NodeMap<Variable>, |
| capture_info_map: NodeMap<Rc<Vec<CaptureInfo>>>, |
| var_kinds: Vec<VarKind>, |
| lnks: Vec<LiveNodeKind>, |
| } |
| |
| impl<'a, 'tcx> IrMaps<'a, 'tcx> { |
| fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> IrMaps<'a, 'tcx> { |
| IrMaps { |
| tcx: tcx, |
| num_live_nodes: 0, |
| num_vars: 0, |
| live_node_map: NodeMap(), |
| variable_map: NodeMap(), |
| capture_info_map: NodeMap(), |
| var_kinds: Vec::new(), |
| lnks: Vec::new(), |
| } |
| } |
| |
| fn add_live_node(&mut self, lnk: LiveNodeKind) -> LiveNode { |
| let ln = LiveNode(self.num_live_nodes); |
| self.lnks.push(lnk); |
| self.num_live_nodes += 1; |
| |
| debug!("{:?} is of kind {}", ln, |
| live_node_kind_to_string(lnk, self.tcx)); |
| |
| ln |
| } |
| |
| fn add_live_node_for_node(&mut self, node_id: NodeId, lnk: LiveNodeKind) { |
| let ln = self.add_live_node(lnk); |
| self.live_node_map.insert(node_id, ln); |
| |
| debug!("{:?} is node {}", ln, node_id); |
| } |
| |
| fn add_variable(&mut self, vk: VarKind) -> Variable { |
| let v = Variable(self.num_vars); |
| self.var_kinds.push(vk); |
| self.num_vars += 1; |
| |
| match vk { |
| Local(LocalInfo { id: node_id, .. }) | Arg(node_id, _) => { |
| self.variable_map.insert(node_id, v); |
| }, |
| ImplicitRet | CleanExit => {} |
| } |
| |
| debug!("{:?} is {:?}", v, vk); |
| |
| v |
| } |
| |
| fn variable(&self, node_id: NodeId, span: Span) -> Variable { |
| match self.variable_map.get(&node_id) { |
| Some(&var) => var, |
| None => { |
| span_bug!(span, "no variable registered for id {}", node_id); |
| } |
| } |
| } |
| |
| fn variable_name(&self, var: Variable) -> String { |
| match self.var_kinds[var.get()] { |
| Local(LocalInfo { name, .. }) | Arg(_, name) => { |
| name.to_string() |
| }, |
| ImplicitRet => "<implicit-ret>".to_string(), |
| CleanExit => "<clean-exit>".to_string() |
| } |
| } |
| |
| fn set_captures(&mut self, node_id: NodeId, cs: Vec<CaptureInfo>) { |
| self.capture_info_map.insert(node_id, Rc::new(cs)); |
| } |
| |
| fn lnk(&self, ln: LiveNode) -> LiveNodeKind { |
| self.lnks[ln.get()] |
| } |
| } |
| |
| impl<'a, 'tcx, 'v> Visitor<'v> for Liveness<'a, 'tcx> { |
| fn visit_fn(&mut self, fk: FnKind<'v>, fd: &'v hir::FnDecl, |
| b: &'v hir::Block, s: Span, n: NodeId) { |
| check_fn(self, fk, fd, b, s, n); |
| } |
| fn visit_local(&mut self, l: &hir::Local) { |
| check_local(self, l); |
| } |
| fn visit_expr(&mut self, ex: &Expr) { |
| check_expr(self, ex); |
| } |
| fn visit_arm(&mut self, a: &hir::Arm) { |
| check_arm(self, a); |
| } |
| } |
| |
| fn visit_fn(ir: &mut IrMaps, |
| fk: FnKind, |
| decl: &hir::FnDecl, |
| body: &hir::Block, |
| sp: Span, |
| id: ast::NodeId) { |
| debug!("visit_fn"); |
| |
| // swap in a new set of IR maps for this function body: |
| let mut fn_maps = IrMaps::new(ir.tcx); |
| |
| debug!("creating fn_maps: {:?}", &fn_maps as *const IrMaps); |
| |
| for arg in &decl.inputs { |
| pat_util::pat_bindings(&arg.pat, |_bm, arg_id, _x, path1| { |
| debug!("adding argument {}", arg_id); |
| let name = path1.node; |
| fn_maps.add_variable(Arg(arg_id, name)); |
| }) |
| }; |
| |
| // gather up the various local variables, significant expressions, |
| // and so forth: |
| intravisit::walk_fn(&mut fn_maps, fk, decl, body, sp, id); |
| |
| // Special nodes and variables: |
| // - exit_ln represents the end of the fn, either by return or panic |
| // - implicit_ret_var is a pseudo-variable that represents |
| // an implicit return |
| let specials = Specials { |
| exit_ln: fn_maps.add_live_node(ExitNode), |
| fallthrough_ln: fn_maps.add_live_node(ExitNode), |
| no_ret_var: fn_maps.add_variable(ImplicitRet), |
| clean_exit_var: fn_maps.add_variable(CleanExit) |
| }; |
| |
| // compute liveness |
| let mut lsets = Liveness::new(&mut fn_maps, specials); |
| let entry_ln = lsets.compute(decl, body); |
| |
| // check for various error conditions |
| lsets.visit_block(body); |
| lsets.check_ret(id, sp, fk, entry_ln, body); |
| lsets.warn_about_unused_args(decl, entry_ln); |
| } |
| |
| fn visit_local(ir: &mut IrMaps, local: &hir::Local) { |
| pat_util::pat_bindings(&local.pat, |_, p_id, sp, path1| { |
| debug!("adding local variable {}", p_id); |
| let name = path1.node; |
| ir.add_live_node_for_node(p_id, VarDefNode(sp)); |
| ir.add_variable(Local(LocalInfo { |
| id: p_id, |
| name: name |
| })); |
| }); |
| intravisit::walk_local(ir, local); |
| } |
| |
| fn visit_arm(ir: &mut IrMaps, arm: &hir::Arm) { |
| for pat in &arm.pats { |
| pat_util::pat_bindings(&pat, |bm, p_id, sp, path1| { |
| debug!("adding local variable {} from match with bm {:?}", |
| p_id, bm); |
| let name = path1.node; |
| ir.add_live_node_for_node(p_id, VarDefNode(sp)); |
| ir.add_variable(Local(LocalInfo { |
| id: p_id, |
| name: name |
| })); |
| }) |
| } |
| intravisit::walk_arm(ir, arm); |
| } |
| |
| fn visit_expr(ir: &mut IrMaps, expr: &Expr) { |
| match expr.node { |
| // live nodes required for uses or definitions of variables: |
| hir::ExprPath(..) => { |
| let def = ir.tcx.expect_def(expr.id); |
| debug!("expr {}: path that leads to {:?}", expr.id, def); |
| if let Def::Local(..) = def { |
| ir.add_live_node_for_node(expr.id, ExprNode(expr.span)); |
| } |
| intravisit::walk_expr(ir, expr); |
| } |
| hir::ExprClosure(..) => { |
| // Interesting control flow (for loops can contain labeled |
| // breaks or continues) |
| ir.add_live_node_for_node(expr.id, ExprNode(expr.span)); |
| |
| // 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 mut call_caps = Vec::new(); |
| ir.tcx.with_freevars(expr.id, |freevars| { |
| for fv in freevars { |
| if let Def::Local(_, rv) = fv.def { |
| let fv_ln = ir.add_live_node(FreeVarNode(fv.span)); |
| call_caps.push(CaptureInfo {ln: fv_ln, |
| var_nid: rv}); |
| } |
| } |
| }); |
| ir.set_captures(expr.id, call_caps); |
| |
| intravisit::walk_expr(ir, expr); |
| } |
| |
| // live nodes required for interesting control flow: |
| hir::ExprIf(..) | hir::ExprMatch(..) | hir::ExprWhile(..) | hir::ExprLoop(..) => { |
| ir.add_live_node_for_node(expr.id, ExprNode(expr.span)); |
| intravisit::walk_expr(ir, expr); |
| } |
| hir::ExprBinary(op, _, _) if op.node.is_lazy() => { |
| ir.add_live_node_for_node(expr.id, ExprNode(expr.span)); |
| intravisit::walk_expr(ir, expr); |
| } |
| |
| // otherwise, live nodes are not required: |
| hir::ExprIndex(..) | hir::ExprField(..) | hir::ExprTupField(..) | |
| hir::ExprVec(..) | hir::ExprCall(..) | hir::ExprMethodCall(..) | |
| hir::ExprTup(..) | hir::ExprBinary(..) | hir::ExprAddrOf(..) | |
| hir::ExprCast(..) | hir::ExprUnary(..) | hir::ExprBreak(_) | |
| hir::ExprAgain(_) | hir::ExprLit(_) | hir::ExprRet(..) | |
| hir::ExprBlock(..) | hir::ExprAssign(..) | hir::ExprAssignOp(..) | |
| hir::ExprStruct(..) | hir::ExprRepeat(..) | |
| hir::ExprInlineAsm(..) | hir::ExprBox(..) | |
| hir::ExprType(..) => { |
| intravisit::walk_expr(ir, expr); |
| } |
| } |
| } |
| |
| // ______________________________________________________________________ |
| // Computing liveness sets |
| // |
| // Actually we compute just a bit more than just liveness, but we use |
| // the same basic propagation framework in all cases. |
| |
| #[derive(Clone, Copy)] |
| struct Users { |
| reader: LiveNode, |
| writer: LiveNode, |
| used: bool |
| } |
| |
| fn invalid_users() -> Users { |
| Users { |
| reader: invalid_node(), |
| writer: invalid_node(), |
| used: false |
| } |
| } |
| |
| #[derive(Copy, Clone)] |
| struct Specials { |
| exit_ln: LiveNode, |
| fallthrough_ln: LiveNode, |
| no_ret_var: Variable, |
| clean_exit_var: Variable |
| } |
| |
| const ACC_READ: u32 = 1; |
| const ACC_WRITE: u32 = 2; |
| const ACC_USE: u32 = 4; |
| |
| struct Liveness<'a, 'tcx: 'a> { |
| ir: &'a mut IrMaps<'a, 'tcx>, |
| s: Specials, |
| successors: Vec<LiveNode>, |
| users: Vec<Users>, |
| // The list of node IDs for the nested loop scopes |
| // we're in. |
| loop_scope: Vec<NodeId>, |
| // mappings from loop node ID to LiveNode |
| // ("break" label should map to loop node ID, |
| // it probably doesn't now) |
| break_ln: NodeMap<LiveNode>, |
| cont_ln: NodeMap<LiveNode> |
| } |
| |
| impl<'a, 'tcx> Liveness<'a, 'tcx> { |
| fn new(ir: &'a mut IrMaps<'a, 'tcx>, specials: Specials) -> Liveness<'a, 'tcx> { |
| let num_live_nodes = ir.num_live_nodes; |
| let num_vars = ir.num_vars; |
| Liveness { |
| ir: ir, |
| s: specials, |
| successors: vec![invalid_node(); num_live_nodes], |
| users: vec![invalid_users(); num_live_nodes * num_vars], |
| loop_scope: Vec::new(), |
| break_ln: NodeMap(), |
| cont_ln: NodeMap(), |
| } |
| } |
| |
| fn live_node(&self, node_id: NodeId, span: Span) -> LiveNode { |
| match self.ir.live_node_map.get(&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. |
| span_bug!( |
| span, |
| "no live node registered for node {}", |
| node_id); |
| } |
| } |
| } |
| |
| fn variable(&self, node_id: NodeId, span: Span) -> Variable { |
| self.ir.variable(node_id, span) |
| } |
| |
| fn pat_bindings<F>(&mut self, pat: &hir::Pat, mut f: F) where |
| F: FnMut(&mut Liveness<'a, 'tcx>, LiveNode, Variable, Span, NodeId), |
| { |
| pat_util::pat_bindings(pat, |_bm, p_id, sp, _n| { |
| let ln = self.live_node(p_id, sp); |
| let var = self.variable(p_id, sp); |
| f(self, ln, var, sp, p_id); |
| }) |
| } |
| |
| fn arm_pats_bindings<F>(&mut self, pat: Option<&hir::Pat>, f: F) where |
| F: FnMut(&mut Liveness<'a, 'tcx>, LiveNode, Variable, Span, NodeId), |
| { |
| if let Some(pat) = pat { |
| self.pat_bindings(pat, f); |
| } |
| } |
| |
| fn define_bindings_in_pat(&mut self, pat: &hir::Pat, succ: LiveNode) |
| -> LiveNode { |
| self.define_bindings_in_arm_pats(Some(pat), succ) |
| } |
| |
| fn define_bindings_in_arm_pats(&mut self, pat: Option<&hir::Pat>, succ: LiveNode) |
| -> LiveNode { |
| let mut succ = succ; |
| self.arm_pats_bindings(pat, |this, ln, var, _sp, _id| { |
| this.init_from_succ(ln, succ); |
| this.define(ln, var); |
| succ = ln; |
| }); |
| succ |
| } |
| |
| fn idx(&self, ln: LiveNode, var: Variable) -> usize { |
| ln.get() * self.ir.num_vars + var.get() |
| } |
| |
| fn live_on_entry(&self, ln: LiveNode, var: Variable) |
| -> Option<LiveNodeKind> { |
| assert!(ln.is_valid()); |
| let reader = self.users[self.idx(ln, var)].reader; |
| if reader.is_valid() {Some(self.ir.lnk(reader))} else {None} |
| } |
| |
| /* |
| Is this variable live on entry to any of its successor nodes? |
| */ |
| fn live_on_exit(&self, ln: LiveNode, var: Variable) |
| -> Option<LiveNodeKind> { |
| let successor = self.successors[ln.get()]; |
| self.live_on_entry(successor, var) |
| } |
| |
| fn used_on_entry(&self, ln: LiveNode, var: Variable) -> bool { |
| assert!(ln.is_valid()); |
| self.users[self.idx(ln, var)].used |
| } |
| |
| fn assigned_on_entry(&self, ln: LiveNode, var: Variable) |
| -> Option<LiveNodeKind> { |
| 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(&self, ln: LiveNode, var: Variable) |
| -> Option<LiveNodeKind> { |
| let successor = self.successors[ln.get()]; |
| self.assigned_on_entry(successor, var) |
| } |
| |
| fn indices2<F>(&mut self, ln: LiveNode, succ_ln: LiveNode, mut op: F) where |
| F: FnMut(&mut Liveness<'a, 'tcx>, usize, usize), |
| { |
| let node_base_idx = self.idx(ln, Variable(0)); |
| let succ_base_idx = self.idx(succ_ln, Variable(0)); |
| for var_idx in 0..self.ir.num_vars { |
| op(self, node_base_idx + var_idx, succ_base_idx + var_idx); |
| } |
| } |
| |
| fn write_vars<F>(&self, |
| wr: &mut Write, |
| ln: LiveNode, |
| mut test: F) |
| -> io::Result<()> where |
| F: FnMut(usize) -> LiveNode, |
| { |
| let node_base_idx = self.idx(ln, Variable(0)); |
| for var_idx in 0..self.ir.num_vars { |
| let idx = node_base_idx + var_idx; |
| if test(idx).is_valid() { |
| write!(wr, " {:?}", Variable(var_idx))?; |
| } |
| } |
| Ok(()) |
| } |
| |
| fn find_loop_scope(&self, |
| opt_label: Option<ast::Name>, |
| id: NodeId, |
| sp: Span) |
| -> NodeId { |
| match opt_label { |
| Some(_) => { |
| // Refers to a labeled loop. Use the results of resolve |
| // to find with one |
| match self.ir.tcx.expect_def(id) { |
| Def::Label(loop_id) => loop_id, |
| _ => span_bug!(sp, "label on break/loop \ |
| doesn't refer to a loop") |
| } |
| } |
| None => { |
| // Vanilla 'break' or 'loop', so use the enclosing |
| // loop scope |
| if self.loop_scope.is_empty() { |
| span_bug!(sp, "break outside loop"); |
| } else { |
| *self.loop_scope.last().unwrap() |
| } |
| } |
| } |
| } |
| |
| #[allow(unused_must_use)] |
| fn ln_str(&self, ln: LiveNode) -> String { |
| let mut wr = Vec::new(); |
| { |
| let wr = &mut wr as &mut Write; |
| write!(wr, "[ln({:?}) of kind {:?} reads", ln.get(), self.ir.lnk(ln)); |
| self.write_vars(wr, ln, |idx| self.users[idx].reader); |
| write!(wr, " writes"); |
| self.write_vars(wr, ln, |idx| self.users[idx].writer); |
| write!(wr, " precedes {:?}]", self.successors[ln.get()]); |
| } |
| String::from_utf8(wr).unwrap() |
| } |
| |
| fn init_empty(&mut self, ln: LiveNode, succ_ln: LiveNode) { |
| self.successors[ln.get()] = 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(&mut self, ln: LiveNode, succ_ln: LiveNode) { |
| // more efficient version of init_empty() / merge_from_succ() |
| self.successors[ln.get()] = succ_ln; |
| |
| self.indices2(ln, succ_ln, |this, idx, succ_idx| { |
| this.users[idx] = this.users[succ_idx] |
| }); |
| debug!("init_from_succ(ln={}, succ={})", |
| self.ln_str(ln), self.ln_str(succ_ln)); |
| } |
| |
| fn merge_from_succ(&mut self, |
| ln: LiveNode, |
| succ_ln: LiveNode, |
| first_merge: bool) |
| -> bool { |
| if ln == succ_ln { return false; } |
| |
| let mut changed = false; |
| self.indices2(ln, succ_ln, |this, idx, succ_idx| { |
| changed |= copy_if_invalid(this.users[succ_idx].reader, |
| &mut this.users[idx].reader); |
| changed |= copy_if_invalid(this.users[succ_idx].writer, |
| &mut this.users[idx].writer); |
| if this.users[succ_idx].used && !this.users[idx].used { |
| this.users[idx].used = true; |
| changed = true; |
| } |
| }); |
| |
| debug!("merge_from_succ(ln={:?}, succ={}, first_merge={}, changed={})", |
| ln, self.ln_str(succ_ln), first_merge, changed); |
| return changed; |
| |
| fn copy_if_invalid(src: LiveNode, dst: &mut LiveNode) -> bool { |
| if src.is_valid() && !dst.is_valid() { |
| *dst = src; |
| true |
| } else { |
| 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(&mut self, writer: LiveNode, var: Variable) { |
| let idx = self.idx(writer, var); |
| self.users[idx].reader = invalid_node(); |
| self.users[idx].writer = invalid_node(); |
| |
| debug!("{:?} defines {:?} (idx={}): {}", writer, var, |
| idx, self.ln_str(writer)); |
| } |
| |
| // Either read, write, or both depending on the acc bitset |
| fn acc(&mut self, ln: LiveNode, var: Variable, acc: u32) { |
| debug!("{:?} accesses[{:x}] {:?}: {}", |
| ln, acc, var, self.ln_str(ln)); |
| |
| let idx = self.idx(ln, var); |
| let user = &mut self.users[idx]; |
| |
| if (acc & ACC_WRITE) != 0 { |
| 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) != 0 { |
| user.reader = ln; |
| } |
| |
| if (acc & ACC_USE) != 0 { |
| user.used = true; |
| } |
| } |
| |
| // _______________________________________________________________________ |
| |
| fn compute(&mut self, decl: &hir::FnDecl, body: &hir::Block) -> LiveNode { |
| // if there is a `break` or `again` at the top level, then it's |
| // effectively a return---this only occurs in `for` loops, |
| // where the body is really a closure. |
| |
| debug!("compute: using id for block, {}", block_to_string(body)); |
| |
| let exit_ln = self.s.exit_ln; |
| let entry_ln: LiveNode = |
| self.with_loop_nodes(body.id, exit_ln, exit_ln, |
| |this| this.propagate_through_fn_block(decl, body)); |
| |
| // hack to skip the loop unless debug! is enabled: |
| debug!("^^ liveness computation results for body {} (entry={:?})", |
| { |
| for ln_idx in 0..self.ir.num_live_nodes { |
| debug!("{:?}", self.ln_str(LiveNode(ln_idx))); |
| } |
| body.id |
| }, |
| entry_ln); |
| |
| entry_ln |
| } |
| |
| fn propagate_through_fn_block(&mut self, _: &hir::FnDecl, blk: &hir::Block) |
| -> LiveNode { |
| // the fallthrough exit is only for those cases where we do not |
| // explicitly return: |
| let s = self.s; |
| self.init_from_succ(s.fallthrough_ln, s.exit_ln); |
| if blk.expr.is_none() { |
| self.acc(s.fallthrough_ln, s.no_ret_var, ACC_READ) |
| } |
| self.acc(s.fallthrough_ln, s.clean_exit_var, ACC_READ); |
| |
| self.propagate_through_block(blk, s.fallthrough_ln) |
| } |
| |
| fn propagate_through_block(&mut self, blk: &hir::Block, succ: LiveNode) |
| -> LiveNode { |
| let succ = self.propagate_through_opt_expr(blk.expr.as_ref().map(|e| &**e), succ); |
| blk.stmts.iter().rev().fold(succ, |succ, stmt| { |
| self.propagate_through_stmt(stmt, succ) |
| }) |
| } |
| |
| fn propagate_through_stmt(&mut self, stmt: &hir::Stmt, succ: LiveNode) |
| -> LiveNode { |
| match stmt.node { |
| hir::StmtDecl(ref decl, _) => { |
| self.propagate_through_decl(&decl, succ) |
| } |
| |
| hir::StmtExpr(ref expr, _) | hir::StmtSemi(ref expr, _) => { |
| self.propagate_through_expr(&expr, succ) |
| } |
| } |
| } |
| |
| fn propagate_through_decl(&mut self, decl: &hir::Decl, succ: LiveNode) |
| -> LiveNode { |
| match decl.node { |
| hir::DeclLocal(ref local) => { |
| self.propagate_through_local(&local, succ) |
| } |
| hir::DeclItem(_) => succ, |
| } |
| } |
| |
| fn propagate_through_local(&mut self, local: &hir::Local, succ: LiveNode) |
| -> LiveNode { |
| // 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 succ = self.propagate_through_opt_expr(local.init.as_ref().map(|e| &**e), succ); |
| self.define_bindings_in_pat(&local.pat, succ) |
| } |
| |
| fn propagate_through_exprs(&mut self, exprs: &[P<Expr>], succ: LiveNode) |
| -> LiveNode { |
| exprs.iter().rev().fold(succ, |succ, expr| { |
| self.propagate_through_expr(&expr, succ) |
| }) |
| } |
| |
| fn propagate_through_opt_expr(&mut self, |
| opt_expr: Option<&Expr>, |
| succ: LiveNode) |
| -> LiveNode { |
| opt_expr.map_or(succ, |expr| self.propagate_through_expr(expr, succ)) |
| } |
| |
| fn propagate_through_expr(&mut self, expr: &Expr, succ: LiveNode) |
| -> LiveNode { |
| debug!("propagate_through_expr: {}", expr_to_string(expr)); |
| |
| match expr.node { |
| // Interesting cases with control flow or which gen/kill |
| |
| hir::ExprPath(..) => { |
| self.access_path(expr, succ, ACC_READ | ACC_USE) |
| } |
| |
| hir::ExprField(ref e, _) => { |
| self.propagate_through_expr(&e, succ) |
| } |
| |
| hir::ExprTupField(ref e, _) => { |
| self.propagate_through_expr(&e, succ) |
| } |
| |
| hir::ExprClosure(_, _, ref blk, _) => { |
| debug!("{} is an ExprClosure", |
| expr_to_string(expr)); |
| |
| /* |
| The next-node for a break is the successor of the entire |
| loop. The next-node for a continue is the top of this loop. |
| */ |
| let node = self.live_node(expr.id, expr.span); |
| self.with_loop_nodes(blk.id, succ, node, |this| { |
| |
| // the construction of a closure itself is not important, |
| // but we have to consider the closed over variables. |
| let caps = match this.ir.capture_info_map.get(&expr.id) { |
| Some(caps) => caps.clone(), |
| None => { |
| span_bug!(expr.span, "no registered caps"); |
| } |
| }; |
| caps.iter().rev().fold(succ, |succ, cap| { |
| this.init_from_succ(cap.ln, succ); |
| let var = this.variable(cap.var_nid, expr.span); |
| this.acc(cap.ln, var, ACC_READ | ACC_USE); |
| cap.ln |
| }) |
| }) |
| } |
| |
| hir::ExprIf(ref cond, ref then, ref els) => { |
| // |
| // (cond) |
| // | |
| // v |
| // (expr) |
| // / \ |
| // | | |
| // v v |
| // (then)(els) |
| // | | |
| // v v |
| // ( succ ) |
| // |
| let else_ln = self.propagate_through_opt_expr(els.as_ref().map(|e| &**e), 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) |
| } |
| |
| hir::ExprWhile(ref cond, ref blk, _) => { |
| self.propagate_through_loop(expr, WhileLoop(&cond), &blk, succ) |
| } |
| |
| // Note that labels have been resolved, so we don't need to look |
| // at the label ident |
| hir::ExprLoop(ref blk, _) => { |
| self.propagate_through_loop(expr, LoopLoop, &blk, succ) |
| } |
| |
| hir::ExprMatch(ref e, ref 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 arm in arms { |
| let body_succ = |
| self.propagate_through_expr(&arm.body, succ); |
| let guard_succ = |
| self.propagate_through_opt_expr(arm.guard.as_ref().map(|e| &**e), body_succ); |
| // only consider the first pattern; any later patterns must have |
| // the same bindings, and we also consider the first pattern to be |
| // the "authoritative" set of ids |
| let arm_succ = |
| self.define_bindings_in_arm_pats(arm.pats.first().map(|p| &**p), |
| guard_succ); |
| self.merge_from_succ(ln, arm_succ, first_merge); |
| first_merge = false; |
| }; |
| self.propagate_through_expr(&e, ln) |
| } |
| |
| hir::ExprRet(ref o_e) => { |
| // ignore succ and subst exit_ln: |
| let exit_ln = self.s.exit_ln; |
| self.propagate_through_opt_expr(o_e.as_ref().map(|e| &**e), exit_ln) |
| } |
| |
| hir::ExprBreak(opt_label) => { |
| // Find which label this break jumps to |
| let sc = self.find_loop_scope(opt_label.map(|l| l.node), expr.id, expr.span); |
| |
| // Now that we know the label we're going to, |
| // look it up in the break loop nodes table |
| |
| match self.break_ln.get(&sc) { |
| Some(&b) => b, |
| None => span_bug!(expr.span, "break to unknown label") |
| } |
| } |
| |
| hir::ExprAgain(opt_label) => { |
| // Find which label this expr continues to |
| let sc = self.find_loop_scope(opt_label.map(|l| l.node), expr.id, expr.span); |
| |
| // Now that we know the label we're going to, |
| // look it up in the continue loop nodes table |
| |
| match self.cont_ln.get(&sc) { |
| Some(&b) => b, |
| None => span_bug!(expr.span, "loop to unknown label") |
| } |
| } |
| |
| hir::ExprAssign(ref l, ref 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) |
| } |
| |
| hir::ExprAssignOp(_, ref l, ref r) => { |
| // an overloaded assign op is like a method call |
| if self.ir.tcx.is_method_call(expr.id) { |
| let succ = self.propagate_through_expr(&l, succ); |
| self.propagate_through_expr(&r, succ) |
| } else { |
| // 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 |
| |
| hir::ExprVec(ref exprs) => { |
| self.propagate_through_exprs(&exprs[..], succ) |
| } |
| |
| hir::ExprRepeat(ref element, ref count) => { |
| let succ = self.propagate_through_expr(&count, succ); |
| self.propagate_through_expr(&element, succ) |
| } |
| |
| hir::ExprStruct(_, ref fields, ref with_expr) => { |
| let succ = self.propagate_through_opt_expr(with_expr.as_ref().map(|e| &**e), succ); |
| fields.iter().rev().fold(succ, |succ, field| { |
| self.propagate_through_expr(&field.expr, succ) |
| }) |
| } |
| |
| hir::ExprCall(ref f, ref args) => { |
| let diverges = !self.ir.tcx.is_method_call(expr.id) && |
| self.ir.tcx.expr_ty_adjusted(&f).fn_ret().diverges(); |
| let succ = if diverges { |
| self.s.exit_ln |
| } else { |
| succ |
| }; |
| let succ = self.propagate_through_exprs(&args[..], succ); |
| self.propagate_through_expr(&f, succ) |
| } |
| |
| hir::ExprMethodCall(_, _, ref args) => { |
| let method_call = ty::MethodCall::expr(expr.id); |
| let method_ty = self.ir.tcx.tables.borrow().method_map[&method_call].ty; |
| let succ = if method_ty.fn_ret().diverges() { |
| self.s.exit_ln |
| } else { |
| succ |
| }; |
| self.propagate_through_exprs(&args[..], succ) |
| } |
| |
| hir::ExprTup(ref exprs) => { |
| self.propagate_through_exprs(&exprs[..], succ) |
| } |
| |
| hir::ExprBinary(op, ref l, ref r) if op.node.is_lazy() => { |
| 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) |
| } |
| |
| hir::ExprIndex(ref l, ref r) | |
| hir::ExprBinary(_, ref l, ref r) => { |
| let r_succ = self.propagate_through_expr(&r, succ); |
| self.propagate_through_expr(&l, r_succ) |
| } |
| |
| hir::ExprBox(ref e) | |
| hir::ExprAddrOf(_, ref e) | |
| hir::ExprCast(ref e, _) | |
| hir::ExprType(ref e, _) | |
| hir::ExprUnary(_, ref e) => { |
| self.propagate_through_expr(&e, succ) |
| } |
| |
| hir::ExprInlineAsm(ref ia, ref outputs, ref inputs) => { |
| let succ = ia.outputs.iter().zip(outputs).rev().fold(succ, |succ, (o, output)| { |
| // see comment on lvalues |
| // in propagate_through_lvalue_components() |
| if o.is_indirect { |
| self.propagate_through_expr(output, succ) |
| } else { |
| let acc = if o.is_rw { ACC_WRITE|ACC_READ } else { ACC_WRITE }; |
| let succ = self.write_lvalue(output, succ, acc); |
| self.propagate_through_lvalue_components(output, succ) |
| } |
| }); |
| |
| // Inputs are executed first. Propagate last because of rev order |
| self.propagate_through_exprs(inputs, succ) |
| } |
| |
| hir::ExprLit(..) => { |
| succ |
| } |
| |
| hir::ExprBlock(ref blk) => { |
| self.propagate_through_block(&blk, succ) |
| } |
| } |
| } |
| |
| fn propagate_through_lvalue_components(&mut self, |
| expr: &Expr, |
| succ: LiveNode) |
| -> LiveNode { |
| // # 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. |
| // |
| // 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 a local variable/argument `x`. In |
| // these cases, the link_node where the write occurs is linked |
| // to node id of `x`. 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. |
| |
| match expr.node { |
| hir::ExprPath(..) => succ, |
| hir::ExprField(ref e, _) => self.propagate_through_expr(&e, succ), |
| hir::ExprTupField(ref e, _) => self.propagate_through_expr(&e, succ), |
| _ => self.propagate_through_expr(expr, succ) |
| } |
| } |
| |
| // see comment on propagate_through_lvalue() |
| fn write_lvalue(&mut self, expr: &Expr, succ: LiveNode, acc: u32) |
| -> LiveNode { |
| match expr.node { |
| hir::ExprPath(..) => { |
| self.access_path(expr, succ, acc) |
| } |
| |
| // 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(&mut self, expr: &Expr, succ: LiveNode, acc: u32) |
| -> LiveNode { |
| match self.ir.tcx.expect_def(expr.id) { |
| Def::Local(_, nid) => { |
| let ln = self.live_node(expr.id, expr.span); |
| if acc != 0 { |
| self.init_from_succ(ln, succ); |
| let var = self.variable(nid, expr.span); |
| self.acc(ln, var, acc); |
| } |
| ln |
| } |
| _ => succ |
| } |
| } |
| |
| fn propagate_through_loop(&mut self, |
| expr: &Expr, |
| kind: LoopKind, |
| body: &hir::Block, |
| succ: LiveNode) |
| -> LiveNode { |
| |
| /* |
| |
| 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); |
| match kind { |
| LoopLoop => {} |
| _ => { |
| // If this is not a `loop` loop, 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; |
| } |
| } |
| debug!("propagate_through_loop: using id for loop body {} {}", |
| expr.id, block_to_string(body)); |
| |
| let cond_ln = match kind { |
| LoopLoop => ln, |
| WhileLoop(ref cond) => self.propagate_through_expr(&cond, ln), |
| }; |
| let body_ln = self.with_loop_nodes(expr.id, succ, ln, |this| { |
| this.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; |
| |
| let new_cond_ln = match kind { |
| LoopLoop => ln, |
| WhileLoop(ref cond) => { |
| self.propagate_through_expr(&cond, ln) |
| } |
| }; |
| assert!(cond_ln == new_cond_ln); |
| assert!(body_ln == self.with_loop_nodes(expr.id, succ, ln, |
| |this| this.propagate_through_block(body, cond_ln))); |
| } |
| |
| cond_ln |
| } |
| |
| fn with_loop_nodes<R, F>(&mut self, |
| loop_node_id: NodeId, |
| break_ln: LiveNode, |
| cont_ln: LiveNode, |
| f: F) |
| -> R where |
| F: FnOnce(&mut Liveness<'a, 'tcx>) -> R, |
| { |
| debug!("with_loop_nodes: {} {}", loop_node_id, break_ln.get()); |
| self.loop_scope.push(loop_node_id); |
| self.break_ln.insert(loop_node_id, break_ln); |
| self.cont_ln.insert(loop_node_id, cont_ln); |
| let r = f(self); |
| self.loop_scope.pop(); |
| r |
| } |
| } |
| |
| // _______________________________________________________________________ |
| // Checking for error conditions |
| |
| fn check_local(this: &mut Liveness, local: &hir::Local) { |
| match local.init { |
| Some(_) => { |
| this.warn_about_unused_or_dead_vars_in_pat(&local.pat); |
| }, |
| None => { |
| this.pat_bindings(&local.pat, |this, ln, var, sp, id| { |
| this.warn_about_unused(sp, id, ln, var); |
| }) |
| } |
| } |
| |
| intravisit::walk_local(this, local); |
| } |
| |
| fn check_arm(this: &mut Liveness, arm: &hir::Arm) { |
| // only consider the first pattern; any later patterns must have |
| // the same bindings, and we also consider the first pattern to be |
| // the "authoritative" set of ids |
| this.arm_pats_bindings(arm.pats.first().map(|p| &**p), |this, ln, var, sp, id| { |
| this.warn_about_unused(sp, id, ln, var); |
| }); |
| intravisit::walk_arm(this, arm); |
| } |
| |
| fn check_expr(this: &mut Liveness, expr: &Expr) { |
| match expr.node { |
| hir::ExprAssign(ref l, _) => { |
| this.check_lvalue(&l); |
| |
| intravisit::walk_expr(this, expr); |
| } |
| |
| hir::ExprAssignOp(_, ref l, _) => { |
| if !this.ir.tcx.is_method_call(expr.id) { |
| this.check_lvalue(&l); |
| } |
| |
| intravisit::walk_expr(this, expr); |
| } |
| |
| hir::ExprInlineAsm(ref ia, ref outputs, ref inputs) => { |
| for input in inputs { |
| this.visit_expr(input); |
| } |
| |
| // Output operands must be lvalues |
| for (o, output) in ia.outputs.iter().zip(outputs) { |
| if !o.is_indirect { |
| this.check_lvalue(output); |
| } |
| this.visit_expr(output); |
| } |
| |
| intravisit::walk_expr(this, expr); |
| } |
| |
| // no correctness conditions related to liveness |
| hir::ExprCall(..) | hir::ExprMethodCall(..) | hir::ExprIf(..) | |
| hir::ExprMatch(..) | hir::ExprWhile(..) | hir::ExprLoop(..) | |
| hir::ExprIndex(..) | hir::ExprField(..) | hir::ExprTupField(..) | |
| hir::ExprVec(..) | hir::ExprTup(..) | hir::ExprBinary(..) | |
| hir::ExprCast(..) | hir::ExprUnary(..) | hir::ExprRet(..) | |
| hir::ExprBreak(..) | hir::ExprAgain(..) | hir::ExprLit(_) | |
| hir::ExprBlock(..) | hir::ExprAddrOf(..) | |
| hir::ExprStruct(..) | hir::ExprRepeat(..) | |
| hir::ExprClosure(..) | hir::ExprPath(..) | hir::ExprBox(..) | |
| hir::ExprType(..) => { |
| intravisit::walk_expr(this, expr); |
| } |
| } |
| } |
| |
| fn check_fn(_v: &Liveness, |
| _fk: FnKind, |
| _decl: &hir::FnDecl, |
| _body: &hir::Block, |
| _sp: Span, |
| _id: NodeId) { |
| // do not check contents of nested fns |
| } |
| |
| impl<'a, 'tcx> Liveness<'a, 'tcx> { |
| fn fn_ret(&self, id: NodeId) -> ty::PolyFnOutput<'tcx> { |
| let fn_ty = self.ir.tcx.node_id_to_type(id); |
| match fn_ty.sty { |
| ty::TyClosure(closure_def_id, substs) => |
| self.ir.tcx.closure_type(closure_def_id, substs).sig.output(), |
| _ => fn_ty.fn_ret() |
| } |
| } |
| |
| fn check_ret(&self, |
| id: NodeId, |
| sp: Span, |
| _fk: FnKind, |
| entry_ln: LiveNode, |
| body: &hir::Block) |
| { |
| // within the fn body, late-bound regions are liberated |
| // and must outlive the *call-site* of the function. |
| let fn_ret = |
| self.ir.tcx.liberate_late_bound_regions( |
| self.ir.tcx.region_maps.call_site_extent(id, body.id), |
| &self.fn_ret(id)); |
| |
| match fn_ret { |
| ty::FnConverging(t_ret) |
| if self.live_on_entry(entry_ln, self.s.no_ret_var).is_some() => { |
| |
| let param_env = ParameterEnvironment::for_item(self.ir.tcx, id); |
| let t_ret_subst = t_ret.subst(self.ir.tcx, ¶m_env.free_substs); |
| let is_nil = self.ir.tcx.infer_ctxt(None, Some(param_env), |
| ProjectionMode::Any).enter(|infcx| { |
| let cause = traits::ObligationCause::dummy(); |
| traits::fully_normalize(&infcx, cause, &t_ret_subst).unwrap().is_nil() |
| }); |
| |
| // for nil return types, it is ok to not return a value expl. |
| if !is_nil { |
| let ends_with_stmt = match body.expr { |
| None if !body.stmts.is_empty() => |
| match body.stmts.last().unwrap().node { |
| hir::StmtSemi(ref e, _) => { |
| self.ir.tcx.expr_ty(&e) == t_ret |
| }, |
| _ => false |
| }, |
| _ => false |
| }; |
| let mut err = struct_span_err!(self.ir.tcx.sess, |
| sp, |
| E0269, |
| "not all control paths return a value"); |
| if ends_with_stmt { |
| let last_stmt = body.stmts.last().unwrap(); |
| let original_span = original_sp(self.ir.tcx.sess.codemap(), |
| last_stmt.span, sp); |
| let span_semicolon = Span { |
| lo: original_span.hi - BytePos(1), |
| hi: original_span.hi, |
| expn_id: original_span.expn_id |
| }; |
| err.span_help(span_semicolon, "consider removing this semicolon:"); |
| } |
| err.emit(); |
| } |
| } |
| ty::FnDiverging |
| if self.live_on_entry(entry_ln, self.s.clean_exit_var).is_some() => { |
| span_err!(self.ir.tcx.sess, sp, E0270, |
| "computation may converge in a function marked as diverging"); |
| } |
| |
| _ => {} |
| } |
| } |
| |
| fn check_lvalue(&mut self, expr: &Expr) { |
| match expr.node { |
| hir::ExprPath(..) => { |
| if let Def::Local(_, nid) = self.ir.tcx.expect_def(expr.id) { |
| // Assignment to an immutable variable or argument: only legal |
| // if there is no later assignment. If this local is actually |
| // mutable, then check for a reassignment to flag the mutability |
| // as being used. |
| let ln = self.live_node(expr.id, expr.span); |
| let var = self.variable(nid, expr.span); |
| self.warn_about_dead_assign(expr.span, expr.id, ln, var); |
| } |
| } |
| _ => { |
| // For other kinds of lvalues, no checks are required, |
| // and any embedded expressions are actually rvalues |
| intravisit::walk_expr(self, expr); |
| } |
| } |
| } |
| |
| fn should_warn(&self, var: Variable) -> Option<String> { |
| let name = self.ir.variable_name(var); |
| if name.is_empty() || name.as_bytes()[0] == ('_' as u8) { |
| None |
| } else { |
| Some(name) |
| } |
| } |
| |
| fn warn_about_unused_args(&self, decl: &hir::FnDecl, entry_ln: LiveNode) { |
| for arg in &decl.inputs { |
| pat_util::pat_bindings(&arg.pat, |_bm, p_id, sp, path1| { |
| let var = self.variable(p_id, sp); |
| // Ignore unused self. |
| let name = path1.node; |
| if name != keywords::SelfValue.name() { |
| if !self.warn_about_unused(sp, p_id, entry_ln, var) { |
| if self.live_on_entry(entry_ln, var).is_none() { |
| self.report_dead_assign(p_id, sp, var, true); |
| } |
| } |
| } |
| }) |
| } |
| } |
| |
| fn warn_about_unused_or_dead_vars_in_pat(&mut self, pat: &hir::Pat) { |
| self.pat_bindings(pat, |this, ln, var, sp, id| { |
| if !this.warn_about_unused(sp, id, ln, var) { |
| this.warn_about_dead_assign(sp, id, ln, var); |
| } |
| }) |
| } |
| |
| fn warn_about_unused(&self, |
| sp: Span, |
| id: NodeId, |
| ln: LiveNode, |
| var: Variable) |
| -> bool { |
| if !self.used_on_entry(ln, var) { |
| let r = self.should_warn(var); |
| if let Some(name) = r { |
| |
| // annoying: for parameters in funcs like `fn(x: i32) |
| // {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.ir.tcx.sess.add_lint(lint::builtin::UNUSED_VARIABLES, id, sp, |
| format!("variable `{}` is assigned to, but never used", |
| name)); |
| } else if name != "self" { |
| self.ir.tcx.sess.add_lint(lint::builtin::UNUSED_VARIABLES, id, sp, |
| format!("unused variable: `{}`", name)); |
| } |
| } |
| true |
| } else { |
| false |
| } |
| } |
| |
| fn warn_about_dead_assign(&self, |
| sp: Span, |
| id: NodeId, |
| ln: LiveNode, |
| var: Variable) { |
| if self.live_on_exit(ln, var).is_none() { |
| self.report_dead_assign(id, sp, var, false); |
| } |
| } |
| |
| fn report_dead_assign(&self, id: NodeId, sp: Span, var: Variable, is_argument: bool) { |
| if let Some(name) = self.should_warn(var) { |
| if is_argument { |
| self.ir.tcx.sess.add_lint(lint::builtin::UNUSED_ASSIGNMENTS, id, sp, |
| format!("value passed to `{}` is never read", name)); |
| } else { |
| self.ir.tcx.sess.add_lint(lint::builtin::UNUSED_ASSIGNMENTS, id, sp, |
| format!("value assigned to `{}` is never read", name)); |
| } |
| } |
| } |
| } |