]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate as utils; |
2 | use rustc_data_structures::fx::FxHashSet; | |
3 | use rustc_hir as hir; | |
4 | use rustc_hir::def::Res; | |
5 | use rustc_hir::intravisit; | |
6 | use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; | |
7 | use rustc_hir::{Expr, ExprKind, HirId, Path}; | |
8 | use rustc_infer::infer::TyCtxtInferExt; | |
9 | use rustc_lint::LateContext; | |
10 | use rustc_middle::mir::FakeReadCause; | |
11 | use rustc_middle::hir::map::Map; | |
12 | use rustc_middle::ty; | |
13 | use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; | |
14 | ||
15 | /// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined. | |
16 | pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<FxHashSet<HirId>> { | |
17 | let mut delegate = MutVarsDelegate { | |
18 | used_mutably: FxHashSet::default(), | |
19 | skip: false, | |
20 | }; | |
21 | cx.tcx.infer_ctxt().enter(|infcx| { | |
22 | ExprUseVisitor::new( | |
23 | &mut delegate, | |
24 | &infcx, | |
25 | expr.hir_id.owner, | |
26 | cx.param_env, | |
27 | cx.typeck_results(), | |
28 | ) | |
29 | .walk_expr(expr); | |
30 | }); | |
31 | ||
32 | if delegate.skip { | |
33 | return None; | |
34 | } | |
35 | Some(delegate.used_mutably) | |
36 | } | |
37 | ||
38 | pub fn is_potentially_mutated<'tcx>(variable: &'tcx Path<'_>, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool { | |
39 | if let Res::Local(id) = variable.res { | |
40 | mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id)) | |
41 | } else { | |
42 | true | |
43 | } | |
44 | } | |
45 | ||
46 | struct MutVarsDelegate { | |
47 | used_mutably: FxHashSet<HirId>, | |
48 | skip: bool, | |
49 | } | |
50 | ||
51 | impl<'tcx> MutVarsDelegate { | |
52 | #[allow(clippy::similar_names)] | |
53 | fn update(&mut self, cat: &PlaceWithHirId<'tcx>) { | |
54 | match cat.place.base { | |
55 | PlaceBase::Local(id) => { | |
56 | self.used_mutably.insert(id); | |
57 | }, | |
58 | PlaceBase::Upvar(_) => { | |
59 | //FIXME: This causes false negatives. We can't get the `NodeId` from | |
60 | //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the | |
61 | //`while`-body, not just the ones in the condition. | |
62 | self.skip = true | |
63 | }, | |
64 | _ => {}, | |
65 | } | |
66 | } | |
67 | } | |
68 | ||
69 | impl<'tcx> Delegate<'tcx> for MutVarsDelegate { | |
70 | fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {} | |
71 | ||
72 | fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) { | |
73 | if let ty::BorrowKind::MutBorrow = bk { | |
74 | self.update(&cmt) | |
75 | } | |
76 | } | |
77 | ||
78 | fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { | |
79 | self.update(&cmt) | |
80 | } | |
81 | ||
82 | fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _:HirId) { } | |
83 | } | |
84 | ||
85 | pub struct ParamBindingIdCollector { | |
86 | binding_hir_ids: Vec<hir::HirId>, | |
87 | } | |
88 | impl<'tcx> ParamBindingIdCollector { | |
89 | fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> { | |
90 | let mut hir_ids: Vec<hir::HirId> = Vec::new(); | |
91 | for param in body.params.iter() { | |
92 | let mut finder = ParamBindingIdCollector { | |
93 | binding_hir_ids: Vec::new(), | |
94 | }; | |
95 | finder.visit_param(param); | |
96 | for hir_id in &finder.binding_hir_ids { | |
97 | hir_ids.push(*hir_id); | |
98 | } | |
99 | } | |
100 | hir_ids | |
101 | } | |
102 | } | |
103 | impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector { | |
104 | type Map = Map<'tcx>; | |
105 | ||
106 | fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { | |
107 | if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind { | |
108 | self.binding_hir_ids.push(hir_id); | |
109 | } | |
110 | intravisit::walk_pat(self, pat); | |
111 | } | |
112 | ||
113 | fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { | |
114 | intravisit::NestedVisitorMap::None | |
115 | } | |
116 | } | |
117 | ||
118 | pub struct BindingUsageFinder<'a, 'tcx> { | |
119 | cx: &'a LateContext<'tcx>, | |
120 | binding_ids: Vec<hir::HirId>, | |
121 | usage_found: bool, | |
122 | } | |
123 | impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> { | |
124 | pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool { | |
125 | let mut finder = BindingUsageFinder { | |
126 | cx, | |
127 | binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body), | |
128 | usage_found: false, | |
129 | }; | |
130 | finder.visit_body(body); | |
131 | finder.usage_found | |
132 | } | |
133 | } | |
134 | impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> { | |
135 | type Map = Map<'tcx>; | |
136 | ||
137 | fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { | |
138 | if !self.usage_found { | |
139 | intravisit::walk_expr(self, expr); | |
140 | } | |
141 | } | |
142 | ||
143 | fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) { | |
144 | if let hir::def::Res::Local(id) = path.res { | |
145 | if self.binding_ids.contains(&id) { | |
146 | self.usage_found = true; | |
147 | } | |
148 | } | |
149 | } | |
150 | ||
151 | fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { | |
152 | intravisit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) | |
153 | } | |
154 | } | |
155 | ||
156 | struct ReturnBreakContinueMacroVisitor { | |
157 | seen_return_break_continue: bool, | |
158 | } | |
159 | ||
160 | impl ReturnBreakContinueMacroVisitor { | |
161 | fn new() -> ReturnBreakContinueMacroVisitor { | |
162 | ReturnBreakContinueMacroVisitor { | |
163 | seen_return_break_continue: false, | |
164 | } | |
165 | } | |
166 | } | |
167 | ||
168 | impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor { | |
169 | type Map = Map<'tcx>; | |
170 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { | |
171 | NestedVisitorMap::None | |
172 | } | |
173 | ||
174 | fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { | |
175 | if self.seen_return_break_continue { | |
176 | // No need to look farther if we've already seen one of them | |
177 | return; | |
178 | } | |
179 | match &ex.kind { | |
180 | ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => { | |
181 | self.seen_return_break_continue = true; | |
182 | }, | |
183 | // Something special could be done here to handle while or for loop | |
184 | // desugaring, as this will detect a break if there's a while loop | |
185 | // or a for loop inside the expression. | |
186 | _ => { | |
187 | if utils::in_macro(ex.span) { | |
188 | self.seen_return_break_continue = true; | |
189 | } else { | |
190 | rustc_hir::intravisit::walk_expr(self, ex); | |
191 | } | |
192 | }, | |
193 | } | |
194 | } | |
195 | } | |
196 | ||
197 | pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { | |
198 | let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new(); | |
199 | recursive_visitor.visit_expr(expression); | |
200 | recursive_visitor.seen_return_break_continue | |
201 | } |