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