| use clippy_config::Conf; | |
| use clippy_utils::diagnostics::span_lint_hir; | |
| use rustc_abi::ExternAbi; | |
| use rustc_hir::def::DefKind; | |
| use rustc_hir::{Body, FnDecl, HirId, HirIdSet, Node, Pat, PatKind, intravisit}; | |
| use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; | |
| use rustc_lint::{LateContext, LateLintPass}; | |
| use rustc_middle::mir::FakeReadCause; | |
| use rustc_middle::ty::layout::LayoutOf; | |
| use rustc_middle::ty::{self, TraitRef, Ty, TyCtxt}; | |
| use rustc_session::impl_lint_pass; | |
| use rustc_span::Span; | |
| use rustc_span::def_id::LocalDefId; | |
| use rustc_span::symbol::kw; | |
| pub struct BoxedLocal { | |
| too_large_for_stack: u64, | |
| } | |
| impl BoxedLocal { | |
| pub fn new(conf: &'static Conf) -> Self { | |
| Self { | |
| too_large_for_stack: conf.too_large_for_stack, | |
| } | |
| } | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for usage of `Box<T>` where an unboxed `T` would | |
| /// work fine. | |
| /// | |
| /// ### Why is this bad? | |
| /// This is an unnecessary allocation, and bad for | |
| /// performance. It is only necessary to allocate if you wish to move the box | |
| /// into something. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// fn foo(x: Box<u32>) {} | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// fn foo(x: u32) {} | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub BOXED_LOCAL, | |
| perf, | |
| "using `Box<T>` where unnecessary" | |
| } | |
| impl_lint_pass!(BoxedLocal => [BOXED_LOCAL]); | |
| fn is_non_trait_box(ty: Ty<'_>) -> bool { | |
| ty.boxed_ty().is_some_and(|boxed| !boxed.is_trait()) | |
| } | |
| struct EscapeDelegate<'a, 'tcx> { | |
| cx: &'a LateContext<'tcx>, | |
| set: HirIdSet, | |
| trait_self_ty: Option<Ty<'tcx>>, | |
| too_large_for_stack: u64, | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for BoxedLocal { | |
| fn check_fn( | |
| &mut self, | |
| cx: &LateContext<'tcx>, | |
| fn_kind: intravisit::FnKind<'tcx>, | |
| _: &'tcx FnDecl<'_>, | |
| body: &'tcx Body<'_>, | |
| _: Span, | |
| fn_def_id: LocalDefId, | |
| ) { | |
| if let Some(header) = fn_kind.header() | |
| && header.abi != ExternAbi::Rust | |
| { | |
| return; | |
| } | |
| let parent_id = cx | |
| .tcx | |
| .hir_get_parent_item(cx.tcx.local_def_id_to_hir_id(fn_def_id)) | |
| .def_id; | |
| let mut trait_self_ty = None; | |
| match cx.tcx.def_kind(parent_id) { | |
| // If the method is an impl for a trait, don't warn. | |
| DefKind::Impl { of_trait: true } => return, | |
| // find `self` ty for this trait if relevant | |
| DefKind::Trait => { | |
| trait_self_ty = Some(TraitRef::identity(cx.tcx, parent_id.to_def_id()).self_ty()); | |
| }, | |
| _ => {}, | |
| } | |
| let mut v = EscapeDelegate { | |
| cx, | |
| set: HirIdSet::default(), | |
| trait_self_ty, | |
| too_large_for_stack: self.too_large_for_stack, | |
| }; | |
| ExprUseVisitor::for_clippy(cx, fn_def_id, &mut v) | |
| .consume_body(body) | |
| .into_ok(); | |
| for node in v.set { | |
| span_lint_hir( | |
| cx, | |
| BOXED_LOCAL, | |
| node, | |
| cx.tcx.hir_span(node), | |
| "local variable doesn't need to be boxed here", | |
| ); | |
| } | |
| } | |
| } | |
| // TODO: Replace with Map::is_argument(..) when it's fixed | |
| fn is_argument(tcx: TyCtxt<'_>, id: HirId) -> bool { | |
| match tcx.hir_node(id) { | |
| Node::Pat(Pat { | |
| kind: PatKind::Binding(..), | |
| .. | |
| }) => (), | |
| _ => return false, | |
| } | |
| matches!(tcx.parent_hir_node(id), Node::Param(_)) | |
| } | |
| impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> { | |
| fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { | |
| if cmt.place.projections.is_empty() | |
| && let PlaceBase::Local(lid) = cmt.place.base | |
| { | |
| // FIXME(rust/#120456) - is `swap_remove` correct? | |
| self.set.swap_remove(&lid); | |
| } | |
| } | |
| fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} | |
| fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) { | |
| if cmt.place.projections.is_empty() | |
| && let PlaceBase::Local(lid) = cmt.place.base | |
| { | |
| // FIXME(rust/#120456) - is `swap_remove` correct? | |
| self.set.swap_remove(&lid); | |
| } | |
| } | |
| fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { | |
| if cmt.place.projections.is_empty() && is_argument(self.cx.tcx, cmt.hir_id) { | |
| // Skip closure arguments | |
| let parent_id = self.cx.tcx.parent_hir_id(cmt.hir_id); | |
| if let Node::Expr(..) = self.cx.tcx.parent_hir_node(parent_id) { | |
| return; | |
| } | |
| // skip if there is a `self` parameter binding to a type | |
| // that contains `Self` (i.e.: `self: Box<Self>`), see #4804 | |
| if let Some(trait_self_ty) = self.trait_self_ty | |
| && self.cx.tcx.hir_name(cmt.hir_id) == kw::SelfLower | |
| && cmt.place.ty().contains(trait_self_ty) | |
| { | |
| return; | |
| } | |
| if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) { | |
| self.set.insert(cmt.hir_id); | |
| } | |
| } | |
| } | |
| fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} | |
| } | |
| impl<'tcx> EscapeDelegate<'_, 'tcx> { | |
| fn is_large_box(&self, ty: Ty<'tcx>) -> bool { | |
| // Large types need to be boxed to avoid stack overflows. | |
| if let Some(boxed_ty) = ty.boxed_ty() { | |
| self.cx.layout_of(boxed_ty).map_or(0, |l| l.size.bytes()) > self.too_large_for_stack | |
| } else { | |
| false | |
| } | |
| } | |
| } |