Skip to content

Commit 4520f68

Browse files
dingxiangfei2009ehuss
authored andcommitted
collect drops instead of taking liveness diff
1 parent c82dd3a commit 4520f68

File tree

3 files changed

+180
-95
lines changed

3 files changed

+180
-95
lines changed

compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs

Lines changed: 96 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
use std::collections::BTreeSet;
1+
use std::cell::RefCell;
2+
use std::collections::hash_map;
3+
use std::rc::Rc;
24

3-
use rustc_data_structures::fx::FxHashSet;
5+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
46
use rustc_data_structures::unord::UnordSet;
57
use rustc_hir::CRATE_HIR_ID;
68
use rustc_hir::def_id::{DefId, LocalDefId};
7-
use rustc_index::bit_set::{BitSet, ChunkedBitSet};
9+
use rustc_index::bit_set::ChunkedBitSet;
10+
use rustc_index::{IndexSlice, IndexVec};
811
use rustc_macros::LintDiagnostic;
912
use rustc_middle::mir::{
1013
BasicBlock, Body, ClearCrossCrate, Local, Location, Place, ProjectionElem, StatementKind,
1114
TerminatorKind, dump_mir,
1215
};
1316
use rustc_middle::ty::{self, Ty, TyCtxt};
1417
use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
15-
use rustc_mir_dataflow::move_paths::{MoveData, MovePathIndex};
16-
use rustc_mir_dataflow::{Analysis, MaybeReachable};
18+
use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
19+
use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor};
1720
use rustc_session::lint;
1821
use rustc_span::Span;
1922
use rustc_type_ir::data_structures::IndexMap;
@@ -25,48 +28,83 @@ fn place_has_common_prefix<'tcx>(left: &Place<'tcx>, right: &Place<'tcx>) -> boo
2528
&& left.projection.iter().zip(right.projection).all(|(left, right)| left == right)
2629
}
2730

28-
fn drops_reachable_from_location<'tcx>(
29-
body: &Body<'tcx>,
30-
block: BasicBlock,
31-
place: &Place<'tcx>,
32-
) -> BitSet<BasicBlock> {
33-
let mut reachable = BitSet::new_empty(body.basic_blocks.len());
34-
let mut visited = reachable.clone();
35-
let mut new_blocks = reachable.clone();
36-
new_blocks.insert(block);
37-
while !new_blocks.is_empty() {
38-
let mut next_front = BitSet::new_empty(new_blocks.domain_size());
39-
for bb in new_blocks.iter() {
40-
if !visited.insert(bb) {
31+
struct DropsReachable<'a, 'mir, 'tcx> {
32+
body: &'a Body<'tcx>,
33+
place: &'a Place<'tcx>,
34+
drop_span: &'a mut Option<Span>,
35+
move_data: &'a MoveData<'tcx>,
36+
maybe_init: &'a mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
37+
block_drop_value_info: &'a mut IndexSlice<BasicBlock, Option<MovePathIndex>>,
38+
collected_drops: &'a mut ChunkedBitSet<MovePathIndex>,
39+
visited: FxHashMap<BasicBlock, Rc<RefCell<ChunkedBitSet<MovePathIndex>>>>,
40+
}
41+
42+
impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
43+
fn visit(&mut self, block: BasicBlock) {
44+
let move_set_size = self.move_data.move_paths.len();
45+
let make_new_path_set = || Rc::new(RefCell::new(ChunkedBitSet::new_empty(move_set_size)));
46+
let data = &self.body.basic_blocks[block];
47+
let Some(terminator) = &data.terminator else { return };
48+
let dropped_local_here =
49+
self.visited.entry(block).or_insert_with(make_new_path_set).clone();
50+
if let Some(dropped) = self.block_drop_value_info[block] {
51+
dropped_local_here.borrow_mut().insert(dropped);
52+
} else if let TerminatorKind::Drop { place, .. } = &terminator.kind
53+
&& let LookupResult::Exact(idx) | LookupResult::Parent(Some(idx)) =
54+
self.move_data.rev_lookup.find(place.as_ref())
55+
{
56+
self.maybe_init.seek_before_primary_effect(Location {
57+
block,
58+
statement_index: data.statements.len(),
59+
});
60+
if let MaybeReachable::Reachable(maybe_init) = self.maybe_init.get()
61+
&& maybe_init.contains(idx)
62+
{
63+
self.block_drop_value_info[block] = Some(idx);
64+
dropped_local_here.borrow_mut().insert(idx);
65+
}
66+
}
67+
68+
for succ in terminator.successors() {
69+
let target = &self.body.basic_blocks[succ];
70+
if target.is_cleanup {
4171
continue;
4272
}
43-
for succ in body.basic_blocks[bb].terminator.iter().flat_map(|term| term.successors()) {
44-
let target = &body.basic_blocks[succ];
45-
if target.is_cleanup {
46-
continue;
73+
74+
// As long as we are passing through a new block, or new dropped place to propagate, we will proceed
75+
let dropped_local_there = match self.visited.entry(succ) {
76+
hash_map::Entry::Occupied(occupied_entry) => {
77+
if !occupied_entry.get().borrow_mut().union(&*dropped_local_here.borrow()) {
78+
continue;
79+
}
80+
occupied_entry.get().clone()
4781
}
48-
if !next_front.contains(succ)
49-
&& let Some(terminator) = &target.terminator
50-
&& let TerminatorKind::Drop {
51-
place: dropped_place,
52-
target: _,
53-
unwind: _,
54-
replace: _,
55-
} = &terminator.kind
56-
&& place_has_common_prefix(dropped_place, place)
57-
{
58-
reachable.insert(succ);
59-
// Now we have discovered a simple control flow path from a future drop point
60-
// to the current drop point.
61-
// We will not continue here.
62-
} else {
63-
next_front.insert(succ);
82+
hash_map::Entry::Vacant(vacant_entry) => {
83+
vacant_entry.insert(dropped_local_here.clone()).clone()
6484
}
85+
};
86+
if let Some(terminator) = &target.terminator
87+
&& let TerminatorKind::Drop {
88+
place: dropped_place,
89+
target: _,
90+
unwind: _,
91+
replace: _,
92+
} = &terminator.kind
93+
&& place_has_common_prefix(dropped_place, self.place)
94+
{
95+
self.collected_drops.union(&*dropped_local_there.borrow());
96+
if self.drop_span.is_none() {
97+
*self.drop_span =
98+
Some(self.body.source_scopes[terminator.source_info.scope].span);
99+
}
100+
// Now we have discovered a simple control flow path from a future drop point
101+
// to the current drop point.
102+
// We will not continue from there.
103+
} else {
104+
self.visit(succ)
65105
}
66106
}
67-
new_blocks = next_front;
68107
}
69-
reachable
70108
}
71109

72110
#[instrument(level = "debug", skip(tcx, param_env))]
@@ -226,39 +264,26 @@ pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<
226264
let move_data = MoveData::gather_moves(body, tcx, |_| true);
227265
let maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data);
228266
let mut maybe_init = maybe_init.iterate_to_fixpoint(tcx, body, None).into_results_cursor(body);
267+
let mut block_drop_value_info = IndexVec::from_elem_n(None, body.basic_blocks.len());
229268
for (&block, candidates) in &bid_per_block {
230269
let mut all_locals_dropped = ChunkedBitSet::new_empty(move_data.move_paths.len());
231-
let mut linted_candidates = BTreeSet::new();
232270
let mut drop_span = None;
233-
for (idx, &(candidate, place)) in candidates.iter().enumerate() {
234-
maybe_init.seek_after_primary_effect(candidate);
235-
let MaybeReachable::Reachable(maybe_init_in_future) = maybe_init.get() else {
236-
continue;
271+
for &(_, place) in candidates.iter() {
272+
let mut collected_drops = ChunkedBitSet::new_empty(move_data.move_paths.len());
273+
let mut search = DropsReachable {
274+
body,
275+
place,
276+
drop_span: &mut drop_span,
277+
move_data: &move_data,
278+
maybe_init: &mut maybe_init,
279+
block_drop_value_info: &mut block_drop_value_info,
280+
collected_drops: &mut collected_drops,
281+
visited: Default::default(),
237282
};
238-
debug!(maybe_init_in_future = ?maybe_init_in_future.iter().map(|idx| &move_data.move_paths[idx]).collect::<Vec<_>>());
239-
let maybe_init_in_future = maybe_init_in_future.clone();
240-
for block in drops_reachable_from_location(body, block, place).iter() {
241-
let data = &body.basic_blocks[block];
242-
if drop_span.is_none() {
243-
drop_span = data
244-
.terminator
245-
.as_ref()
246-
.map(|term| body.source_scopes[term.source_info.scope].span);
247-
}
248-
debug!(?candidate, "inspect");
249-
maybe_init.seek_before_primary_effect(Location {
250-
block,
251-
statement_index: data.statements.len(),
252-
});
253-
let MaybeReachable::Reachable(maybe_init_now) = maybe_init.get() else { continue };
254-
let mut locals_dropped = maybe_init_in_future.clone();
255-
debug!(maybe_init_now = ?maybe_init_now.iter().map(|idx| &move_data.move_paths[idx]).collect::<Vec<_>>());
256-
locals_dropped.subtract(maybe_init_now);
257-
debug!(locals_dropped = ?locals_dropped.iter().map(|idx| &move_data.move_paths[idx]).collect::<Vec<_>>());
258-
if all_locals_dropped.union(&locals_dropped) {
259-
linted_candidates.insert(idx);
260-
}
261-
}
283+
search.visit(block);
284+
let _ = search;
285+
286+
all_locals_dropped.union(&collected_drops);
262287
}
263288
{
264289
let mut to_exclude = ChunkedBitSet::new_empty(all_locals_dropped.domain_size());
@@ -292,13 +317,9 @@ pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<
292317
continue;
293318
}
294319
}
295-
let mut iter = linted_candidates.iter();
296-
let Some(first_linted_local) = iter.next().map(|&idx| candidates[idx].1.local) else {
297-
continue;
298-
};
299-
if iter.all(|&idx| candidates[idx].1.local == first_linted_local) {
320+
if candidates.iter().all(|&(_, place)| candidates[0].1.local == place.local) {
300321
for path_idx in all_locals_dropped.iter() {
301-
if move_data.move_paths[path_idx].place.local == first_linted_local {
322+
if move_data.move_paths[path_idx].place.local == candidates[0].1.local {
302323
to_exclude.insert(path_idx);
303324
}
304325
}

tests/ui/drop/lint-tail-expr-drop-order.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//@ build-fail
66

77
#![deny(tail_expr_drop_order)] //~ NOTE: the lint level is defined here
8+
#![allow(dropping_copy_types)]
89

910
struct LoudDropper;
1011
impl Drop for LoudDropper {
@@ -18,6 +19,8 @@ impl Drop for LoudDropper {
1819
//~| NOTE: dropping the local runs this custom `Drop` impl, which will run second in Rust 2024
1920
//~| NOTE: dropping the temporary runs this custom `Drop` impl, which will run first in Rust 2024
2021
//~| NOTE: dropping the local runs this custom `Drop` impl, which will run second in Rust 2024
22+
//~| NOTE: dropping the temporary runs this custom `Drop` impl, which will run first in Rust 2024
23+
//~| NOTE: dropping the local runs this custom `Drop` impl, which will run second in Rust 2024
2124
fn drop(&mut self) {
2225
// This destructor should be considered significant because it is a custom destructor
2326
// and we will assume that the destructor can generate side effects arbitrarily so that
@@ -212,4 +215,22 @@ fn should_lint_with_dtor_span() -> i32 {
212215
//~| NOTE: for more information, see issue #123739
213216
}
214217

218+
fn should_lint_with_transient_drops() {
219+
//~^ NOTE: temporary will be dropped on exiting the block, before the block's local variables
220+
drop((
221+
{
222+
LoudDropper.get()
223+
//~^ ERROR: relative drop order changing in Rust 2024
224+
//~| NOTE: in Rust 2024, this temporary will be dropped first
225+
//~| WARN: this changes meaning in Rust 2024
226+
//~| NOTE: most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects
227+
//~| NOTE: for more information, see issue #123739
228+
},
229+
{
230+
let _x = LoudDropper;
231+
//~^ NOTE: in Rust 2024, this local variable or temporary value will be dropped second
232+
},
233+
));
234+
}
235+
215236
fn main() {}

0 commit comments

Comments
 (0)