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