@@ -112,6 +112,8 @@ pub struct Frame<'mir, 'tcx, Tag: Provenance = AllocId, Extra = ()> {
112112 /// The locals are stored as `Option<Value>`s.
113113 /// `None` represents a local that is currently dead, while a live local
114114 /// can either directly contain `Scalar` or refer to some part of an `Allocation`.
115+ ///
116+ /// Do *not* access this directly; always go through the machine hook!
115117 pub locals : IndexVec < mir:: Local , LocalState < ' tcx , Tag > > ,
116118
117119 /// The span of the `tracing` crate is stored here.
@@ -179,10 +181,6 @@ pub struct LocalState<'tcx, Tag: Provenance = AllocId> {
179181pub enum LocalValue < Tag : Provenance = AllocId > {
180182 /// This local is not currently alive, and cannot be used at all.
181183 Dead ,
182- /// This local is alive but not yet allocated. It cannot be read from or have its address taken,
183- /// and will be allocated on the first write. This is to support unsized locals, where we cannot
184- /// know their size in advance.
185- Unallocated ,
186184 /// A normal, live local.
187185 /// Mostly for convenience, we re-use the `Operand` type here.
188186 /// This is an optimization over just always having a pointer here;
@@ -196,12 +194,10 @@ impl<'tcx, Tag: Provenance + 'static> LocalState<'tcx, Tag> {
196194 ///
197195 /// Note: This may only be invoked from the `Machine::access_local` hook and not from
198196 /// anywhere else. You may be invalidating machine invariants if you do!
199- pub fn access ( & self ) -> InterpResult < ' tcx , Operand < Tag > > {
200- match self . value {
201- LocalValue :: Dead => throw_ub ! ( DeadLocal ) ,
202- LocalValue :: Unallocated => {
203- bug ! ( "The type checker should prevent reading from a never-written local" )
204- }
197+ #[ inline]
198+ pub fn access ( & self ) -> InterpResult < ' tcx , & Operand < Tag > > {
199+ match & self . value {
200+ LocalValue :: Dead => throw_ub ! ( DeadLocal ) , // could even be "invalid program"?
205201 LocalValue :: Live ( val) => Ok ( val) ,
206202 }
207203 }
@@ -211,15 +207,11 @@ impl<'tcx, Tag: Provenance + 'static> LocalState<'tcx, Tag> {
211207 ///
212208 /// Note: This may only be invoked from the `Machine::access_local_mut` hook and not from
213209 /// anywhere else. You may be invalidating machine invariants if you do!
214- pub fn access_mut (
215- & mut self ,
216- ) -> InterpResult < ' tcx , Result < & mut LocalValue < Tag > , MemPlace < Tag > > > {
217- match self . value {
218- LocalValue :: Dead => throw_ub ! ( DeadLocal ) ,
219- LocalValue :: Live ( Operand :: Indirect ( mplace) ) => Ok ( Err ( mplace) ) ,
220- ref mut local @ ( LocalValue :: Live ( Operand :: Immediate ( _) ) | LocalValue :: Unallocated ) => {
221- Ok ( Ok ( local) )
222- }
210+ #[ inline]
211+ pub fn access_mut ( & mut self ) -> InterpResult < ' tcx , & mut Operand < Tag > > {
212+ match & mut self . value {
213+ LocalValue :: Dead => throw_ub ! ( DeadLocal ) , // could even be "invalid program"?
214+ LocalValue :: Live ( val) => Ok ( val) ,
223215 }
224216 }
225217}
@@ -710,16 +702,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
710702 } ) ?;
711703 }
712704
713- // Locals are initially unallocated .
714- let dummy = LocalState { value : LocalValue :: Unallocated , layout : Cell :: new ( None ) } ;
705+ // Most locals are initially dead .
706+ let dummy = LocalState { value : LocalValue :: Dead , layout : Cell :: new ( None ) } ;
715707 let mut locals = IndexVec :: from_elem ( dummy, & body. local_decls ) ;
716708
717- // Now mark those locals as dead that we do not want to initialize
718- // Mark locals that use `Storage*` annotations as dead on function entry.
709+ // Now mark those locals as live that have no `Storage*` annotations.
719710 let always_live = always_live_locals ( self . body ( ) ) ;
720711 for local in locals. indices ( ) {
721- if ! always_live. contains ( local) {
722- locals[ local] . value = LocalValue :: Dead ;
712+ if always_live. contains ( local) {
713+ locals[ local] . value = LocalValue :: Live ( Operand :: Immediate ( Immediate :: Uninit ) ) ;
723714 }
724715 }
725716 // done
@@ -791,59 +782,69 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
791782 if unwinding { "during unwinding" } else { "returning from function" }
792783 ) ;
793784
794- // Sanity check `unwinding`.
785+ // Check `unwinding`.
795786 assert_eq ! (
796787 unwinding,
797788 match self . frame( ) . loc {
798789 Ok ( loc) => self . body( ) . basic_blocks( ) [ loc. block] . is_cleanup,
799790 Err ( _) => true ,
800791 }
801792 ) ;
802-
803793 if unwinding && self . frame_idx ( ) == 0 {
804794 throw_ub_format ! ( "unwinding past the topmost frame of the stack" ) ;
805795 }
806796
807- let frame =
808- self . stack_mut ( ) . pop ( ) . expect ( "tried to pop a stack frame, but there were none" ) ;
809-
810- if !unwinding {
811- let op = self . local_to_op ( & frame, mir:: RETURN_PLACE , None ) ?;
812- self . copy_op_transmute ( & op, & frame. return_place ) ?;
813- trace ! ( "{:?}" , self . dump_place( * frame. return_place) ) ;
814- }
815-
816- let return_to_block = frame. return_to_block ;
817-
818- // Now where do we jump next?
797+ // Copy return value. Must of course happen *before* we deallocate the locals.
798+ let copy_ret_result = if !unwinding {
799+ let op = self
800+ . local_to_op ( self . frame ( ) , mir:: RETURN_PLACE , None )
801+ . expect ( "return place should always be live" ) ;
802+ let dest = self . frame ( ) . return_place ;
803+ let err = self . copy_op_transmute ( & op, & dest) ;
804+ trace ! ( "return value: {:?}" , self . dump_place( * dest) ) ;
805+ // We delay actually short-circuiting on this error until *after* the stack frame is
806+ // popped, since we want this error to be attributed to the caller, whose type defines
807+ // this transmute.
808+ err
809+ } else {
810+ Ok ( ( ) )
811+ } ;
819812
813+ // Cleanup: deallocate locals.
820814 // Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
821- // In that case, we return early. We also avoid validation in that case,
822- // because this is CTFE and the final value will be thoroughly validated anyway.
815+ // We do this while the frame is still on the stack, so errors point to the callee.
816+ let return_to_block = self . frame ( ) . return_to_block ;
823817 let cleanup = match return_to_block {
824818 StackPopCleanup :: Goto { .. } => true ,
825819 StackPopCleanup :: Root { cleanup, .. } => cleanup,
826820 } ;
821+ if cleanup {
822+ // We need to take the locals out, since we need to mutate while iterating.
823+ let locals = mem:: take ( & mut self . frame_mut ( ) . locals ) ;
824+ for local in & locals {
825+ self . deallocate_local ( local. value ) ?;
826+ }
827+ }
828+
829+ // All right, now it is time to actually pop the frame.
830+ // Note that its locals are gone already, but that's fine.
831+ let frame =
832+ self . stack_mut ( ) . pop ( ) . expect ( "tried to pop a stack frame, but there were none" ) ;
833+ // Report error from return value copy, if any.
834+ copy_ret_result?;
827835
836+ // If we are not doing cleanup, also skip everything else.
828837 if !cleanup {
829838 assert ! ( self . stack( ) . is_empty( ) , "only the topmost frame should ever be leaked" ) ;
830839 assert ! ( !unwinding, "tried to skip cleanup during unwinding" ) ;
831- // Leak the locals, skip validation, skip machine hook.
840+ // Skip machine hook.
832841 return Ok ( ( ) ) ;
833842 }
834-
835- trace ! ( "locals: {:#?}" , frame. locals) ;
836-
837- // Cleanup: deallocate all locals that are backed by an allocation.
838- for local in & frame. locals {
839- self . deallocate_local ( local. value ) ?;
840- }
841-
842843 if M :: after_stack_pop ( self , frame, unwinding) ? == StackPopJump :: NoJump {
843844 // The hook already did everything.
844- // We want to skip the `info!` below, hence early return.
845845 return Ok ( ( ) ) ;
846846 }
847+
847848 // Normal return, figure out where to jump.
848849 if unwinding {
849850 // Follow the unwind edge.
@@ -874,7 +875,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
874875 assert ! ( local != mir:: RETURN_PLACE , "Cannot make return place live" ) ;
875876 trace ! ( "{:?} is now live" , local) ;
876877
877- let local_val = LocalValue :: Unallocated ;
878+ let local_val = LocalValue :: Live ( Operand :: Immediate ( Immediate :: Uninit ) ) ;
878879 // StorageLive expects the local to be dead, and marks it live.
879880 let old = mem:: replace ( & mut self . frame_mut ( ) . locals [ local] . value , local_val) ;
880881 if !matches ! ( old, LocalValue :: Dead ) {
@@ -977,7 +978,9 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug
977978
978979 match self . ecx . stack ( ) [ frame] . locals [ local] . value {
979980 LocalValue :: Dead => write ! ( fmt, " is dead" ) ?,
980- LocalValue :: Unallocated => write ! ( fmt, " is unallocated" ) ?,
981+ LocalValue :: Live ( Operand :: Immediate ( Immediate :: Uninit ) ) => {
982+ write ! ( fmt, " is uninitialized" ) ?
983+ }
981984 LocalValue :: Live ( Operand :: Indirect ( mplace) ) => {
982985 write ! (
983986 fmt,
0 commit comments