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}
;
14 use crate::utils
::{contains_ty, span_lint}
;
16 #[derive(Copy, Clone)]
17 pub struct BoxedLocal
{
18 pub too_large_for_stack
: u64,
21 declare_clippy_lint
! {
22 /// **What it does:** Checks for usage of `Box<T>` where an unboxed `T` would
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
29 /// **Known problems:** None.
33 /// # fn foo(bar: usize) {}
35 /// let x = Box::new(1);
37 /// println!("{}", *x);
42 /// println!("{}", x);
46 "using `Box<T>` where unnecessary"
49 fn is_non_trait_box(ty
: Ty
<'_
>) -> bool
{
50 ty
.is_box() && !ty
.boxed_ty().is_trait()
53 struct EscapeDelegate
<'a
, 'tcx
> {
54 cx
: &'a LateContext
<'tcx
>,
56 trait_self_ty
: Option
<Ty
<'a
>>,
57 too_large_for_stack
: u64,
60 impl_lint_pass
!(BoxedLocal
=> [BOXED_LOCAL
]);
62 impl<'tcx
> LateLintPass
<'tcx
> for BoxedLocal
{
65 cx
: &LateContext
<'tcx
>,
66 fn_kind
: intravisit
::FnKind
<'tcx
>,
72 if let Some(header
) = fn_kind
.header() {
73 if header
.abi
!= Abi
::Rust
{
78 let parent_id
= cx
.tcx
.hir().get_parent_item(hir_id
);
79 let parent_node
= cx
.tcx
.hir().find(parent_id
);
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
{
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
{
95 Some(TraitRef
::identity(cx
.tcx
, trait_item
.id
.def_id
.to_def_id()).self_ty());
102 let mut v
= EscapeDelegate
{
104 set
: HirIdSet
::default(),
106 too_large_for_stack
: self.too_large_for_stack
,
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
);
118 cx
.tcx
.hir().span(node
),
119 "local variable doesn't need to be boxed here",
125 // TODO: Replace with Map::is_argument(..) when it's fixed
126 fn is_argument(map
: rustc_middle
::hir
::map
::Map
<'_
>, id
: HirId
) -> bool
{
128 Some(Node
::Binding(_
)) => (),
132 matches
!(map
.find(map
.get_parent_node(id
)), Some(Node
::Param(_
)))
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
);
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
);
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
);
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
)) {
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
) {
182 if is_non_trait_box(cmt
.place
.ty()) && !self.is_large_box(cmt
.place
.ty()) {
183 self.set
.insert(cmt
.hir_id
);
189 fn fake_read(&mut self, _
: rustc_typeck
::expr_use_visitor
::Place
<'tcx
>, _
: FakeReadCause
, _
: HirId
) { }
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.
196 self.cx
.layout_of(ty
.boxed_ty()).map_or(0, |l
| l
.size
.bytes()) > self.too_large_for_stack