clippy_utils/mir/
mod.rs

1use rustc_hir::{Expr, HirId};
2use rustc_index::bit_set::DenseBitSet;
3use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
4use rustc_middle::mir::{
5    BasicBlock, Body, InlineAsmOperand, Local, Location, Place, START_BLOCK, StatementKind, TerminatorKind, traversal,
6};
7use rustc_middle::ty::TyCtxt;
8
9mod possible_borrower;
10pub use possible_borrower::PossibleBorrowerMap;
11
12mod possible_origin;
13
14mod transitive_relation;
15
16#[derive(Clone, Debug, Default)]
17pub struct LocalUsage {
18    /// The locations where the local is used, if any.
19    pub local_use_locs: Vec<Location>,
20    /// The locations where the local is consumed or mutated, if any.
21    pub local_consume_or_mutate_locs: Vec<Location>,
22}
23
24pub fn visit_local_usage(locals: &[Local], mir: &Body<'_>, location: Location) -> Option<Vec<LocalUsage>> {
25    let init = vec![
26        LocalUsage {
27            local_use_locs: Vec::new(),
28            local_consume_or_mutate_locs: Vec::new(),
29        };
30        locals.len()
31    ];
32
33    traversal::Postorder::new(&mir.basic_blocks, location.block, ())
34        .collect::<Vec<_>>()
35        .into_iter()
36        .rev()
37        .try_fold(init, |usage, tbb| {
38            let tdata = &mir.basic_blocks[tbb];
39
40            // Give up on loops
41            if tdata.terminator().successors().any(|s| s == location.block) {
42                return None;
43            }
44
45            let mut v = V {
46                locals,
47                location,
48                results: usage,
49            };
50            v.visit_basic_block_data(tbb, tdata);
51            Some(v.results)
52        })
53}
54
55struct V<'a> {
56    locals: &'a [Local],
57    location: Location,
58    results: Vec<LocalUsage>,
59}
60
61impl<'tcx> Visitor<'tcx> for V<'_> {
62    fn visit_place(&mut self, place: &Place<'tcx>, ctx: PlaceContext, loc: Location) {
63        if loc.block == self.location.block && loc.statement_index <= self.location.statement_index {
64            return;
65        }
66
67        let local = place.local;
68
69        for (i, self_local) in self.locals.iter().enumerate() {
70            if local == *self_local {
71                if !matches!(
72                    ctx,
73                    PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
74                ) {
75                    self.results[i].local_use_locs.push(loc);
76                }
77                if matches!(
78                    ctx,
79                    PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
80                        | PlaceContext::MutatingUse(MutatingUseContext::Borrow)
81                ) {
82                    self.results[i].local_consume_or_mutate_locs.push(loc);
83                }
84            }
85        }
86    }
87}
88
89/// Checks if the block is part of a cycle
90pub fn block_in_cycle(body: &Body<'_>, block: BasicBlock) -> bool {
91    let mut seen = DenseBitSet::new_empty(body.basic_blocks.len());
92    let mut to_visit = Vec::with_capacity(body.basic_blocks.len() / 2);
93
94    seen.insert(block);
95    let mut next = block;
96    loop {
97        for succ in body.basic_blocks[next].terminator().successors() {
98            if seen.insert(succ) {
99                to_visit.push(succ);
100            } else if succ == block {
101                return true;
102            }
103        }
104
105        if let Some(x) = to_visit.pop() {
106            next = x;
107        } else {
108            return false;
109        }
110    }
111}
112
113/// Convenience wrapper around `visit_local_usage`.
114pub fn used_exactly_once(mir: &Body<'_>, local: Local) -> Option<bool> {
115    visit_local_usage(
116        &[local],
117        mir,
118        Location {
119            block: START_BLOCK,
120            statement_index: 0,
121        },
122    )
123    .map(|mut vec| {
124        let LocalUsage { local_use_locs, .. } = vec.remove(0);
125        let mut locations = local_use_locs
126            .into_iter()
127            .filter(|&location| !is_local_assignment(mir, local, location));
128        if let Some(location) = locations.next() {
129            locations.next().is_none() && !block_in_cycle(mir, location.block)
130        } else {
131            false
132        }
133    })
134}
135
136/// Returns the `mir::Body` containing the node associated with `hir_id`.
137#[allow(clippy::module_name_repetitions)]
138pub fn enclosing_mir(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<&Body<'_>> {
139    let body_owner_local_def_id = tcx.hir().enclosing_body_owner(hir_id);
140    if tcx.hir().body_owner_kind(body_owner_local_def_id).is_fn_or_closure() {
141        Some(tcx.optimized_mir(body_owner_local_def_id.to_def_id()))
142    } else {
143        None
144    }
145}
146
147/// Tries to determine the `Local` corresponding to `expr`, if any.
148/// This function is expensive and should be used sparingly.
149pub fn expr_local(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Local> {
150    enclosing_mir(tcx, expr.hir_id).and_then(|mir| {
151        mir.local_decls.iter_enumerated().find_map(|(local, local_decl)| {
152            if local_decl.source_info.span == expr.span {
153                Some(local)
154            } else {
155                None
156            }
157        })
158    })
159}
160
161/// Returns a vector of `mir::Location` where `local` is assigned.
162pub fn local_assignments(mir: &Body<'_>, local: Local) -> Vec<Location> {
163    let mut locations = Vec::new();
164    for (block, data) in mir.basic_blocks.iter_enumerated() {
165        for statement_index in 0..=data.statements.len() {
166            let location = Location { block, statement_index };
167            if is_local_assignment(mir, local, location) {
168                locations.push(location);
169            }
170        }
171    }
172    locations
173}
174
175// `is_local_assignment` is based on `is_place_assignment`:
176// https://github.com/rust-lang/rust/blob/b7413511dc85ec01ef4b91785f86614589ac6103/compiler/rustc_middle/src/mir/visit.rs#L1350
177fn is_local_assignment(mir: &Body<'_>, local: Local, location: Location) -> bool {
178    let Location { block, statement_index } = location;
179    let basic_block = &mir.basic_blocks[block];
180    if statement_index < basic_block.statements.len() {
181        let statement = &basic_block.statements[statement_index];
182        if let StatementKind::Assign(box (place, _)) = statement.kind {
183            place.as_local() == Some(local)
184        } else {
185            false
186        }
187    } else {
188        let terminator = basic_block.terminator();
189        match &terminator.kind {
190            TerminatorKind::Call { destination, .. } => destination.as_local() == Some(local),
191            TerminatorKind::InlineAsm { operands, .. } => operands.iter().any(|operand| {
192                if let InlineAsmOperand::Out { place: Some(place), .. } = operand {
193                    place.as_local() == Some(local)
194                } else {
195                    false
196                }
197            }),
198            _ => false,
199        }
200    }
201}