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