]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/escape.rs
Merge commit 'a5d597637dcb78dc73f93561ce474f23d4177c35' into clippyup
[rust.git] / clippy_lints / src / escape.rs
1 use clippy_utils::diagnostics::span_lint;
2 use clippy_utils::ty::contains_ty;
3 use rustc_hir::intravisit;
4 use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node};
5 use rustc_infer::infer::TyCtxtInferExt;
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_middle::mir::FakeReadCause;
8 use rustc_middle::ty::layout::LayoutOf;
9 use rustc_middle::ty::{self, TraitRef, Ty};
10 use rustc_session::{declare_tool_lint, impl_lint_pass};
11 use rustc_span::source_map::Span;
12 use rustc_span::symbol::kw;
13 use rustc_target::spec::abi::Abi;
14 use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
15
16 #[derive(Copy, Clone)]
17 pub struct BoxedLocal {
18     pub too_large_for_stack: u64,
19 }
20
21 declare_clippy_lint! {
22     /// ### What it does
23     /// Checks for usage of `Box<T>` where an unboxed `T` would
24     /// work fine.
25     ///
26     /// ### Why is this bad?
27     /// This is an unnecessary allocation, and bad for
28     /// performance. It is only necessary to allocate if you wish to move the box
29     /// into something.
30     ///
31     /// ### Example
32     /// ```rust
33     /// # fn foo(bar: usize) {}
34     /// // Bad
35     /// let x = Box::new(1);
36     /// foo(*x);
37     /// println!("{}", *x);
38     ///
39     /// // Good
40     /// let x = 1;
41     /// foo(x);
42     /// println!("{}", x);
43     /// ```
44     #[clippy::version = "pre 1.29.0"]
45     pub BOXED_LOCAL,
46     perf,
47     "using `Box<T>` where unnecessary"
48 }
49
50 fn is_non_trait_box(ty: Ty<'_>) -> bool {
51     ty.is_box() && !ty.boxed_ty().is_trait()
52 }
53
54 struct EscapeDelegate<'a, 'tcx> {
55     cx: &'a LateContext<'tcx>,
56     set: HirIdSet,
57     trait_self_ty: Option<Ty<'tcx>>,
58     too_large_for_stack: u64,
59 }
60
61 impl_lint_pass!(BoxedLocal => [BOXED_LOCAL]);
62
63 impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
64     fn check_fn(
65         &mut self,
66         cx: &LateContext<'tcx>,
67         fn_kind: intravisit::FnKind<'tcx>,
68         _: &'tcx FnDecl<'_>,
69         body: &'tcx Body<'_>,
70         _: Span,
71         hir_id: HirId,
72     ) {
73         if let Some(header) = fn_kind.header() {
74             if header.abi != Abi::Rust {
75                 return;
76             }
77         }
78
79         let parent_id = cx.tcx.hir().get_parent_item(hir_id);
80         let parent_node = cx.tcx.hir().find(parent_id);
81
82         let mut trait_self_ty = None;
83         if let Some(Node::Item(item)) = parent_node {
84             // If the method is an impl for a trait, don't warn.
85             if let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = item.kind {
86                 return;
87             }
88
89             // find `self` ty for this trait if relevant
90             if let ItemKind::Trait(_, _, _, _, items) = item.kind {
91                 for trait_item in items {
92                     if trait_item.id.hir_id() == hir_id {
93                         // be sure we have `self` parameter in this function
94                         if trait_item.kind == (AssocItemKind::Fn { has_self: true }) {
95                             trait_self_ty = Some(
96                                 TraitRef::identity(cx.tcx, trait_item.id.def_id.to_def_id())
97                                     .self_ty()
98                                     .skip_binder(),
99                             );
100                         }
101                     }
102                 }
103             }
104         }
105
106         let mut v = EscapeDelegate {
107             cx,
108             set: HirIdSet::default(),
109             trait_self_ty,
110             too_large_for_stack: self.too_large_for_stack,
111         };
112
113         let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
114         cx.tcx.infer_ctxt().enter(|infcx| {
115             ExprUseVisitor::new(&mut v, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
116         });
117
118         for node in v.set {
119             span_lint(
120                 cx,
121                 BOXED_LOCAL,
122                 cx.tcx.hir().span(node),
123                 "local variable doesn't need to be boxed here",
124             );
125         }
126     }
127 }
128
129 // TODO: Replace with Map::is_argument(..) when it's fixed
130 fn is_argument(map: rustc_middle::hir::map::Map<'_>, id: HirId) -> bool {
131     match map.find(id) {
132         Some(Node::Binding(_)) => (),
133         _ => return false,
134     }
135
136     matches!(map.find(map.get_parent_node(id)), Some(Node::Param(_)))
137 }
138
139 impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> {
140     fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
141         if cmt.place.projections.is_empty() {
142             if let PlaceBase::Local(lid) = cmt.place.base {
143                 self.set.remove(&lid);
144                 let map = &self.cx.tcx.hir();
145                 if let Some(Node::Binding(_)) = map.find(cmt.hir_id) {
146                     if self.set.contains(&lid) {
147                         // let y = x where x is known
148                         // remove x, insert y
149                         self.set.insert(cmt.hir_id);
150                         self.set.remove(&lid);
151                     }
152                 }
153             }
154         }
155     }
156
157     fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
158         if cmt.place.projections.is_empty() {
159             if let PlaceBase::Local(lid) = cmt.place.base {
160                 self.set.remove(&lid);
161             }
162         }
163     }
164
165     fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
166         if cmt.place.projections.is_empty() {
167             let map = &self.cx.tcx.hir();
168             if is_argument(*map, cmt.hir_id) {
169                 // Skip closure arguments
170                 let parent_id = map.get_parent_node(cmt.hir_id);
171                 if let Some(Node::Expr(..)) = map.find(map.get_parent_node(parent_id)) {
172                     return;
173                 }
174
175                 // skip if there is a `self` parameter binding to a type
176                 // that contains `Self` (i.e.: `self: Box<Self>`), see #4804
177                 if let Some(trait_self_ty) = self.trait_self_ty {
178                     if map.name(cmt.hir_id) == kw::SelfLower && contains_ty(self.cx.tcx, cmt.place.ty(), trait_self_ty)
179                     {
180                         return;
181                     }
182                 }
183
184                 if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) {
185                     self.set.insert(cmt.hir_id);
186                 }
187             }
188         }
189     }
190
191     fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) {}
192 }
193
194 impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> {
195     fn is_large_box(&self, ty: Ty<'tcx>) -> bool {
196         // Large types need to be boxed to avoid stack overflows.
197         if ty.is_box() {
198             self.cx.layout_of(ty.boxed_ty()).map_or(0, |l| l.size.bytes()) > self.too_large_for_stack
199         } else {
200             false
201         }
202     }
203 }