@@ -112,6 +112,8 @@ pub struct Frame<'mir, 'tcx, Tag: Provenance = AllocId, Extra = ()> {
112
112
/// The locals are stored as `Option<Value>`s.
113
113
/// `None` represents a local that is currently dead, while a live local
114
114
/// 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!
115
117
pub locals : IndexVec < mir:: Local , LocalState < ' tcx , Tag > > ,
116
118
117
119
/// The span of the `tracing` crate is stored here.
@@ -179,10 +181,6 @@ pub struct LocalState<'tcx, Tag: Provenance = AllocId> {
179
181
pub enum LocalValue < Tag : Provenance = AllocId > {
180
182
/// This local is not currently alive, and cannot be used at all.
181
183
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 ,
186
184
/// A normal, live local.
187
185
/// Mostly for convenience, we re-use the `Operand` type here.
188
186
/// This is an optimization over just always having a pointer here;
@@ -196,12 +194,10 @@ impl<'tcx, Tag: Provenance + 'static> LocalState<'tcx, Tag> {
196
194
///
197
195
/// Note: This may only be invoked from the `Machine::access_local` hook and not from
198
196
/// 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"?
205
201
LocalValue :: Live ( val) => Ok ( val) ,
206
202
}
207
203
}
@@ -211,15 +207,11 @@ impl<'tcx, Tag: Provenance + 'static> LocalState<'tcx, Tag> {
211
207
///
212
208
/// Note: This may only be invoked from the `Machine::access_local_mut` hook and not from
213
209
/// 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) ,
223
215
}
224
216
}
225
217
}
@@ -710,16 +702,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
710
702
} ) ?;
711
703
}
712
704
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 ) } ;
715
707
let mut locals = IndexVec :: from_elem ( dummy, & body. local_decls ) ;
716
708
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.
719
710
let always_live = always_live_locals ( self . body ( ) ) ;
720
711
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 ) ) ;
723
714
}
724
715
}
725
716
// done
@@ -791,59 +782,69 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
791
782
if unwinding { "during unwinding" } else { "returning from function" }
792
783
) ;
793
784
794
- // Sanity check `unwinding`.
785
+ // Check `unwinding`.
795
786
assert_eq ! (
796
787
unwinding,
797
788
match self . frame( ) . loc {
798
789
Ok ( loc) => self . body( ) . basic_blocks( ) [ loc. block] . is_cleanup,
799
790
Err ( _) => true ,
800
791
}
801
792
) ;
802
-
803
793
if unwinding && self . frame_idx ( ) == 0 {
804
794
throw_ub_format ! ( "unwinding past the topmost frame of the stack" ) ;
805
795
}
806
796
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
+ } ;
819
812
813
+ // Cleanup: deallocate locals.
820
814
// 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 ;
823
817
let cleanup = match return_to_block {
824
818
StackPopCleanup :: Goto { .. } => true ,
825
819
StackPopCleanup :: Root { cleanup, .. } => cleanup,
826
820
} ;
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?;
827
835
836
+ // If we are not doing cleanup, also skip everything else.
828
837
if !cleanup {
829
838
assert ! ( self . stack( ) . is_empty( ) , "only the topmost frame should ever be leaked" ) ;
830
839
assert ! ( !unwinding, "tried to skip cleanup during unwinding" ) ;
831
- // Leak the locals, skip validation, skip machine hook.
840
+ // Skip machine hook.
832
841
return Ok ( ( ) ) ;
833
842
}
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
-
842
843
if M :: after_stack_pop ( self , frame, unwinding) ? == StackPopJump :: NoJump {
843
844
// The hook already did everything.
844
- // We want to skip the `info!` below, hence early return.
845
845
return Ok ( ( ) ) ;
846
846
}
847
+
847
848
// Normal return, figure out where to jump.
848
849
if unwinding {
849
850
// Follow the unwind edge.
@@ -874,7 +875,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
874
875
assert ! ( local != mir:: RETURN_PLACE , "Cannot make return place live" ) ;
875
876
trace ! ( "{:?} is now live" , local) ;
876
877
877
- let local_val = LocalValue :: Unallocated ;
878
+ let local_val = LocalValue :: Live ( Operand :: Immediate ( Immediate :: Uninit ) ) ;
878
879
// StorageLive expects the local to be dead, and marks it live.
879
880
let old = mem:: replace ( & mut self . frame_mut ( ) . locals [ local] . value , local_val) ;
880
881
if !matches ! ( old, LocalValue :: Dead ) {
@@ -977,7 +978,9 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug
977
978
978
979
match self . ecx . stack ( ) [ frame] . locals [ local] . value {
979
980
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
+ }
981
984
LocalValue :: Live ( Operand :: Indirect ( mplace) ) => {
982
985
write ! (
983
986
fmt,
0 commit comments