| //! 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 |
| //! IDs. |
| //! |
| //! # 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 `u32`) and the ID for a variable |
| //! is called a `variable` (another newtype'd `u32`). |
| //! |
| //! 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 `RWU` struct |
| //! |
| //! At each live node `N`, we track three pieces of information for each |
| //! variable `V` (these are encapsulated in the `RWU` 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 |
| //! |
| //! - `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::LiveNodeKind::*; |
| use self::VarKind::*; |
| |
| use rustc::hir; |
| use rustc::hir::{Expr, HirId}; |
| use rustc::hir::def::*; |
| use rustc::hir::def_id::DefId; |
| use rustc::hir::intravisit::{self, Visitor, FnKind, NestedVisitorMap}; |
| use rustc::hir::Node; |
| use rustc::hir::ptr::P; |
| use rustc::ty::{self, TyCtxt}; |
| use rustc::ty::query::Providers; |
| use rustc::lint; |
| use rustc::util::nodemap::{HirIdMap, HirIdSet}; |
| |
| use errors::Applicability; |
| use rustc_data_structures::fx::FxIndexMap; |
| use std::collections::VecDeque; |
| use std::{fmt, u32}; |
| use std::io::prelude::*; |
| use std::io; |
| use std::rc::Rc; |
| use syntax::ast; |
| use syntax::symbol::sym; |
| use syntax_pos::Span; |
| |
| #[derive(Copy, Clone, PartialEq)] |
| struct Variable(u32); |
| |
| #[derive(Copy, Clone, PartialEq)] |
| struct LiveNode(u32); |
| |
| impl Variable { |
| fn get(&self) -> usize { self.0 as usize } |
| } |
| |
| impl LiveNode { |
| fn get(&self) -> usize { self.0 as usize } |
| } |
| |
| #[derive(Copy, Clone, PartialEq, Debug)] |
| enum LiveNodeKind { |
| UpvarNode(Span), |
| ExprNode(Span), |
| VarDefNode(Span), |
| ExitNode |
| } |
| |
| fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String { |
| let cm = tcx.sess.source_map(); |
| match lnk { |
| UpvarNode(s) => { |
| format!("Upvar 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_owned(), |
| } |
| } |
| |
| impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> { |
| fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { |
| NestedVisitorMap::OnlyBodies(&self.tcx.hir()) |
| } |
| |
| fn visit_fn(&mut self, fk: FnKind<'tcx>, fd: &'tcx hir::FnDecl, |
| b: hir::BodyId, s: Span, id: HirId) { |
| visit_fn(self, fk, fd, b, s, id); |
| } |
| |
| fn visit_local(&mut self, l: &'tcx hir::Local) { visit_local(self, l); } |
| fn visit_expr(&mut self, ex: &'tcx Expr) { visit_expr(self, ex); } |
| fn visit_arm(&mut self, a: &'tcx hir::Arm) { visit_arm(self, a); } |
| } |
| |
| fn check_mod_liveness(tcx: TyCtxt<'_>, module_def_id: DefId) { |
| tcx.hir().visit_item_likes_in_module( |
| module_def_id, |
| &mut IrMaps::new(tcx, module_def_id).as_deep_visitor(), |
| ); |
| } |
| |
| pub fn provide(providers: &mut Providers<'_>) { |
| *providers = Providers { |
| check_mod_liveness, |
| ..*providers |
| }; |
| } |
| |
| 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.0 != u32::MAX |
| } |
| } |
| |
| fn invalid_node() -> LiveNode { LiveNode(u32::MAX) } |
| |
| struct CaptureInfo { |
| ln: LiveNode, |
| var_hid: HirId |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| struct LocalInfo { |
| id: HirId, |
| name: ast::Name, |
| is_shorthand: bool, |
| } |
| |
| #[derive(Copy, Clone, Debug)] |
| enum VarKind { |
| Param(HirId, ast::Name), |
| Local(LocalInfo), |
| CleanExit |
| } |
| |
| struct IrMaps<'tcx> { |
| tcx: TyCtxt<'tcx>, |
| body_owner: DefId, |
| num_live_nodes: usize, |
| num_vars: usize, |
| live_node_map: HirIdMap<LiveNode>, |
| variable_map: HirIdMap<Variable>, |
| capture_info_map: HirIdMap<Rc<Vec<CaptureInfo>>>, |
| var_kinds: Vec<VarKind>, |
| lnks: Vec<LiveNodeKind>, |
| } |
| |
| impl IrMaps<'tcx> { |
| fn new(tcx: TyCtxt<'tcx>, body_owner: DefId) -> IrMaps<'tcx> { |
| IrMaps { |
| tcx, |
| body_owner, |
| num_live_nodes: 0, |
| num_vars: 0, |
| live_node_map: HirIdMap::default(), |
| variable_map: HirIdMap::default(), |
| capture_info_map: Default::default(), |
| var_kinds: Vec::new(), |
| lnks: Vec::new(), |
| } |
| } |
| |
| fn add_live_node(&mut self, lnk: LiveNodeKind) -> LiveNode { |
| let ln = LiveNode(self.num_live_nodes as u32); |
| 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, hir_id: HirId, lnk: LiveNodeKind) { |
| let ln = self.add_live_node(lnk); |
| self.live_node_map.insert(hir_id, ln); |
| |
| debug!("{:?} is node {:?}", ln, hir_id); |
| } |
| |
| fn add_variable(&mut self, vk: VarKind) -> Variable { |
| let v = Variable(self.num_vars as u32); |
| self.var_kinds.push(vk); |
| self.num_vars += 1; |
| |
| match vk { |
| Local(LocalInfo { id: node_id, .. }) | Param(node_id, _) => { |
| self.variable_map.insert(node_id, v); |
| }, |
| CleanExit => {} |
| } |
| |
| debug!("{:?} is {:?}", v, vk); |
| |
| v |
| } |
| |
| fn variable(&self, hir_id: HirId, span: Span) -> Variable { |
| match self.variable_map.get(&hir_id) { |
| Some(&var) => var, |
| None => { |
| span_bug!(span, "no variable registered for id {:?}", hir_id); |
| } |
| } |
| } |
| |
| fn variable_name(&self, var: Variable) -> String { |
| match self.var_kinds[var.get()] { |
| Local(LocalInfo { name, .. }) | Param(_, name) => { |
| name.to_string() |
| }, |
| CleanExit => "<clean-exit>".to_owned() |
| } |
| } |
| |
| fn variable_is_shorthand(&self, var: Variable) -> bool { |
| match self.var_kinds[var.get()] { |
| Local(LocalInfo { is_shorthand, .. }) => is_shorthand, |
| Param(..) | CleanExit => false |
| } |
| } |
| |
| fn set_captures(&mut self, hir_id: HirId, cs: Vec<CaptureInfo>) { |
| self.capture_info_map.insert(hir_id, Rc::new(cs)); |
| } |
| |
| fn lnk(&self, ln: LiveNode) -> LiveNodeKind { |
| self.lnks[ln.get()] |
| } |
| } |
| |
| fn visit_fn<'tcx>( |
| ir: &mut IrMaps<'tcx>, |
| fk: FnKind<'tcx>, |
| decl: &'tcx hir::FnDecl, |
| body_id: hir::BodyId, |
| sp: Span, |
| id: hir::HirId, |
| ) { |
| debug!("visit_fn"); |
| |
| // swap in a new set of IR maps for this function body: |
| let def_id = ir.tcx.hir().local_def_id(id); |
| let mut fn_maps = IrMaps::new(ir.tcx, def_id); |
| |
| // Don't run unused pass for #[derive()] |
| if let FnKind::Method(..) = fk { |
| let parent = ir.tcx.hir().get_parent_item(id); |
| if let Some(Node::Item(i)) = ir.tcx.hir().find(parent) { |
| if i.attrs.iter().any(|a| a.check_name(sym::automatically_derived)) { |
| return; |
| } |
| } |
| } |
| |
| debug!("creating fn_maps: {:p}", &fn_maps); |
| |
| let body = ir.tcx.hir().body(body_id); |
| |
| for param in &body.params { |
| let is_shorthand = match param.pat.kind { |
| rustc::hir::PatKind::Struct(..) => true, |
| _ => false, |
| }; |
| param.pat.each_binding(|_bm, hir_id, _x, ident| { |
| debug!("adding parameters {:?}", hir_id); |
| let var = if is_shorthand { |
| Local(LocalInfo { |
| id: hir_id, |
| name: ident.name, |
| is_shorthand: true, |
| }) |
| } else { |
| Param(hir_id, ident.name) |
| }; |
| fn_maps.add_variable(var); |
| }) |
| }; |
| |
| // gather up the various local variables, significant expressions, |
| // and so forth: |
| intravisit::walk_fn(&mut fn_maps, fk, decl, body_id, sp, id); |
| |
| // compute liveness |
| let mut lsets = Liveness::new(&mut fn_maps, body_id); |
| let entry_ln = lsets.compute(&body.value); |
| |
| // check for various error conditions |
| lsets.visit_body(body); |
| lsets.warn_about_unused_args(body, entry_ln); |
| } |
| |
| fn add_from_pat(ir: &mut IrMaps<'_>, pat: &P<hir::Pat>) { |
| // For struct patterns, take note of which fields used shorthand |
| // (`x` rather than `x: x`). |
| let mut shorthand_field_ids = HirIdSet::default(); |
| let mut pats = VecDeque::new(); |
| pats.push_back(pat); |
| while let Some(pat) = pats.pop_front() { |
| use rustc::hir::PatKind::*; |
| match &pat.kind { |
| Binding(.., inner_pat) => { |
| pats.extend(inner_pat.iter()); |
| } |
| Struct(_, fields, _) => { |
| let ids = fields.iter().filter(|f| f.is_shorthand).map(|f| f.pat.hir_id); |
| shorthand_field_ids.extend(ids); |
| } |
| Ref(inner_pat, _) | Box(inner_pat) => { |
| pats.push_back(inner_pat); |
| } |
| TupleStruct(_, inner_pats, _) | Tuple(inner_pats, _) | Or(inner_pats) => { |
| pats.extend(inner_pats.iter()); |
| } |
| Slice(pre_pats, inner_pat, post_pats) => { |
| pats.extend(pre_pats.iter()); |
| pats.extend(inner_pat.iter()); |
| pats.extend(post_pats.iter()); |
| } |
| _ => {} |
| } |
| } |
| |
| pat.each_binding(|_, hir_id, _, ident| { |
| ir.add_live_node_for_node(hir_id, VarDefNode(ident.span)); |
| ir.add_variable(Local(LocalInfo { |
| id: hir_id, |
| name: ident.name, |
| is_shorthand: shorthand_field_ids.contains(&hir_id) |
| })); |
| }); |
| } |
| |
| fn visit_local<'tcx>(ir: &mut IrMaps<'tcx>, local: &'tcx hir::Local) { |
| add_from_pat(ir, &local.pat); |
| intravisit::walk_local(ir, local); |
| } |
| |
| fn visit_arm<'tcx>(ir: &mut IrMaps<'tcx>, arm: &'tcx hir::Arm) { |
| add_from_pat(ir, &arm.pat); |
| intravisit::walk_arm(ir, arm); |
| } |
| |
| fn visit_expr<'tcx>(ir: &mut IrMaps<'tcx>, expr: &'tcx Expr) { |
| match expr.kind { |
| // live nodes required for uses or definitions of variables: |
| hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => { |
| debug!("expr {}: path that leads to {:?}", expr.hir_id, path.res); |
| if let Res::Local(var_hir_id) = path.res { |
| let upvars = ir.tcx.upvars(ir.body_owner); |
| if !upvars.map_or(false, |upvars| upvars.contains_key(&var_hir_id)) { |
| ir.add_live_node_for_node(expr.hir_id, ExprNode(expr.span)); |
| } |
| } |
| intravisit::walk_expr(ir, expr); |
| } |
| hir::ExprKind::Closure(..) => { |
| // Interesting control flow (for loops can contain labeled |
| // breaks or continues) |
| ir.add_live_node_for_node(expr.hir_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(); |
| let closure_def_id = ir.tcx.hir().local_def_id(expr.hir_id); |
| if let Some(upvars) = ir.tcx.upvars(closure_def_id) { |
| let parent_upvars = ir.tcx.upvars(ir.body_owner); |
| call_caps.extend(upvars.iter().filter_map(|(&var_id, upvar)| { |
| let has_parent = parent_upvars |
| .map_or(false, |upvars| upvars.contains_key(&var_id)); |
| if !has_parent { |
| let upvar_ln = ir.add_live_node(UpvarNode(upvar.span)); |
| Some(CaptureInfo { ln: upvar_ln, var_hid: var_id }) |
| } else { |
| None |
| } |
| })); |
| } |
| ir.set_captures(expr.hir_id, call_caps); |
| let old_body_owner = ir.body_owner; |
| ir.body_owner = closure_def_id; |
| intravisit::walk_expr(ir, expr); |
| ir.body_owner = old_body_owner; |
| } |
| |
| // live nodes required for interesting control flow: |
| hir::ExprKind::Match(..) | |
| hir::ExprKind::Loop(..) => { |
| ir.add_live_node_for_node(expr.hir_id, ExprNode(expr.span)); |
| intravisit::walk_expr(ir, expr); |
| } |
| hir::ExprKind::Binary(op, ..) if op.node.is_lazy() => { |
| ir.add_live_node_for_node(expr.hir_id, ExprNode(expr.span)); |
| intravisit::walk_expr(ir, expr); |
| } |
| |
| // otherwise, live nodes are not required: |
| hir::ExprKind::Index(..) | |
| hir::ExprKind::Field(..) | |
| hir::ExprKind::Array(..) | |
| hir::ExprKind::Call(..) | |
| hir::ExprKind::MethodCall(..) | |
| hir::ExprKind::Tup(..) | |
| hir::ExprKind::Binary(..) | |
| hir::ExprKind::AddrOf(..) | |
| hir::ExprKind::Cast(..) | |
| hir::ExprKind::DropTemps(..) | |
| hir::ExprKind::Unary(..) | |
| hir::ExprKind::Break(..) | |
| hir::ExprKind::Continue(_) | |
| hir::ExprKind::Lit(_) | |
| hir::ExprKind::Ret(..) | |
| hir::ExprKind::Block(..) | |
| hir::ExprKind::Assign(..) | |
| hir::ExprKind::AssignOp(..) | |
| hir::ExprKind::Struct(..) | |
| hir::ExprKind::Repeat(..) | |
| hir::ExprKind::InlineAsm(..) | |
| hir::ExprKind::Box(..) | |
| hir::ExprKind::Yield(..) | |
| hir::ExprKind::Type(..) | |
| hir::ExprKind::Err | |
| hir::ExprKind::Path(hir::QPath::TypeRelative(..)) => { |
| 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 RWU { |
| reader: LiveNode, |
| writer: LiveNode, |
| used: bool |
| } |
| |
| /// Conceptually, this is like a `Vec<RWU>`. But the number of `RWU`s can get |
| /// very large, so it uses a more compact representation that takes advantage |
| /// of the fact that when the number of `RWU`s is large, most of them have an |
| /// invalid reader and an invalid writer. |
| struct RWUTable { |
| /// Each entry in `packed_rwus` is either INV_INV_FALSE, INV_INV_TRUE, or |
| /// an index into `unpacked_rwus`. In the common cases, this compacts the |
| /// 65 bits of data into 32; in the uncommon cases, it expands the 65 bits |
| /// in 96. |
| /// |
| /// More compact representations are possible -- e.g., use only 2 bits per |
| /// packed `RWU` and make the secondary table a HashMap that maps from |
| /// indices to `RWU`s -- but this one strikes a good balance between size |
| /// and speed. |
| packed_rwus: Vec<u32>, |
| unpacked_rwus: Vec<RWU>, |
| } |
| |
| // A constant representing `RWU { reader: invalid_node(); writer: invalid_node(); used: false }`. |
| const INV_INV_FALSE: u32 = u32::MAX; |
| |
| // A constant representing `RWU { reader: invalid_node(); writer: invalid_node(); used: true }`. |
| const INV_INV_TRUE: u32 = u32::MAX - 1; |
| |
| impl RWUTable { |
| fn new(num_rwus: usize) -> RWUTable { |
| Self { |
| packed_rwus: vec![INV_INV_FALSE; num_rwus], |
| unpacked_rwus: vec![], |
| } |
| } |
| |
| fn get(&self, idx: usize) -> RWU { |
| let packed_rwu = self.packed_rwus[idx]; |
| match packed_rwu { |
| INV_INV_FALSE => RWU { reader: invalid_node(), writer: invalid_node(), used: false }, |
| INV_INV_TRUE => RWU { reader: invalid_node(), writer: invalid_node(), used: true }, |
| _ => self.unpacked_rwus[packed_rwu as usize], |
| } |
| } |
| |
| fn get_reader(&self, idx: usize) -> LiveNode { |
| let packed_rwu = self.packed_rwus[idx]; |
| match packed_rwu { |
| INV_INV_FALSE | INV_INV_TRUE => invalid_node(), |
| _ => self.unpacked_rwus[packed_rwu as usize].reader, |
| } |
| } |
| |
| fn get_writer(&self, idx: usize) -> LiveNode { |
| let packed_rwu = self.packed_rwus[idx]; |
| match packed_rwu { |
| INV_INV_FALSE | INV_INV_TRUE => invalid_node(), |
| _ => self.unpacked_rwus[packed_rwu as usize].writer, |
| } |
| } |
| |
| fn get_used(&self, idx: usize) -> bool { |
| let packed_rwu = self.packed_rwus[idx]; |
| match packed_rwu { |
| INV_INV_FALSE => false, |
| INV_INV_TRUE => true, |
| _ => self.unpacked_rwus[packed_rwu as usize].used, |
| } |
| } |
| |
| #[inline] |
| fn copy_packed(&mut self, dst_idx: usize, src_idx: usize) { |
| self.packed_rwus[dst_idx] = self.packed_rwus[src_idx]; |
| } |
| |
| fn assign_unpacked(&mut self, idx: usize, rwu: RWU) { |
| if rwu.reader == invalid_node() && rwu.writer == invalid_node() { |
| // When we overwrite an indexing entry in `self.packed_rwus` with |
| // `INV_INV_{TRUE,FALSE}` we don't remove the corresponding entry |
| // from `self.unpacked_rwus`; it's not worth the effort, and we |
| // can't have entries shifting around anyway. |
| self.packed_rwus[idx] = if rwu.used { |
| INV_INV_TRUE |
| } else { |
| INV_INV_FALSE |
| } |
| } else { |
| // Add a new RWU to `unpacked_rwus` and make `packed_rwus[idx]` |
| // point to it. |
| self.packed_rwus[idx] = self.unpacked_rwus.len() as u32; |
| self.unpacked_rwus.push(rwu); |
| } |
| } |
| |
| fn assign_inv_inv(&mut self, idx: usize) { |
| self.packed_rwus[idx] = if self.get_used(idx) { |
| INV_INV_TRUE |
| } else { |
| INV_INV_FALSE |
| }; |
| } |
| } |
| |
| #[derive(Copy, Clone)] |
| struct Specials { |
| exit_ln: LiveNode, |
| fallthrough_ln: LiveNode, |
| clean_exit_var: Variable |
| } |
| |
| const ACC_READ: u32 = 1; |
| const ACC_WRITE: u32 = 2; |
| const ACC_USE: u32 = 4; |
| |
| struct Liveness<'a, 'tcx> { |
| ir: &'a mut IrMaps<'tcx>, |
| tables: &'a ty::TypeckTables<'tcx>, |
| s: Specials, |
| successors: Vec<LiveNode>, |
| rwu_table: RWUTable, |
| |
| // mappings from loop node ID to LiveNode |
| // ("break" label should map to loop node ID, |
| // it probably doesn't now) |
| break_ln: HirIdMap<LiveNode>, |
| cont_ln: HirIdMap<LiveNode>, |
| } |
| |
| impl<'a, 'tcx> Liveness<'a, 'tcx> { |
| fn new(ir: &'a mut IrMaps<'tcx>, body: hir::BodyId) -> Liveness<'a, 'tcx> { |
| // 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: ir.add_live_node(ExitNode), |
| fallthrough_ln: ir.add_live_node(ExitNode), |
| clean_exit_var: ir.add_variable(CleanExit) |
| }; |
| |
| let tables = ir.tcx.body_tables(body); |
| |
| let num_live_nodes = ir.num_live_nodes; |
| let num_vars = ir.num_vars; |
| |
| Liveness { |
| ir, |
| tables, |
| s: specials, |
| successors: vec![invalid_node(); num_live_nodes], |
| rwu_table: RWUTable::new(num_live_nodes * num_vars), |
| break_ln: Default::default(), |
| cont_ln: Default::default(), |
| } |
| } |
| |
| fn live_node(&self, hir_id: HirId, span: Span) -> LiveNode { |
| match self.ir.live_node_map.get(&hir_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 {:?}", |
| hir_id); |
| } |
| } |
| } |
| |
| fn variable(&self, hir_id: HirId, span: Span) -> Variable { |
| self.ir.variable(hir_id, span) |
| } |
| |
| fn define_bindings_in_pat(&mut self, pat: &hir::Pat, mut succ: LiveNode) -> LiveNode { |
| // In an or-pattern, 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. |
| pat.each_binding_or_first(&mut |_, hir_id, pat_sp, ident| { |
| let ln = self.live_node(hir_id, pat_sp); |
| let var = self.variable(hir_id, ident.span); |
| self.init_from_succ(ln, succ); |
| self.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.rwu_table.get_reader(self.idx(ln, var)); |
| 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.rwu_table.get_used(self.idx(ln, var)) |
| } |
| |
| fn assigned_on_entry(&self, ln: LiveNode, var: Variable) |
| -> Option<LiveNodeKind> { |
| assert!(ln.is_valid()); |
| let writer = self.rwu_table.get_writer(self.idx(ln, var)); |
| 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 dyn 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 as u32))?; |
| } |
| } |
| Ok(()) |
| } |
| |
| |
| #[allow(unused_must_use)] |
| fn ln_str(&self, ln: LiveNode) -> String { |
| let mut wr = Vec::new(); |
| { |
| let wr = &mut wr as &mut dyn Write; |
| write!(wr, "[ln({:?}) of kind {:?} reads", ln.get(), self.ir.lnk(ln)); |
| self.write_vars(wr, ln, |idx| self.rwu_table.get_reader(idx)); |
| write!(wr, " writes"); |
| self.write_vars(wr, ln, |idx| self.rwu_table.get_writer(idx)); |
| 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 RWUs here because they are all |
| // set to INV_INV_FALSE when they are created, and the sets only grow |
| // during iterations. |
| } |
| |
| 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.rwu_table.copy_packed(idx, 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| { |
| let mut rwu = this.rwu_table.get(idx); |
| let succ_rwu = this.rwu_table.get(succ_idx); |
| if succ_rwu.reader.is_valid() && !rwu.reader.is_valid() { |
| rwu.reader = succ_rwu.reader; |
| changed = true |
| } |
| |
| if succ_rwu.writer.is_valid() && !rwu.writer.is_valid() { |
| rwu.writer = succ_rwu.writer; |
| changed = true |
| } |
| |
| if succ_rwu.used && !rwu.used { |
| rwu.used = true; |
| changed = true; |
| } |
| |
| if changed { |
| this.rwu_table.assign_unpacked(idx, rwu); |
| } |
| }); |
| |
| debug!("merge_from_succ(ln={:?}, succ={}, first_merge={}, changed={})", |
| ln, self.ln_str(succ_ln), first_merge, changed); |
| return changed; |
| } |
| |
| // 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.rwu_table.assign_inv_inv(idx); |
| |
| 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 mut rwu = self.rwu_table.get(idx); |
| |
| if (acc & ACC_WRITE) != 0 { |
| rwu.reader = invalid_node(); |
| rwu.writer = ln; |
| } |
| |
| // Important: if we both read/write, must do read second |
| // or else the write will override. |
| if (acc & ACC_READ) != 0 { |
| rwu.reader = ln; |
| } |
| |
| if (acc & ACC_USE) != 0 { |
| rwu.used = true; |
| } |
| |
| self.rwu_table.assign_unpacked(idx, rwu); |
| } |
| |
| fn compute(&mut self, body: &hir::Expr) -> LiveNode { |
| debug!("compute: using id for body, {}", |
| self.ir.tcx.hir().hir_to_pretty_string(body.hir_id)); |
| |
| // 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); |
| self.acc(s.fallthrough_ln, s.clean_exit_var, ACC_READ); |
| |
| let entry_ln = self.propagate_through_expr(body, s.fallthrough_ln); |
| |
| // 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 as u32))); |
| } |
| body.hir_id |
| }, |
| entry_ln); |
| |
| entry_ln |
| } |
| |
| fn propagate_through_block(&mut self, blk: &hir::Block, succ: LiveNode) |
| -> LiveNode { |
| if blk.targeted_by_break { |
| self.break_ln.insert(blk.hir_id, succ); |
| } |
| 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.kind { |
| hir::StmtKind::Local(ref local) => { |
| // 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) |
| } |
| hir::StmtKind::Item(..) => succ, |
| hir::StmtKind::Expr(ref expr) | hir::StmtKind::Semi(ref expr) => { |
| self.propagate_through_expr(&expr, succ) |
| } |
| } |
| } |
| |
| fn propagate_through_exprs(&mut self, exprs: &[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: {}", self.ir.tcx.hir().hir_to_pretty_string(expr.hir_id)); |
| |
| match expr.kind { |
| // Interesting cases with control flow or which gen/kill |
| hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => { |
| self.access_path(expr.hir_id, path, succ, ACC_READ | ACC_USE) |
| } |
| |
| hir::ExprKind::Field(ref e, _) => { |
| self.propagate_through_expr(&e, succ) |
| } |
| |
| hir::ExprKind::Closure(..) => { |
| debug!("{} is an ExprKind::Closure", |
| self.ir.tcx.hir().hir_to_pretty_string(expr.hir_id)); |
| |
| // the construction of a closure itself is not important, |
| // but we have to consider the closed over variables. |
| let caps = self.ir.capture_info_map.get(&expr.hir_id).cloned().unwrap_or_else(|| |
| span_bug!(expr.span, "no registered caps")); |
| |
| caps.iter().rev().fold(succ, |succ, cap| { |
| self.init_from_succ(cap.ln, succ); |
| let var = self.variable(cap.var_hid, expr.span); |
| self.acc(cap.ln, var, ACC_READ | ACC_USE); |
| cap.ln |
| }) |
| } |
| |
| // Note that labels have been resolved, so we don't need to look |
| // at the label ident |
| hir::ExprKind::Loop(ref blk, _, _) => { |
| self.propagate_through_loop(expr, &blk, succ) |
| } |
| |
| hir::ExprKind::Match(ref e, ref arms, _) => { |
| // |
| // (e) |
| // | |
| // v |
| // (expr) |
| // / | \ |
| // | | | |
| // v v v |
| // (..arms..) |
| // | | | |
| // v v v |
| // ( succ ) |
| // |
| // |
| let ln = self.live_node(expr.hir_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(|hir::Guard::If(e)| &**e), |
| body_succ |
| ); |
| let arm_succ = self.define_bindings_in_pat(&arm.pat, guard_succ); |
| self.merge_from_succ(ln, arm_succ, first_merge); |
| first_merge = false; |
| }; |
| self.propagate_through_expr(&e, ln) |
| } |
| |
| hir::ExprKind::Ret(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::ExprKind::Break(label, ref opt_expr) => { |
| // Find which label this break jumps to |
| let target = match label.target_id { |
| Ok(hir_id) => self.break_ln.get(&hir_id), |
| Err(err) => span_bug!(expr.span, "loop scope error: {}", err), |
| }.cloned(); |
| |
| // Now that we know the label we're going to, |
| // look it up in the break loop nodes table |
| |
| match target { |
| Some(b) => self.propagate_through_opt_expr(opt_expr.as_ref().map(|e| &**e), b), |
| None => { |
| // FIXME: This should have been checked earlier. Once this is fixed, |
| // replace with `delay_span_bug`. (#62480) |
| self.ir.tcx.sess.struct_span_err( |
| expr.span, |
| "`break` to unknown label", |
| ).emit(); |
| errors::FatalError.raise() |
| } |
| } |
| } |
| |
| hir::ExprKind::Continue(label) => { |
| // Find which label this expr continues to |
| let sc = label.target_id.unwrap_or_else(|err| |
| span_bug!(expr.span, "loop scope error: {}", err)); |
| |
| // Now that we know the label we're going to, |
| // look it up in the continue loop nodes table |
| self.cont_ln.get(&sc).cloned().unwrap_or_else(|| |
| span_bug!(expr.span, "continue to unknown label")) |
| } |
| |
| hir::ExprKind::Assign(ref l, ref r) => { |
| // see comment on places in |
| // propagate_through_place_components() |
| let succ = self.write_place(&l, succ, ACC_WRITE); |
| let succ = self.propagate_through_place_components(&l, succ); |
| self.propagate_through_expr(&r, succ) |
| } |
| |
| hir::ExprKind::AssignOp(_, ref l, ref r) => { |
| // an overloaded assign op is like a method call |
| if self.tables.is_method_call(expr) { |
| let succ = self.propagate_through_expr(&l, succ); |
| self.propagate_through_expr(&r, succ) |
| } else { |
| // see comment on places in |
| // propagate_through_place_components() |
| let succ = self.write_place(&l, succ, ACC_WRITE|ACC_READ); |
| let succ = self.propagate_through_expr(&r, succ); |
| self.propagate_through_place_components(&l, succ) |
| } |
| } |
| |
| // Uninteresting cases: just propagate in rev exec order |
| |
| hir::ExprKind::Array(ref exprs) => { |
| self.propagate_through_exprs(exprs, succ) |
| } |
| |
| hir::ExprKind::Struct(_, 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::ExprKind::Call(ref f, ref args) => { |
| let m = self.ir.tcx.hir().get_module_parent(expr.hir_id); |
| let succ = if self.ir.tcx.is_ty_uninhabited_from(m, self.tables.expr_ty(expr)) { |
| self.s.exit_ln |
| } else { |
| succ |
| }; |
| let succ = self.propagate_through_exprs(args, succ); |
| self.propagate_through_expr(&f, succ) |
| } |
| |
| hir::ExprKind::MethodCall(.., ref args) => { |
| let m = self.ir.tcx.hir().get_module_parent(expr.hir_id); |
| let succ = if self.ir.tcx.is_ty_uninhabited_from(m, self.tables.expr_ty(expr)) { |
| self.s.exit_ln |
| } else { |
| succ |
| }; |
| |
| self.propagate_through_exprs(args, succ) |
| } |
| |
| hir::ExprKind::Tup(ref exprs) => { |
| self.propagate_through_exprs(exprs, succ) |
| } |
| |
| hir::ExprKind::Binary(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.hir_id, expr.span); |
| self.init_from_succ(ln, succ); |
| self.merge_from_succ(ln, r_succ, false); |
| |
| self.propagate_through_expr(&l, ln) |
| } |
| |
| hir::ExprKind::Index(ref l, ref r) | |
| hir::ExprKind::Binary(_, ref l, ref r) => { |
| let r_succ = self.propagate_through_expr(&r, succ); |
| self.propagate_through_expr(&l, r_succ) |
| } |
| |
| hir::ExprKind::Box(ref e) | |
| hir::ExprKind::AddrOf(_, _, ref e) | |
| hir::ExprKind::Cast(ref e, _) | |
| hir::ExprKind::Type(ref e, _) | |
| hir::ExprKind::DropTemps(ref e) | |
| hir::ExprKind::Unary(_, ref e) | |
| hir::ExprKind::Yield(ref e, _) | |
| hir::ExprKind::Repeat(ref e, _) => { |
| self.propagate_through_expr(&e, succ) |
| } |
| |
| hir::ExprKind::InlineAsm(ref asm) => { |
| let ia = &asm.inner; |
| let outputs = &asm.outputs_exprs; |
| let inputs = &asm.inputs_exprs; |
| let succ = ia.outputs.iter().zip(outputs).rev().fold(succ, |succ, (o, output)| { |
| // see comment on places |
| // in propagate_through_place_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_place(output, succ, acc); |
| self.propagate_through_place_components(output, succ) |
| } |
| }); |
| |
| // Inputs are executed first. Propagate last because of rev order |
| self.propagate_through_exprs(inputs, succ) |
| } |
| |
| hir::ExprKind::Lit(..) | hir::ExprKind::Err | |
| hir::ExprKind::Path(hir::QPath::TypeRelative(..)) => { |
| succ |
| } |
| |
| // Note that labels have been resolved, so we don't need to look |
| // at the label ident |
| hir::ExprKind::Block(ref blk, _) => { |
| self.propagate_through_block(&blk, succ) |
| } |
| } |
| } |
| |
| fn propagate_through_place_components(&mut self, |
| expr: &Expr, |
| succ: LiveNode) |
| -> LiveNode { |
| // # Places |
| // |
| // 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 place Untracked place |
| // ----------------------++----------------------- |
| // || |
| // | || | |
| // v || v |
| // (rvalue) || (rvalue) |
| // | || | |
| // v || v |
| // (write of place) || (place components) |
| // | || | |
| // v || v |
| // (succ) || (succ) |
| // || |
| // ----------------------++----------------------- |
| // |
| // I will cover the two cases in turn: |
| // |
| // # Tracked places |
| // |
| // A tracked place 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_place()` routine generates |
| // the contents of this node. There are no subcomponents to |
| // consider. |
| // |
| // # Non-tracked places |
| // |
| // These are places 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_place_components()` (this fn). |
| // |
| // # Illegal places |
| // |
| // It is still possible to observe assignments to non-places; |
| // these errors are detected in the later pass borrowck. We |
| // just ignore such cases and treat them as reads. |
| |
| match expr.kind { |
| hir::ExprKind::Path(_) => succ, |
| hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(&e, succ), |
| _ => self.propagate_through_expr(expr, succ) |
| } |
| } |
| |
| // see comment on propagate_through_place() |
| fn write_place(&mut self, expr: &Expr, succ: LiveNode, acc: u32) -> LiveNode { |
| match expr.kind { |
| hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => { |
| self.access_path(expr.hir_id, path, succ, acc) |
| } |
| |
| // We do not track other places, so just propagate through |
| // to their subcomponents. Also, it may happen that |
| // non-places occur here, because those are detected in the |
| // later pass borrowck. |
| _ => succ |
| } |
| } |
| |
| fn access_var(&mut self, hir_id: HirId, var_hid: HirId, succ: LiveNode, acc: u32, span: Span) |
| -> LiveNode { |
| let ln = self.live_node(hir_id, span); |
| if acc != 0 { |
| self.init_from_succ(ln, succ); |
| let var = self.variable(var_hid, span); |
| self.acc(ln, var, acc); |
| } |
| ln |
| } |
| |
| fn access_path(&mut self, hir_id: HirId, path: &hir::Path, succ: LiveNode, acc: u32) |
| -> LiveNode { |
| match path.res { |
| Res::Local(hid) => { |
| let upvars = self.ir.tcx.upvars(self.ir.body_owner); |
| if !upvars.map_or(false, |upvars| upvars.contains_key(&hid)) { |
| self.access_var(hir_id, hid, succ, acc, path.span) |
| } else { |
| succ |
| } |
| } |
| _ => succ |
| } |
| } |
| |
| fn propagate_through_loop( |
| &mut self, |
| expr: &Expr, |
| body: &hir::Block, |
| succ: LiveNode |
| ) -> LiveNode { |
| /* |
| We model control flow like this: |
| |
| (expr) <-+ |
| | | |
| v | |
| (body) --+ |
| |
| Note that a `continue` expression targeting the `loop` will have a successor of `expr`. |
| Meanwhile, a `break` expression will have a successor of `succ`. |
| */ |
| |
| // first iteration: |
| let mut first_merge = true; |
| let ln = self.live_node(expr.hir_id, expr.span); |
| self.init_empty(ln, succ); |
| debug!("propagate_through_loop: using id for loop body {} {}", |
| expr.hir_id, self.ir.tcx.hir().hir_to_pretty_string(body.hir_id)); |
| |
| self.break_ln.insert(expr.hir_id, succ); |
| |
| self.cont_ln.insert(expr.hir_id, ln); |
| |
| let body_ln = self.propagate_through_block(body, ln); |
| |
| // repeat until fixed point is reached: |
| while self.merge_from_succ(ln, body_ln, first_merge) { |
| first_merge = false; |
| assert_eq!(body_ln, self.propagate_through_block(body, ln)); |
| } |
| |
| ln |
| } |
| } |
| |
| // _______________________________________________________________________ |
| // Checking for error conditions |
| |
| impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> { |
| fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { |
| NestedVisitorMap::None |
| } |
| |
| fn visit_local(&mut self, local: &'tcx hir::Local) { |
| self.check_unused_vars_in_pat(&local.pat, None, |spans, hir_id, ln, var| { |
| if local.init.is_some() { |
| self.warn_about_dead_assign(spans, hir_id, ln, var); |
| } |
| }); |
| |
| intravisit::walk_local(self, local); |
| } |
| |
| fn visit_expr(&mut self, ex: &'tcx Expr) { |
| check_expr(self, ex); |
| } |
| |
| fn visit_arm(&mut self, arm: &'tcx hir::Arm) { |
| self.check_unused_vars_in_pat(&arm.pat, None, |_, _, _, _| {}); |
| intravisit::walk_arm(self, arm); |
| } |
| } |
| |
| fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr) { |
| match expr.kind { |
| hir::ExprKind::Assign(ref l, _) => { |
| this.check_place(&l); |
| } |
| |
| hir::ExprKind::AssignOp(_, ref l, _) => { |
| if !this.tables.is_method_call(expr) { |
| this.check_place(&l); |
| } |
| } |
| |
| hir::ExprKind::InlineAsm(ref asm) => { |
| for input in &asm.inputs_exprs { |
| this.visit_expr(input); |
| } |
| |
| // Output operands must be places |
| for (o, output) in asm.inner.outputs.iter().zip(&asm.outputs_exprs) { |
| if !o.is_indirect { |
| this.check_place(output); |
| } |
| this.visit_expr(output); |
| } |
| } |
| |
| // no correctness conditions related to liveness |
| hir::ExprKind::Call(..) | hir::ExprKind::MethodCall(..) | |
| hir::ExprKind::Match(..) | hir::ExprKind::Loop(..) | |
| hir::ExprKind::Index(..) | hir::ExprKind::Field(..) | |
| hir::ExprKind::Array(..) | hir::ExprKind::Tup(..) | hir::ExprKind::Binary(..) | |
| hir::ExprKind::Cast(..) | hir::ExprKind::DropTemps(..) | hir::ExprKind::Unary(..) | |
| hir::ExprKind::Ret(..) | hir::ExprKind::Break(..) | hir::ExprKind::Continue(..) | |
| hir::ExprKind::Lit(_) | hir::ExprKind::Block(..) | hir::ExprKind::AddrOf(..) | |
| hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) | |
| hir::ExprKind::Closure(..) | hir::ExprKind::Path(_) | hir::ExprKind::Yield(..) | |
| hir::ExprKind::Box(..) | hir::ExprKind::Type(..) | hir::ExprKind::Err => {} |
| } |
| |
| intravisit::walk_expr(this, expr); |
| } |
| |
| impl<'tcx> Liveness<'_, 'tcx> { |
| fn check_place(&mut self, expr: &'tcx Expr) { |
| match expr.kind { |
| hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => { |
| if let Res::Local(var_hid) = path.res { |
| let upvars = self.ir.tcx.upvars(self.ir.body_owner); |
| if !upvars.map_or(false, |upvars| upvars.contains_key(&var_hid)) { |
| // 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.hir_id, expr.span); |
| let var = self.variable(var_hid, expr.span); |
| self.warn_about_dead_assign(vec![expr.span], expr.hir_id, ln, var); |
| } |
| } |
| } |
| _ => { |
| // For other kinds of places, 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] == b'_' { |
| None |
| } else { |
| Some(name) |
| } |
| } |
| |
| fn warn_about_unused_args(&self, body: &hir::Body, entry_ln: LiveNode) { |
| for p in &body.params { |
| self.check_unused_vars_in_pat(&p.pat, Some(entry_ln), |spans, hir_id, ln, var| { |
| if self.live_on_entry(ln, var).is_none() { |
| self.report_dead_assign(hir_id, spans, var, true); |
| } |
| }); |
| } |
| } |
| |
| fn check_unused_vars_in_pat( |
| &self, |
| pat: &hir::Pat, |
| entry_ln: Option<LiveNode>, |
| on_used_on_entry: impl Fn(Vec<Span>, HirId, LiveNode, Variable), |
| ) { |
| // In an or-pattern, only consider the variable; any later patterns must have the same |
| // bindings, and we also consider the first pattern to be the "authoritative" set of ids. |
| // However, we should take the spans of variables with the same name from the later |
| // patterns so the suggestions to prefix with underscores will apply to those too. |
| let mut vars: FxIndexMap<String, (LiveNode, Variable, HirId, Vec<Span>)> = <_>::default(); |
| |
| pat.each_binding(|_, hir_id, pat_sp, ident| { |
| let ln = entry_ln.unwrap_or_else(|| self.live_node(hir_id, pat_sp)); |
| let var = self.variable(hir_id, ident.span); |
| vars.entry(self.ir.variable_name(var)) |
| .and_modify(|(.., spans)| spans.push(ident.span)) |
| .or_insert_with(|| (ln, var, hir_id, vec![ident.span])); |
| }); |
| |
| for (_, (ln, var, id, spans)) in vars { |
| if self.used_on_entry(ln, var) { |
| on_used_on_entry(spans, id, ln, var); |
| } else { |
| self.report_unused(spans, id, ln, var); |
| } |
| } |
| } |
| |
| fn report_unused(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) { |
| if let Some(name) = self.should_warn(var).filter(|name| name != "self") { |
| // 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.lint_hir_note( |
| lint::builtin::UNUSED_VARIABLES, |
| hir_id, |
| spans, |
| &format!("variable `{}` is assigned to, but never used", name), |
| &format!("consider using `_{}` instead", name), |
| ); |
| } else { |
| let mut err = self.ir.tcx.struct_span_lint_hir( |
| lint::builtin::UNUSED_VARIABLES, |
| hir_id, |
| spans.clone(), |
| &format!("unused variable: `{}`", name), |
| ); |
| |
| if self.ir.variable_is_shorthand(var) { |
| if let Node::Binding(pat) = self.ir.tcx.hir().get(hir_id) { |
| // Handle `ref` and `ref mut`. |
| let spans = spans.iter() |
| .map(|_span| (pat.span, format!("{}: _", name))) |
| .collect(); |
| |
| err.multipart_suggestion( |
| "try ignoring the field", |
| spans, |
| Applicability::MachineApplicable, |
| ); |
| } |
| } else { |
| err.multipart_suggestion( |
| "consider prefixing with an underscore", |
| spans.iter().map(|span| (*span, format!("_{}", name))).collect(), |
| Applicability::MachineApplicable, |
| ); |
| } |
| |
| err.emit() |
| } |
| } |
| } |
| |
| fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) { |
| if self.live_on_exit(ln, var).is_none() { |
| self.report_dead_assign(hir_id, spans, var, false); |
| } |
| } |
| |
| fn report_dead_assign(&self, hir_id: HirId, spans: Vec<Span>, var: Variable, is_param: bool) { |
| if let Some(name) = self.should_warn(var) { |
| if is_param { |
| self.ir.tcx.struct_span_lint_hir(lint::builtin::UNUSED_ASSIGNMENTS, hir_id, spans, |
| &format!("value passed to `{}` is never read", name)) |
| .help("maybe it is overwritten before being read?") |
| .emit(); |
| } else { |
| self.ir.tcx.struct_span_lint_hir(lint::builtin::UNUSED_ASSIGNMENTS, hir_id, spans, |
| &format!("value assigned to `{}` is never read", name)) |
| .help("maybe it is overwritten before being read?") |
| .emit(); |
| } |
| } |
| } |
| } |