]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_utils/src/visitors.rs
Auto merge of #96605 - Urgau:string-retain-codegen, r=thomcc
[rust.git] / src / tools / clippy / clippy_utils / src / visitors.rs
1 use crate::path_to_local_id;
2 use core::ops::ControlFlow;
3 use rustc_hir as hir;
4 use rustc_hir::def::{DefKind, Res};
5 use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
6 use rustc_hir::{
7     Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, UnsafeSource,
8     Unsafety,
9 };
10 use rustc_lint::LateContext;
11 use rustc_middle::hir::map::Map;
12 use rustc_middle::hir::nested_filter;
13 use rustc_middle::ty;
14
15 /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
16 /// bodies (i.e. closures) are visited.
17 /// If the callback returns `true`, the expr just provided to the callback is walked.
18 #[must_use]
19 pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
20     struct V<'tcx, F> {
21         hir: Map<'tcx>,
22         f: F,
23     }
24     impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
25         type NestedFilter = nested_filter::OnlyBodies;
26         fn nested_visit_map(&mut self) -> Self::Map {
27             self.hir
28         }
29
30         fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
31             if (self.f)(expr) {
32                 walk_expr(self, expr);
33             }
34         }
35     }
36     V { hir: cx.tcx.hir(), f }
37 }
38
39 /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
40 /// bodies (i.e. closures) are not visited.
41 /// If the callback returns `true`, the expr just provided to the callback is walked.
42 #[must_use]
43 pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
44     struct V<F>(F);
45     impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<F> {
46         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
47             if (self.0)(e) {
48                 walk_expr(self, e);
49             }
50         }
51     }
52     V(f)
53 }
54
55 /// returns `true` if expr contains match expr desugared from try
56 fn contains_try(expr: &hir::Expr<'_>) -> bool {
57     let mut found = false;
58     expr_visitor_no_bodies(|e| {
59         if !found {
60             found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar));
61         }
62         !found
63     })
64     .visit_expr(expr);
65     found
66 }
67
68 pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
69 where
70     F: FnMut(&'hir hir::Expr<'hir>) -> bool,
71 {
72     struct RetFinder<F> {
73         in_stmt: bool,
74         failed: bool,
75         cb: F,
76     }
77
78     struct WithStmtGuarg<'a, F> {
79         val: &'a mut RetFinder<F>,
80         prev_in_stmt: bool,
81     }
82
83     impl<F> RetFinder<F> {
84         fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
85             let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
86             WithStmtGuarg {
87                 val: self,
88                 prev_in_stmt,
89             }
90         }
91     }
92
93     impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
94         type Target = RetFinder<F>;
95
96         fn deref(&self) -> &Self::Target {
97             self.val
98         }
99     }
100
101     impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
102         fn deref_mut(&mut self) -> &mut Self::Target {
103             self.val
104         }
105     }
106
107     impl<F> Drop for WithStmtGuarg<'_, F> {
108         fn drop(&mut self) {
109             self.val.in_stmt = self.prev_in_stmt;
110         }
111     }
112
113     impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
114         fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
115             intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt);
116         }
117
118         fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
119             if self.failed {
120                 return;
121             }
122             if self.in_stmt {
123                 match expr.kind {
124                     hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
125                     _ => intravisit::walk_expr(self, expr),
126                 }
127             } else {
128                 match expr.kind {
129                     hir::ExprKind::If(cond, then, else_opt) => {
130                         self.inside_stmt(true).visit_expr(cond);
131                         self.visit_expr(then);
132                         if let Some(el) = else_opt {
133                             self.visit_expr(el);
134                         }
135                     },
136                     hir::ExprKind::Match(cond, arms, _) => {
137                         self.inside_stmt(true).visit_expr(cond);
138                         for arm in arms {
139                             self.visit_expr(arm.body);
140                         }
141                     },
142                     hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
143                     hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
144                     _ => self.failed |= !(self.cb)(expr),
145                 }
146             }
147         }
148     }
149
150     !contains_try(expr) && {
151         let mut ret_finder = RetFinder {
152             in_stmt: false,
153             failed: false,
154             cb: callback,
155         };
156         ret_finder.visit_expr(expr);
157         !ret_finder.failed
158     }
159 }
160
161 /// A type which can be visited.
162 pub trait Visitable<'tcx> {
163     /// Calls the corresponding `visit_*` function on the visitor.
164     fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
165 }
166 macro_rules! visitable_ref {
167     ($t:ident, $f:ident) => {
168         impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
169             fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
170                 visitor.$f(self);
171             }
172         }
173     };
174 }
175 visitable_ref!(Arm, visit_arm);
176 visitable_ref!(Block, visit_block);
177 visitable_ref!(Body, visit_body);
178 visitable_ref!(Expr, visit_expr);
179 visitable_ref!(Stmt, visit_stmt);
180
181 // impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
182 // where
183 //     I::Item: Visitable<'tcx>,
184 // {
185 //     fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
186 //         for x in self {
187 //             x.visit(visitor);
188 //         }
189 //     }
190 // }
191
192 /// Checks if the given resolved path is used in the given body.
193 pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
194     let mut found = false;
195     expr_visitor(cx, |e| {
196         if found {
197             return false;
198         }
199
200         if let ExprKind::Path(p) = &e.kind {
201             if cx.qpath_res(p, e.hir_id) == res {
202                 found = true;
203             }
204         }
205         !found
206     })
207     .visit_expr(&cx.tcx.hir().body(body).value);
208     found
209 }
210
211 /// Checks if the given local is used.
212 pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
213     let mut is_used = false;
214     let mut visitor = expr_visitor(cx, |expr| {
215         if !is_used {
216             is_used = path_to_local_id(expr, id);
217         }
218         !is_used
219     });
220     visitable.visit(&mut visitor);
221     drop(visitor);
222     is_used
223 }
224
225 /// Checks if the given expression is a constant.
226 pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
227     struct V<'a, 'tcx> {
228         cx: &'a LateContext<'tcx>,
229         is_const: bool,
230     }
231     impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
232         type NestedFilter = nested_filter::OnlyBodies;
233         fn nested_visit_map(&mut self) -> Self::Map {
234             self.cx.tcx.hir()
235         }
236
237         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
238             if !self.is_const {
239                 return;
240             }
241             match e.kind {
242                 ExprKind::ConstBlock(_) => return,
243                 ExprKind::Call(
244                     &Expr {
245                         kind: ExprKind::Path(ref p),
246                         hir_id,
247                         ..
248                     },
249                     _,
250                 ) if self
251                     .cx
252                     .qpath_res(p, hir_id)
253                     .opt_def_id()
254                     .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
255                 ExprKind::MethodCall(..)
256                     if self
257                         .cx
258                         .typeck_results()
259                         .type_dependent_def_id(e.hir_id)
260                         .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
261                 ExprKind::Binary(_, lhs, rhs)
262                     if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty()
263                         && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {},
264                 ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (),
265                 ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (),
266                 ExprKind::Index(base, _)
267                     if matches!(
268                         self.cx.typeck_results().expr_ty(base).peel_refs().kind(),
269                         ty::Slice(_) | ty::Array(..)
270                     ) => {},
271                 ExprKind::Path(ref p)
272                     if matches!(
273                         self.cx.qpath_res(p, e.hir_id),
274                         Res::Def(
275                             DefKind::Const
276                                 | DefKind::AssocConst
277                                 | DefKind::AnonConst
278                                 | DefKind::ConstParam
279                                 | DefKind::Ctor(..)
280                                 | DefKind::Fn
281                                 | DefKind::AssocFn,
282                             _
283                         ) | Res::SelfCtor(_)
284                     ) => {},
285
286                 ExprKind::AddrOf(..)
287                 | ExprKind::Array(_)
288                 | ExprKind::Block(..)
289                 | ExprKind::Cast(..)
290                 | ExprKind::DropTemps(_)
291                 | ExprKind::Field(..)
292                 | ExprKind::If(..)
293                 | ExprKind::Let(..)
294                 | ExprKind::Lit(_)
295                 | ExprKind::Match(..)
296                 | ExprKind::Repeat(..)
297                 | ExprKind::Struct(..)
298                 | ExprKind::Tup(_)
299                 | ExprKind::Type(..) => (),
300
301                 _ => {
302                     self.is_const = false;
303                     return;
304                 },
305             }
306             walk_expr(self, e);
307         }
308     }
309
310     let mut v = V { cx, is_const: true };
311     v.visit_expr(e);
312     v.is_const
313 }
314
315 /// Checks if the given expression performs an unsafe operation outside of an unsafe block.
316 pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
317     struct V<'a, 'tcx> {
318         cx: &'a LateContext<'tcx>,
319         is_unsafe: bool,
320     }
321     impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
322         type NestedFilter = nested_filter::OnlyBodies;
323         fn nested_visit_map(&mut self) -> Self::Map {
324             self.cx.tcx.hir()
325         }
326         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
327             if self.is_unsafe {
328                 return;
329             }
330             match e.kind {
331                 ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => {
332                     self.is_unsafe = true;
333                 },
334                 ExprKind::MethodCall(..)
335                     if self
336                         .cx
337                         .typeck_results()
338                         .type_dependent_def_id(e.hir_id)
339                         .map_or(false, |id| self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe) =>
340                 {
341                     self.is_unsafe = true;
342                 },
343                 ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() {
344                     ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
345                     ty::FnPtr(sig) if sig.unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
346                     _ => walk_expr(self, e),
347                 },
348                 ExprKind::Path(ref p)
349                     if self
350                         .cx
351                         .qpath_res(p, e.hir_id)
352                         .opt_def_id()
353                         .map_or(false, |id| self.cx.tcx.is_mutable_static(id)) =>
354                 {
355                     self.is_unsafe = true;
356                 },
357                 _ => walk_expr(self, e),
358             }
359         }
360         fn visit_block(&mut self, b: &'tcx Block<'_>) {
361             if !matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) {
362                 walk_block(self, b);
363             }
364         }
365         fn visit_nested_item(&mut self, id: ItemId) {
366             if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind {
367                 self.is_unsafe = i.unsafety == Unsafety::Unsafe;
368             }
369         }
370     }
371     let mut v = V { cx, is_unsafe: false };
372     v.visit_expr(e);
373     v.is_unsafe
374 }
375
376 /// Checks if the given expression contains an unsafe block
377 pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
378     struct V<'cx, 'tcx> {
379         cx: &'cx LateContext<'tcx>,
380         found_unsafe: bool,
381     }
382     impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
383         type NestedFilter = nested_filter::OnlyBodies;
384         fn nested_visit_map(&mut self) -> Self::Map {
385             self.cx.tcx.hir()
386         }
387
388         fn visit_block(&mut self, b: &'tcx Block<'_>) {
389             if self.found_unsafe {
390                 return;
391             }
392             if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
393                 self.found_unsafe = true;
394                 return;
395             }
396             walk_block(self, b);
397         }
398     }
399     let mut v = V {
400         cx,
401         found_unsafe: false,
402     };
403     v.visit_expr(e);
404     v.found_unsafe
405 }
406
407 /// Runs the given function for each sub-expression producing the final value consumed by the parent
408 /// of the give expression.
409 ///
410 /// e.g. for the following expression
411 /// ```rust,ignore
412 /// if foo {
413 ///     f(0)
414 /// } else {
415 ///     1 + 1
416 /// }
417 /// ```
418 /// this will pass both `f(0)` and `1+1` to the given function.
419 pub fn for_each_value_source<'tcx, B>(
420     e: &'tcx Expr<'tcx>,
421     f: &mut impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
422 ) -> ControlFlow<B> {
423     match e.kind {
424         ExprKind::Block(Block { expr: Some(e), .. }, _) => for_each_value_source(e, f),
425         ExprKind::Match(_, arms, _) => {
426             for arm in arms {
427                 for_each_value_source(arm.body, f)?;
428             }
429             ControlFlow::Continue(())
430         },
431         ExprKind::If(_, if_expr, Some(else_expr)) => {
432             for_each_value_source(if_expr, f)?;
433             for_each_value_source(else_expr, f)
434         },
435         ExprKind::DropTemps(e) => for_each_value_source(e, f),
436         _ => f(e),
437     }
438 }