Skip to main content

clippy_utils/mir/
mod.rs

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