Skip to content

Commit 08f8919

Browse files
committed
Fix drop scopes in mir
1 parent f44fc27 commit 08f8919

File tree

5 files changed

+237
-24
lines changed

5 files changed

+237
-24
lines changed

crates/hir-ty/src/mir/eval.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1896,7 +1896,14 @@ impl Evaluator<'_> {
18961896
let mir_body = self
18971897
.db
18981898
.monomorphized_mir_body(def, generic_args, self.trait_env.clone())
1899-
.map_err(|e| MirEvalError::MirLowerError(imp, e))?;
1899+
.map_err(|e| {
1900+
MirEvalError::InFunction(
1901+
Either::Left(imp),
1902+
Box::new(MirEvalError::MirLowerError(imp, e)),
1903+
span,
1904+
locals.body.owner,
1905+
)
1906+
})?;
19001907
let result = self.interpret_mir(&mir_body, arg_bytes.iter().cloned()).map_err(|e| {
19011908
MirEvalError::InFunction(Either::Left(imp), Box::new(e), span, locals.body.owner)
19021909
})?;

crates/hir-ty/src/mir/eval/tests.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use base_db::{fixture::WithFixture, FileId};
22
use hir_def::db::DefDatabase;
3+
use syntax::{TextRange, TextSize};
34

45
use crate::{db::HirDatabase, test_db::TestDB, Interner, Substitution};
56

@@ -45,7 +46,21 @@ fn check_pass_and_stdio(ra_fixture: &str, expected_stdout: &str, expected_stderr
4546
match x {
4647
Err(e) => {
4748
let mut err = String::new();
48-
let span_formatter = |file, range| format!("{:?} {:?}", file, range);
49+
let line_index = |size: TextSize| {
50+
let mut size = u32::from(size) as usize;
51+
let mut lines = ra_fixture.lines().enumerate();
52+
while let Some((i, l)) = lines.next() {
53+
if let Some(x) = size.checked_sub(l.len()) {
54+
size = x;
55+
} else {
56+
return (i, size);
57+
}
58+
}
59+
(usize::MAX, size)
60+
};
61+
let span_formatter = |file, range: TextRange| {
62+
format!("{:?} {:?}..{:?}", file, line_index(range.start()), line_index(range.end()))
63+
};
4964
e.pretty_print(&mut err, &db, span_formatter).unwrap();
5065
panic!("Error in interpreting: {err}");
5166
}
@@ -115,6 +130,58 @@ fn main() {
115130
);
116131
}
117132

133+
#[test]
134+
fn drop_if_let() {
135+
check_pass(
136+
r#"
137+
//- minicore: drop, add, option, cell, builtin_impls
138+
139+
use core::cell::Cell;
140+
141+
struct X<'a>(&'a Cell<i32>);
142+
impl<'a> Drop for X<'a> {
143+
fn drop(&mut self) {
144+
self.0.set(self.0.get() + 1)
145+
}
146+
}
147+
148+
fn should_not_reach() {
149+
_ // FIXME: replace this function with panic when that works
150+
}
151+
152+
#[test]
153+
fn main() {
154+
let s = Cell::new(0);
155+
let x = Some(X(&s));
156+
if let Some(y) = x {
157+
if s.get() != 0 {
158+
should_not_reach();
159+
}
160+
if s.get() != 0 {
161+
should_not_reach();
162+
}
163+
} else {
164+
should_not_reach();
165+
}
166+
if s.get() != 1 {
167+
should_not_reach();
168+
}
169+
let x = Some(X(&s));
170+
if let None = x {
171+
should_not_reach();
172+
} else {
173+
if s.get() != 1 {
174+
should_not_reach();
175+
}
176+
}
177+
if s.get() != 1 {
178+
should_not_reach();
179+
}
180+
}
181+
"#,
182+
);
183+
}
184+
118185
#[test]
119186
fn drop_in_place() {
120187
check_pass(

crates/hir-ty/src/mir/lower.rs

Lines changed: 93 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ struct LoopBlocks {
4848
/// `None` for loops that are not terminating
4949
end: Option<BasicBlockId>,
5050
place: Place,
51+
drop_scope_index: usize,
5152
}
5253

5354
#[derive(Debug, Clone, Default)]
@@ -101,6 +102,35 @@ pub enum MirLowerError {
101102
GenericArgNotProvided(TypeOrConstParamId, Substitution),
102103
}
103104

105+
/// A token to ensuring that each drop scope is popped at most once, thanks to the compiler that checks moves.
106+
struct DropScopeToken;
107+
impl DropScopeToken {
108+
fn pop_and_drop(self, ctx: &mut MirLowerCtx<'_>, current: BasicBlockId) -> BasicBlockId {
109+
std::mem::forget(self);
110+
ctx.pop_drop_scope_internal(current)
111+
}
112+
113+
/// It is useful when we want a drop scope is syntaxically closed, but we don't want to execute any drop
114+
/// code. Either when the control flow is diverging (so drop code doesn't reached) or when drop is handled
115+
/// for us (for example a block that ended with a return statement. Return will drop everything, so the block shouldn't
116+
/// do anything)
117+
fn pop_assume_dropped(self, ctx: &mut MirLowerCtx<'_>) {
118+
std::mem::forget(self);
119+
ctx.pop_drop_scope_assume_dropped_internal();
120+
}
121+
}
122+
123+
// Uncomment this to make `DropScopeToken` a drop bomb. Unfortunately we can't do this in release, since
124+
// in cases that mir lowering fails, we don't handle (and don't need to handle) drop scopes so it will be
125+
// actually reached. `pop_drop_scope_assert_finished` will also detect this case, but doesn't show useful
126+
// stack trace.
127+
//
128+
// impl Drop for DropScopeToken {
129+
// fn drop(&mut self) {
130+
// never!("Drop scope doesn't popped");
131+
// }
132+
// }
133+
104134
impl MirLowerError {
105135
pub fn pretty_print(
106136
&self,
@@ -506,7 +536,6 @@ impl<'ctx> MirLowerCtx<'ctx> {
506536
self.lower_loop(current, place.clone(), Some(*label), expr_id.into(), |this, begin| {
507537
if let Some(current) = this.lower_block_to_place(statements, begin, *tail, place, expr_id.into())? {
508538
let end = this.current_loop_end()?;
509-
let current = this.pop_drop_scope(current);
510539
this.set_goto(current, end, expr_id.into());
511540
}
512541
Ok(())
@@ -516,30 +545,39 @@ impl<'ctx> MirLowerCtx<'ctx> {
516545
}
517546
}
518547
Expr::Loop { body, label } => self.lower_loop(current, place, *label, expr_id.into(), |this, begin| {
519-
if let Some((_, current)) = this.lower_expr_as_place(begin, *body, true)? {
520-
let current = this.pop_drop_scope(current);
548+
let scope = this.push_drop_scope();
549+
if let Some((_, mut current)) = this.lower_expr_as_place(begin, *body, true)? {
550+
current = scope.pop_and_drop(this, current);
521551
this.set_goto(current, begin, expr_id.into());
552+
} else {
553+
scope.pop_assume_dropped(this);
522554
}
523555
Ok(())
524556
}),
525557
Expr::While { condition, body, label } => {
526558
self.lower_loop(current, place, *label, expr_id.into(),|this, begin| {
559+
let scope = this.push_drop_scope();
527560
let Some((discr, to_switch)) = this.lower_expr_to_some_operand(*condition, begin)? else {
528561
return Ok(());
529562
};
530-
let end = this.current_loop_end()?;
563+
let fail_cond = this.new_basic_block();
531564
let after_cond = this.new_basic_block();
532565
this.set_terminator(
533566
to_switch,
534567
TerminatorKind::SwitchInt {
535568
discr,
536-
targets: SwitchTargets::static_if(1, after_cond, end),
569+
targets: SwitchTargets::static_if(1, after_cond, fail_cond),
537570
},
538571
expr_id.into(),
539572
);
573+
let fail_cond = this.drop_until_scope(this.drop_scopes.len() - 1, fail_cond);
574+
let end = this.current_loop_end()?;
575+
this.set_goto(fail_cond, end, expr_id.into());
540576
if let Some((_, block)) = this.lower_expr_as_place(after_cond, *body, true)? {
541-
let block = this.pop_drop_scope(block);
577+
let block = scope.pop_and_drop(this, block);
542578
this.set_goto(block, begin, expr_id.into());
579+
} else {
580+
scope.pop_assume_dropped(this);
543581
}
544582
Ok(())
545583
})
@@ -637,7 +675,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
637675
Some(l) => self.labeled_loop_blocks.get(l).ok_or(MirLowerError::UnresolvedLabel)?,
638676
None => self.current_loop_blocks.as_ref().ok_or(MirLowerError::ContinueWithoutLoop)?,
639677
};
640-
self.set_goto(current, loop_data.begin, expr_id.into());
678+
let begin = loop_data.begin;
679+
current = self.drop_until_scope(loop_data.drop_scope_index, current);
680+
self.set_goto(current, begin, expr_id.into());
641681
Ok(None)
642682
},
643683
&Expr::Break { expr, label } => {
@@ -651,10 +691,16 @@ impl<'ctx> MirLowerCtx<'ctx> {
651691
};
652692
current = c;
653693
}
654-
let end = match label {
655-
Some(l) => self.labeled_loop_blocks.get(&l).ok_or(MirLowerError::UnresolvedLabel)?.end.expect("We always generate end for labeled loops"),
656-
None => self.current_loop_end()?,
694+
let (end, drop_scope) = match label {
695+
Some(l) => {
696+
let loop_blocks = self.labeled_loop_blocks.get(&l).ok_or(MirLowerError::UnresolvedLabel)?;
697+
(loop_blocks.end.expect("We always generate end for labeled loops"), loop_blocks.drop_scope_index)
698+
},
699+
None => {
700+
(self.current_loop_end()?, self.current_loop_blocks.as_ref().unwrap().drop_scope_index)
701+
},
657702
};
703+
current = self.drop_until_scope(drop_scope, current);
658704
self.set_goto(current, end, expr_id.into());
659705
Ok(None)
660706
}
@@ -1378,7 +1424,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
13781424
let begin = self.new_basic_block();
13791425
let prev = mem::replace(
13801426
&mut self.current_loop_blocks,
1381-
Some(LoopBlocks { begin, end: None, place }),
1427+
Some(LoopBlocks { begin, end: None, place, drop_scope_index: self.drop_scopes.len() }),
13821428
);
13831429
let prev_label = if let Some(label) = label {
13841430
// We should generate the end now, to make sure that it wouldn't change later. It is
@@ -1391,7 +1437,6 @@ impl<'ctx> MirLowerCtx<'ctx> {
13911437
None
13921438
};
13931439
self.set_goto(prev_block, begin, span);
1394-
self.push_drop_scope();
13951440
f(self, begin)?;
13961441
let my = mem::replace(&mut self.current_loop_blocks, prev).ok_or(
13971442
MirLowerError::ImplementationError("current_loop_blocks is corrupt".to_string()),
@@ -1489,6 +1534,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
14891534
place: Place,
14901535
span: MirSpan,
14911536
) -> Result<Option<Idx<BasicBlock>>> {
1537+
let scope = self.push_drop_scope();
14921538
for statement in statements.iter() {
14931539
match statement {
14941540
hir_def::hir::Statement::Let { pat, initializer, else_branch, type_ref: _ } => {
@@ -1497,6 +1543,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
14971543
let Some((init_place, c)) =
14981544
self.lower_expr_as_place(current, *expr_id, true)?
14991545
else {
1546+
scope.pop_assume_dropped(self);
15001547
return Ok(None);
15011548
};
15021549
current = c;
@@ -1528,18 +1575,25 @@ impl<'ctx> MirLowerCtx<'ctx> {
15281575
}
15291576
}
15301577
hir_def::hir::Statement::Expr { expr, has_semi: _ } => {
1531-
self.push_drop_scope();
1578+
let scope2 = self.push_drop_scope();
15321579
let Some((_, c)) = self.lower_expr_as_place(current, *expr, true)? else {
1580+
scope2.pop_assume_dropped(self);
1581+
scope.pop_assume_dropped(self);
15331582
return Ok(None);
15341583
};
1535-
current = self.pop_drop_scope(c);
1584+
current = scope2.pop_and_drop(self, c);
15361585
}
15371586
}
15381587
}
1539-
match tail {
1540-
Some(tail) => self.lower_expr_to_place(tail, place, current),
1541-
None => Ok(Some(current)),
1588+
if let Some(tail) = tail {
1589+
let Some(c) = self.lower_expr_to_place(tail, place, current)? else {
1590+
scope.pop_assume_dropped(self);
1591+
return Ok(None);
1592+
};
1593+
current = c;
15421594
}
1595+
current = scope.pop_and_drop(self, current);
1596+
Ok(Some(current))
15431597
}
15441598

15451599
fn lower_params_and_bindings(
@@ -1625,16 +1679,34 @@ impl<'ctx> MirLowerCtx<'ctx> {
16251679
current
16261680
}
16271681

1628-
fn push_drop_scope(&mut self) {
1682+
fn push_drop_scope(&mut self) -> DropScopeToken {
16291683
self.drop_scopes.push(DropScope::default());
1684+
DropScopeToken
1685+
}
1686+
1687+
/// Don't call directly
1688+
fn pop_drop_scope_assume_dropped_internal(&mut self) {
1689+
self.drop_scopes.pop();
16301690
}
16311691

1632-
fn pop_drop_scope(&mut self, mut current: BasicBlockId) -> BasicBlockId {
1692+
/// Don't call directly
1693+
fn pop_drop_scope_internal(&mut self, mut current: BasicBlockId) -> BasicBlockId {
16331694
let scope = self.drop_scopes.pop().unwrap();
16341695
self.emit_drop_and_storage_dead_for_scope(&scope, &mut current);
16351696
current
16361697
}
16371698

1699+
fn pop_drop_scope_assert_finished(
1700+
&mut self,
1701+
mut current: BasicBlockId,
1702+
) -> Result<BasicBlockId> {
1703+
current = self.pop_drop_scope_internal(current);
1704+
if !self.drop_scopes.is_empty() {
1705+
implementation_error!("Mismatched count between drop scope push and pops");
1706+
}
1707+
Ok(current)
1708+
}
1709+
16381710
fn emit_drop_and_storage_dead_for_scope(
16391711
&mut self,
16401712
scope: &DropScope,
@@ -1728,7 +1800,7 @@ pub fn mir_body_for_closure_query(
17281800
|_| true,
17291801
)?;
17301802
if let Some(current) = ctx.lower_expr_to_place(*root, return_slot().into(), current)? {
1731-
let current = ctx.pop_drop_scope(current);
1803+
let current = ctx.pop_drop_scope_assert_finished(current)?;
17321804
ctx.set_terminator(current, TerminatorKind::Return, (*root).into());
17331805
}
17341806
let mut upvar_map: FxHashMap<LocalId, Vec<(&CapturedItem, usize)>> = FxHashMap::default();
@@ -1863,7 +1935,7 @@ pub fn lower_to_mir(
18631935
ctx.lower_params_and_bindings([].into_iter(), binding_picker)?
18641936
};
18651937
if let Some(current) = ctx.lower_expr_to_place(root_expr, return_slot().into(), current)? {
1866-
let current = ctx.pop_drop_scope(current);
1938+
let current = ctx.pop_drop_scope_assert_finished(current)?;
18671939
ctx.set_terminator(current, TerminatorKind::Return, root_expr.into());
18681940
}
18691941
Ok(ctx.result)

0 commit comments

Comments
 (0)