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