@@ -48,6 +48,7 @@ struct LoopBlocks {
48
48
/// `None` for loops that are not terminating
49
49
end: Option<BasicBlockId>,
50
50
place: Place,
51
+ drop_scope_index: usize,
51
52
}
52
53
53
54
#[derive(Debug, Clone, Default)]
@@ -101,6 +102,35 @@ pub enum MirLowerError {
101
102
GenericArgNotProvided(TypeOrConstParamId, Substitution),
102
103
}
103
104
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
+
104
134
impl MirLowerError {
105
135
pub fn pretty_print(
106
136
&self,
@@ -506,7 +536,6 @@ impl<'ctx> MirLowerCtx<'ctx> {
506
536
self.lower_loop(current, place.clone(), Some(*label), expr_id.into(), |this, begin| {
507
537
if let Some(current) = this.lower_block_to_place(statements, begin, *tail, place, expr_id.into())? {
508
538
let end = this.current_loop_end()?;
509
- let current = this.pop_drop_scope(current);
510
539
this.set_goto(current, end, expr_id.into());
511
540
}
512
541
Ok(())
@@ -516,30 +545,39 @@ impl<'ctx> MirLowerCtx<'ctx> {
516
545
}
517
546
}
518
547
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);
521
551
this.set_goto(current, begin, expr_id.into());
552
+ } else {
553
+ scope.pop_assume_dropped(this);
522
554
}
523
555
Ok(())
524
556
}),
525
557
Expr::While { condition, body, label } => {
526
558
self.lower_loop(current, place, *label, expr_id.into(),|this, begin| {
559
+ let scope = this.push_drop_scope();
527
560
let Some((discr, to_switch)) = this.lower_expr_to_some_operand(*condition, begin)? else {
528
561
return Ok(());
529
562
};
530
- let end = this.current_loop_end()? ;
563
+ let fail_cond = this.new_basic_block() ;
531
564
let after_cond = this.new_basic_block();
532
565
this.set_terminator(
533
566
to_switch,
534
567
TerminatorKind::SwitchInt {
535
568
discr,
536
- targets: SwitchTargets::static_if(1, after_cond, end ),
569
+ targets: SwitchTargets::static_if(1, after_cond, fail_cond ),
537
570
},
538
571
expr_id.into(),
539
572
);
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());
540
576
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);
542
578
this.set_goto(block, begin, expr_id.into());
579
+ } else {
580
+ scope.pop_assume_dropped(this);
543
581
}
544
582
Ok(())
545
583
})
@@ -637,7 +675,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
637
675
Some(l) => self.labeled_loop_blocks.get(l).ok_or(MirLowerError::UnresolvedLabel)?,
638
676
None => self.current_loop_blocks.as_ref().ok_or(MirLowerError::ContinueWithoutLoop)?,
639
677
};
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());
641
681
Ok(None)
642
682
},
643
683
&Expr::Break { expr, label } => {
@@ -651,10 +691,16 @@ impl<'ctx> MirLowerCtx<'ctx> {
651
691
};
652
692
current = c;
653
693
}
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
+ },
657
702
};
703
+ current = self.drop_until_scope(drop_scope, current);
658
704
self.set_goto(current, end, expr_id.into());
659
705
Ok(None)
660
706
}
@@ -1378,7 +1424,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
1378
1424
let begin = self.new_basic_block();
1379
1425
let prev = mem::replace(
1380
1426
&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() }),
1382
1428
);
1383
1429
let prev_label = if let Some(label) = label {
1384
1430
// 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> {
1391
1437
None
1392
1438
};
1393
1439
self.set_goto(prev_block, begin, span);
1394
- self.push_drop_scope();
1395
1440
f(self, begin)?;
1396
1441
let my = mem::replace(&mut self.current_loop_blocks, prev).ok_or(
1397
1442
MirLowerError::ImplementationError("current_loop_blocks is corrupt".to_string()),
@@ -1489,6 +1534,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
1489
1534
place: Place,
1490
1535
span: MirSpan,
1491
1536
) -> Result<Option<Idx<BasicBlock>>> {
1537
+ let scope = self.push_drop_scope();
1492
1538
for statement in statements.iter() {
1493
1539
match statement {
1494
1540
hir_def::hir::Statement::Let { pat, initializer, else_branch, type_ref: _ } => {
@@ -1497,6 +1543,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
1497
1543
let Some((init_place, c)) =
1498
1544
self.lower_expr_as_place(current, *expr_id, true)?
1499
1545
else {
1546
+ scope.pop_assume_dropped(self);
1500
1547
return Ok(None);
1501
1548
};
1502
1549
current = c;
@@ -1528,18 +1575,25 @@ impl<'ctx> MirLowerCtx<'ctx> {
1528
1575
}
1529
1576
}
1530
1577
hir_def::hir::Statement::Expr { expr, has_semi: _ } => {
1531
- self.push_drop_scope();
1578
+ let scope2 = self.push_drop_scope();
1532
1579
let Some((_, c)) = self.lower_expr_as_place(current, *expr, true)? else {
1580
+ scope2.pop_assume_dropped(self);
1581
+ scope.pop_assume_dropped(self);
1533
1582
return Ok(None);
1534
1583
};
1535
- current = self.pop_drop_scope( c);
1584
+ current = scope2.pop_and_drop(self, c);
1536
1585
}
1537
1586
}
1538
1587
}
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;
1542
1594
}
1595
+ current = scope.pop_and_drop(self, current);
1596
+ Ok(Some(current))
1543
1597
}
1544
1598
1545
1599
fn lower_params_and_bindings(
@@ -1625,16 +1679,34 @@ impl<'ctx> MirLowerCtx<'ctx> {
1625
1679
current
1626
1680
}
1627
1681
1628
- fn push_drop_scope(&mut self) {
1682
+ fn push_drop_scope(&mut self) -> DropScopeToken {
1629
1683
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();
1630
1690
}
1631
1691
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 {
1633
1694
let scope = self.drop_scopes.pop().unwrap();
1634
1695
self.emit_drop_and_storage_dead_for_scope(&scope, &mut current);
1635
1696
current
1636
1697
}
1637
1698
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
+
1638
1710
fn emit_drop_and_storage_dead_for_scope(
1639
1711
&mut self,
1640
1712
scope: &DropScope,
@@ -1728,7 +1800,7 @@ pub fn mir_body_for_closure_query(
1728
1800
|_| true,
1729
1801
)?;
1730
1802
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)? ;
1732
1804
ctx.set_terminator(current, TerminatorKind::Return, (*root).into());
1733
1805
}
1734
1806
let mut upvar_map: FxHashMap<LocalId, Vec<(&CapturedItem, usize)>> = FxHashMap::default();
@@ -1863,7 +1935,7 @@ pub fn lower_to_mir(
1863
1935
ctx.lower_params_and_bindings([].into_iter(), binding_picker)?
1864
1936
};
1865
1937
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)? ;
1867
1939
ctx.set_terminator(current, TerminatorKind::Return, root_expr.into());
1868
1940
}
1869
1941
Ok(ctx.result)
0 commit comments