@@ -21,7 +21,7 @@ use rustc_data_structures::fx::FxHashMap;
21
21
22
22
use super :: {
23
23
Immediate , Operand , MemPlace , MPlaceTy , Place , PlaceTy , ScalarMaybeUndef ,
24
- Memory , Machine
24
+ Memory , Machine , StackPopInfo
25
25
} ;
26
26
27
27
pub struct InterpCx < ' mir , ' tcx , M : Machine < ' mir , ' tcx > > {
@@ -60,6 +60,9 @@ pub struct Frame<'mir, 'tcx, Tag=(), Extra=()> {
60
60
/// The span of the call site.
61
61
pub span : source_map:: Span ,
62
62
63
+ /// Extra data for the machine.
64
+ pub extra : Extra ,
65
+
63
66
////////////////////////////////////////////////////////////////////////////////
64
67
// Return place and locals
65
68
////////////////////////////////////////////////////////////////////////////////
@@ -82,21 +85,22 @@ pub struct Frame<'mir, 'tcx, Tag=(), Extra=()> {
82
85
////////////////////////////////////////////////////////////////////////////////
83
86
/// The block that is currently executed (or will be executed after the above call stacks
84
87
/// return).
85
- pub block : mir:: BasicBlock ,
88
+ /// If this is `None`, we are unwinding and this function doesn't need any clean-up.
89
+ /// Just continue the same as with `Resume`.
90
+ pub block : Option < mir:: BasicBlock > ,
86
91
87
92
/// The index of the currently evaluated statement.
88
93
pub stmt : usize ,
89
-
90
- /// Extra data for the machine.
91
- pub extra : Extra ,
92
94
}
93
95
94
96
#[ derive( Clone , Eq , PartialEq , Debug ) ] // Miri debug-prints these
95
97
pub enum StackPopCleanup {
96
98
/// Jump to the next block in the caller, or cause UB if None (that's a function
97
99
/// that may never return). Also store layout of return place so
98
100
/// we can validate it at that layout.
99
- Goto ( Option < mir:: BasicBlock > ) ,
101
+ /// `ret` stores the block we jump to on a normal return, while 'unwind'
102
+ /// stores the block used for cleanup during unwinding
103
+ Goto { ret : Option < mir:: BasicBlock > , unwind : Option < mir:: BasicBlock > } ,
100
104
/// Just do nohing: Used by Main and for the box_alloc hook in miri.
101
105
/// `cleanup` says whether locals are deallocated. Static computation
102
106
/// wants them leaked to intern what they need (and just throw away
@@ -489,7 +493,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
489
493
let extra = M :: stack_push ( self ) ?;
490
494
self . stack . push ( Frame {
491
495
body,
492
- block : mir:: START_BLOCK ,
496
+ block : Some ( mir:: START_BLOCK ) ,
493
497
return_to_block,
494
498
return_place,
495
499
// empty local array, we fill it in below, after we are inside the stack frame and
@@ -547,60 +551,118 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
547
551
}
548
552
}
549
553
550
- pub ( super ) fn pop_stack_frame ( & mut self ) -> InterpResult < ' tcx > {
551
- info ! ( "LEAVING({}) {}" , self . cur_frame( ) , self . frame( ) . instance) ;
554
+ /// Pops the current frame from the stack, deallocating the
555
+ /// memory for allocated locals.
556
+ ///
557
+ /// If `unwinding` is `false`, then we are performing a normal return
558
+ /// from a function. In this case, we jump back into the frame of the caller,
559
+ /// and continue execution as normal.
560
+ ///
561
+ /// If `unwinding` is `true`, then we are in the middle of a panic,
562
+ /// and need to unwind this frame. In this case, we jump to the
563
+ /// `cleanup` block for the function, which is responsible for running
564
+ /// `Drop` impls for any locals that have been initialized at this point.
565
+ /// The cleanup block ends with a special `Resume` terminator, which will
566
+ /// cause us to continue unwinding.
567
+ pub ( super ) fn pop_stack_frame (
568
+ & mut self ,
569
+ unwinding : bool
570
+ ) -> InterpResult < ' tcx > {
571
+ info ! ( "LEAVING({}) {} (unwinding = {})" ,
572
+ self . cur_frame( ) , self . frame( ) . instance, unwinding) ;
573
+
574
+ // Sanity check `unwinding`.
575
+ assert_eq ! (
576
+ unwinding,
577
+ match self . frame( ) . block {
578
+ None => true ,
579
+ Some ( block) => self . body( ) . basic_blocks( ) [ block] . is_cleanup
580
+ }
581
+ ) ;
582
+
552
583
:: log_settings:: settings ( ) . indentation -= 1 ;
553
584
let frame = self . stack . pop ( ) . expect (
554
585
"tried to pop a stack frame, but there were none" ,
555
586
) ;
556
- M :: stack_pop ( self , frame. extra ) ?;
557
- // Abort early if we do not want to clean up: We also avoid validation in that case,
587
+ let stack_pop_info = M :: stack_pop ( self , frame. extra , unwinding) ?;
588
+ if let ( false , StackPopInfo :: StopUnwinding ) = ( unwinding, stack_pop_info) {
589
+ bug ! ( "Attempted to stop unwinding while there is no unwinding!" ) ;
590
+ }
591
+
592
+ // Now where do we jump next?
593
+
594
+ // Determine if we leave this function normally or via unwinding.
595
+ let cur_unwinding = if let StackPopInfo :: StopUnwinding = stack_pop_info {
596
+ false
597
+ } else {
598
+ unwinding
599
+ } ;
600
+
601
+ // Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
602
+ // In that case, we return early. We also avoid validation in that case,
558
603
// because this is CTFE and the final value will be thoroughly validated anyway.
559
- match frame. return_to_block {
560
- StackPopCleanup :: Goto ( _) => { } ,
561
- StackPopCleanup :: None { cleanup } => {
562
- if !cleanup {
563
- assert ! ( self . stack. is_empty( ) , "only the topmost frame should ever be leaked" ) ;
564
- // Leak the locals, skip validation.
565
- return Ok ( ( ) ) ;
566
- }
567
- }
604
+ let ( cleanup, next_block) = match frame. return_to_block {
605
+ StackPopCleanup :: Goto { ret, unwind } => {
606
+ ( true , Some ( if cur_unwinding { unwind } else { ret } ) )
607
+ } ,
608
+ StackPopCleanup :: None { cleanup, .. } => ( cleanup, None )
609
+ } ;
610
+
611
+ if !cleanup {
612
+ assert ! ( self . stack. is_empty( ) , "only the topmost frame should ever be leaked" ) ;
613
+ assert ! ( next_block. is_none( ) , "tried to skip cleanup when we have a next block!" ) ;
614
+ // Leak the locals, skip validation.
615
+ return Ok ( ( ) ) ;
568
616
}
569
- // Deallocate all locals that are backed by an allocation.
617
+
618
+ // Cleanup: deallocate all locals that are backed by an allocation.
570
619
for local in frame. locals {
571
620
self . deallocate_local ( local. value ) ?;
572
621
}
573
- // Validate the return value. Do this after deallocating so that we catch dangling
574
- // references.
575
- if let Some ( return_place) = frame. return_place {
576
- if M :: enforce_validity ( self ) {
577
- // Data got changed, better make sure it matches the type!
578
- // It is still possible that the return place held invalid data while
579
- // the function is running, but that's okay because nobody could have
580
- // accessed that same data from the "outside" to observe any broken
581
- // invariant -- that is, unless a function somehow has a ptr to
582
- // its return place... but the way MIR is currently generated, the
583
- // return place is always a local and then this cannot happen.
584
- self . validate_operand (
585
- self . place_to_op ( return_place) ?,
586
- vec ! [ ] ,
587
- None ,
588
- ) ?;
589
- }
622
+
623
+
624
+ trace ! ( "StackPopCleanup: {:?} StackPopInfo: {:?} cur_unwinding = {:?}" ,
625
+ frame. return_to_block, stack_pop_info, cur_unwinding) ;
626
+ if cur_unwinding {
627
+ // Follow the unwind edge.
628
+ let unwind = next_block. expect ( "Encounted StackPopCleanup::None when unwinding!" ) ;
629
+ let next_frame = self . frame_mut ( ) ;
630
+ // If `unwind` is `None`, we'll leave that function immediately again.
631
+ next_frame. block = unwind;
632
+ next_frame. stmt = 0 ;
590
633
} else {
591
- // Uh, that shouldn't happen... the function did not intend to return
592
- throw_ub ! ( Unreachable )
593
- }
594
- // Jump to new block -- *after* validation so that the spans make more sense.
595
- match frame. return_to_block {
596
- StackPopCleanup :: Goto ( block) => {
597
- self . goto_block ( block) ?;
634
+ // Follow the normal return edge.
635
+ // Validate the return value. Do this after deallocating so that we catch dangling
636
+ // references.
637
+ if let Some ( return_place) = frame. return_place {
638
+ if M :: enforce_validity ( self ) {
639
+ // Data got changed, better make sure it matches the type!
640
+ // It is still possible that the return place held invalid data while
641
+ // the function is running, but that's okay because nobody could have
642
+ // accessed that same data from the "outside" to observe any broken
643
+ // invariant -- that is, unless a function somehow has a ptr to
644
+ // its return place... but the way MIR is currently generated, the
645
+ // return place is always a local and then this cannot happen.
646
+ self . validate_operand (
647
+ self . place_to_op ( return_place) ?,
648
+ vec ! [ ] ,
649
+ None ,
650
+ ) ?;
651
+ }
652
+ } else {
653
+ // Uh, that shouldn't happen... the function did not intend to return
654
+ throw_ub ! ( Unreachable ) ;
655
+ }
656
+
657
+ // Jump to new block -- *after* validation so that the spans make more sense.
658
+ if let Some ( ret) = next_block {
659
+ self . goto_block ( ret) ?;
598
660
}
599
- StackPopCleanup :: None { .. } => { }
600
661
}
601
662
602
663
if self . stack . len ( ) > 0 {
603
- info ! ( "CONTINUING({}) {}" , self . cur_frame( ) , self . frame( ) . instance) ;
664
+ info ! ( "CONTINUING({}) {} (unwinding = {})" ,
665
+ self . cur_frame( ) , self . frame( ) . instance, cur_unwinding) ;
604
666
}
605
667
606
668
Ok ( ( ) )
@@ -745,16 +807,20 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
745
807
} else {
746
808
last_span = Some ( span) ;
747
809
}
748
- let block = & body. basic_blocks ( ) [ block] ;
749
- let source_info = if stmt < block. statements . len ( ) {
750
- block. statements [ stmt] . source_info
751
- } else {
752
- block. terminator ( ) . source_info
753
- } ;
754
- let lint_root = match body. source_scope_local_data {
755
- mir:: ClearCrossCrate :: Set ( ref ivs) => Some ( ivs[ source_info. scope ] . lint_root ) ,
756
- mir:: ClearCrossCrate :: Clear => None ,
757
- } ;
810
+
811
+ let lint_root = block. and_then ( |block| {
812
+ let block = & body. basic_blocks ( ) [ block] ;
813
+ let source_info = if stmt < block. statements . len ( ) {
814
+ block. statements [ stmt] . source_info
815
+ } else {
816
+ block. terminator ( ) . source_info
817
+ } ;
818
+ match body. source_scope_local_data {
819
+ mir:: ClearCrossCrate :: Set ( ref ivs) => Some ( ivs[ source_info. scope ] . lint_root ) ,
820
+ mir:: ClearCrossCrate :: Clear => None ,
821
+ }
822
+ } ) ;
823
+
758
824
frames. push ( FrameInfo { call_site : span, instance, lint_root } ) ;
759
825
}
760
826
trace ! ( "generate stacktrace: {:#?}, {:?}" , frames, explicit_span) ;
0 commit comments