@@ -21,7 +21,7 @@ use rustc_data_structures::fx::FxHashMap;
2121
2222use super :: {
2323 Immediate , Operand , MemPlace , MPlaceTy , Place , PlaceTy , ScalarMaybeUndef ,
24- Memory , Machine
24+ Memory , Machine , StackPopInfo
2525} ;
2626
2727pub struct InterpCx < ' mir , ' tcx , M : Machine < ' mir , ' tcx > > {
@@ -60,6 +60,9 @@ pub struct Frame<'mir, 'tcx, Tag=(), Extra=()> {
6060 /// The span of the call site.
6161 pub span : source_map:: Span ,
6262
63+ /// Extra data for the machine.
64+ pub extra : Extra ,
65+
6366 ////////////////////////////////////////////////////////////////////////////////
6467 // Return place and locals
6568 ////////////////////////////////////////////////////////////////////////////////
@@ -82,21 +85,22 @@ pub struct Frame<'mir, 'tcx, Tag=(), Extra=()> {
8285 ////////////////////////////////////////////////////////////////////////////////
8386 /// The block that is currently executed (or will be executed after the above call stacks
8487 /// 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 > ,
8691
8792 /// The index of the currently evaluated statement.
8893 pub stmt : usize ,
89-
90- /// Extra data for the machine.
91- pub extra : Extra ,
9294}
9395
9496#[ derive( Clone , Eq , PartialEq , Debug ) ] // Miri debug-prints these
9597pub enum StackPopCleanup {
9698 /// Jump to the next block in the caller, or cause UB if None (that's a function
9799 /// that may never return). Also store layout of return place so
98100 /// 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 > } ,
100104 /// Just do nohing: Used by Main and for the box_alloc hook in miri.
101105 /// `cleanup` says whether locals are deallocated. Static computation
102106 /// 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> {
489493 let extra = M :: stack_push ( self ) ?;
490494 self . stack . push ( Frame {
491495 body,
492- block : mir:: START_BLOCK ,
496+ block : Some ( mir:: START_BLOCK ) ,
493497 return_to_block,
494498 return_place,
495499 // 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> {
547551 }
548552 }
549553
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+
552583 :: log_settings:: settings ( ) . indentation -= 1 ;
553584 let frame = self . stack . pop ( ) . expect (
554585 "tried to pop a stack frame, but there were none" ,
555586 ) ;
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,
558603 // 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 ( ( ) ) ;
568616 }
569- // Deallocate all locals that are backed by an allocation.
617+
618+ // Cleanup: deallocate all locals that are backed by an allocation.
570619 for local in frame. locals {
571620 self . deallocate_local ( local. value ) ?;
572621 }
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 ;
590633 } 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) ?;
598660 }
599- StackPopCleanup :: None { .. } => { }
600661 }
601662
602663 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) ;
604666 }
605667
606668 Ok ( ( ) )
@@ -745,16 +807,20 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
745807 } else {
746808 last_span = Some ( span) ;
747809 }
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+
758824 frames. push ( FrameInfo { call_site : span, instance, lint_root } ) ;
759825 }
760826 trace ! ( "generate stacktrace: {:#?}, {:?}" , frames, explicit_span) ;
0 commit comments