Skip to content

Commit f3c40a3

Browse files
committed
fix: replace_box FP when the box is moved
1 parent d05f74e commit f3c40a3

File tree

5 files changed

+100
-6
lines changed

5 files changed

+100
-6
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
820820
store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny));
821821
store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg));
822822
store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites));
823-
store.register_late_pass(|_| Box::new(replace_box::ReplaceBox));
823+
store.register_late_pass(|_| Box::new(replace_box::ReplaceBox::default()));
824824
// add lints here, do not remove this comment, it's used in `new_lint`
825825
}

clippy_lints/src/replace_box.rs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ use clippy_utils::res::{MaybeDef, MaybeResPath};
33
use clippy_utils::sugg::Sugg;
44
use clippy_utils::ty::implements_trait;
55
use clippy_utils::{is_default_equivalent_call, local_is_initialized};
6+
use rustc_data_structures::fx::FxHashMap;
67
use rustc_errors::Applicability;
7-
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
8+
use rustc_hir::def_id::LocalDefId;
9+
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, LangItem, Node, QPath};
10+
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
811
use rustc_lint::{LateContext, LateLintPass};
9-
use rustc_session::declare_lint_pass;
12+
use rustc_middle::mir::FakeReadCause;
13+
use rustc_middle::ty;
14+
use rustc_session::impl_lint_pass;
1015
use rustc_span::sym;
1116

1217
declare_clippy_lint! {
@@ -33,7 +38,13 @@ declare_clippy_lint! {
3338
perf,
3439
"assigning a newly created box to `Box<T>` is inefficient"
3540
}
36-
declare_lint_pass!(ReplaceBox => [REPLACE_BOX]);
41+
42+
#[derive(Default)]
43+
pub struct ReplaceBox {
44+
moved_vars: FxHashMap<LocalDefId, HirIdSet>,
45+
}
46+
47+
impl_lint_pass!(ReplaceBox => [REPLACE_BOX]);
3748

3849
impl LateLintPass<'_> for ReplaceBox {
3950
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
@@ -42,7 +53,30 @@ impl LateLintPass<'_> for ReplaceBox {
4253
&& !rhs.span.from_expansion()
4354
&& let lhs_ty = cx.typeck_results().expr_ty(lhs)
4455
// No diagnostic for late-initialized locals
45-
&& lhs.res_local_id().is_none_or(|local| local_is_initialized(cx, local))
56+
&& let local = lhs.res_local_id()
57+
&& local.is_none_or(|local| {
58+
if !local_is_initialized(cx, local) {
59+
return false;
60+
}
61+
62+
let Some(body) = cx.enclosing_body.map(|b| cx.tcx.hir_body(b)) else {
63+
return true;
64+
};
65+
66+
let owner_id = cx.tcx.hir_enclosing_body_owner(expr.hir_id);
67+
!self.moved_vars
68+
.entry(owner_id)
69+
.or_insert_with(|| {
70+
let mut ctx = MovedVariablesCtxt {
71+
cx,
72+
moved_vars: HirIdSet::default(),
73+
};
74+
ExprUseVisitor::for_clippy(cx, owner_id, &mut ctx)
75+
.consume_body(body)
76+
.into_ok();
77+
ctx.moved_vars
78+
}).contains(&local)
79+
})
4680
&& let Some(inner_ty) = lhs_ty.boxed_ty()
4781
{
4882
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
@@ -109,3 +143,27 @@ fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<
109143
None
110144
}
111145
}
146+
147+
struct MovedVariablesCtxt<'a, 'tcx> {
148+
cx: &'a LateContext<'tcx>,
149+
moved_vars: HirIdSet,
150+
}
151+
152+
impl<'tcx> Delegate<'tcx> for MovedVariablesCtxt<'_, 'tcx> {
153+
fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
154+
if let PlaceBase::Local(vid) = cmt.place.base
155+
&& let Node::Expr(expr) = self.cx.tcx.hir_node(cmt.hir_id)
156+
&& expr.res_local_id().is_some_and(|hir_id| hir_id == vid)
157+
{
158+
self.moved_vars.insert(vid);
159+
}
160+
}
161+
162+
fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
163+
164+
fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {}
165+
166+
fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
167+
168+
fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
169+
}

tests/ui/replace_box.fixed

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,17 @@ fn main() {
7070
let bb: Box<u32>;
7171
bb = Default::default();
7272
}
73+
74+
fn issue15951() {
75+
let mut x = Box::new(());
76+
let y = x;
77+
x = Box::new(());
78+
79+
struct Foo {
80+
inner: String,
81+
}
82+
let mut x = Box::new(Foo { inner: String::new() });
83+
let y = x.inner;
84+
*x = Foo { inner: String::new() };
85+
//~^ replace_box
86+
}

tests/ui/replace_box.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,17 @@ fn main() {
7070
let bb: Box<u32>;
7171
bb = Default::default();
7272
}
73+
74+
fn issue15951() {
75+
let mut x = Box::new(());
76+
let y = x;
77+
x = Box::new(());
78+
79+
struct Foo {
80+
inner: String,
81+
}
82+
let mut x = Box::new(Foo { inner: String::new() });
83+
let y = x.inner;
84+
x = Box::new(Foo { inner: String::new() });
85+
//~^ replace_box
86+
}

tests/ui/replace_box.stderr

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,13 @@ LL | b = Box::new(mac!(three));
4848
|
4949
= note: this creates a needless allocation
5050

51-
error: aborting due to 6 previous errors
51+
error: creating a new box
52+
--> tests/ui/replace_box.rs:84:5
53+
|
54+
LL | x = Box::new(Foo { inner: String::new() });
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*x = Foo { inner: String::new() }`
56+
|
57+
= note: this creates a needless allocation
58+
59+
error: aborting due to 7 previous errors
5260

0 commit comments

Comments
 (0)