Skip to content

Commit 07a7094

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

File tree

5 files changed

+105
-6
lines changed

5 files changed

+105
-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: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ use clippy_utils::sugg::Sugg;
44
use clippy_utils::ty::implements_trait;
55
use clippy_utils::{is_default_equivalent_call, local_is_initialized};
66
use rustc_errors::Applicability;
7-
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
7+
use rustc_hir::{Body, Expr, ExprKind, HirId, HirIdSet, LangItem, Node, QPath};
8+
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
89
use rustc_lint::{LateContext, LateLintPass};
9-
use rustc_session::declare_lint_pass;
10+
use rustc_middle::mir::FakeReadCause;
11+
use rustc_middle::ty;
12+
use rustc_session::impl_lint_pass;
1013
use rustc_span::sym;
1114

1215
declare_clippy_lint! {
@@ -33,16 +36,52 @@ declare_clippy_lint! {
3336
perf,
3437
"assigning a newly created box to `Box<T>` is inefficient"
3538
}
36-
declare_lint_pass!(ReplaceBox => [REPLACE_BOX]);
39+
40+
#[derive(Default)]
41+
pub struct ReplaceBox {
42+
// Stack of caches for moved vars. The latest entry is the
43+
// body being currently visited.
44+
moved_vars_caches: Vec<Option<HirIdSet>>,
45+
}
46+
47+
impl ReplaceBox {
48+
fn current_moved_vars_cache(&mut self) -> &mut Option<HirIdSet> {
49+
self.moved_vars_caches.last_mut().unwrap()
50+
}
51+
}
52+
53+
impl_lint_pass!(ReplaceBox => [REPLACE_BOX]);
3754

3855
impl LateLintPass<'_> for ReplaceBox {
56+
fn check_body(&mut self, _: &LateContext<'_>, _: &Body<'_>) {
57+
self.moved_vars_caches.push(None);
58+
}
59+
60+
fn check_body_post(&mut self, _: &LateContext<'_>, _: &Body<'_>) {
61+
_ = self.moved_vars_caches.pop();
62+
}
63+
3964
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
4065
if let ExprKind::Assign(lhs, rhs, _) = &expr.kind
4166
&& !lhs.span.from_expansion()
4267
&& !rhs.span.from_expansion()
4368
&& let lhs_ty = cx.typeck_results().expr_ty(lhs)
4469
// No diagnostic for late-initialized locals
45-
&& lhs.res_local_id().is_none_or(|local| local_is_initialized(cx, local))
70+
&& let local = lhs.res_local_id()
71+
&& local.is_none_or(|local| {
72+
local_is_initialized(cx, local)
73+
&& !self.current_moved_vars_cache().get_or_insert({
74+
let body_id = cx.enclosing_body.unwrap();
75+
let mut ctx = MovedVariablesCtxt {
76+
cx,
77+
moved_vars: HirIdSet::default(),
78+
};
79+
ExprUseVisitor::for_clippy(cx, cx.tcx.hir_body_owner_def_id(body_id), &mut ctx)
80+
.consume_body(cx.tcx.hir_body(body_id))
81+
.into_ok();
82+
ctx.moved_vars
83+
}).contains(&local)
84+
})
4685
&& let Some(inner_ty) = lhs_ty.boxed_ty()
4786
{
4887
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
@@ -109,3 +148,27 @@ fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<
109148
None
110149
}
111150
}
151+
152+
struct MovedVariablesCtxt<'a, 'tcx> {
153+
cx: &'a LateContext<'tcx>,
154+
moved_vars: HirIdSet,
155+
}
156+
157+
impl<'tcx> Delegate<'tcx> for MovedVariablesCtxt<'_, 'tcx> {
158+
fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
159+
if let PlaceBase::Local(vid) = cmt.place.base
160+
&& let Node::Expr(expr) = self.cx.tcx.hir_node(cmt.hir_id)
161+
&& expr.res_local_id().is_some_and(|hir_id| hir_id == vid)
162+
{
163+
self.moved_vars.insert(vid);
164+
}
165+
}
166+
167+
fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
168+
169+
fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {}
170+
171+
fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
172+
173+
fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
174+
}

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)