1313//! can be registered. The [`State`] can be queried to retrieve the abstract value stored for a
1414//! certain place by passing the map.
1515//!
16- //! This framework is currently experimental. In particular, the features related to references are
17- //! currently guarded behind `-Zunsound-mir-opts`, because their correctness relies on Stacked
18- //! Borrows. Also, only places with scalar types can be tracked currently. This is because scalar
19- //! types are indivisible, which simplifies the current implementation. But this limitation could be
20- //! lifted in the future.
16+ //! This framework is currently experimental. Originally, it supported shared references and enum
17+ //! variants. However, it was discovered that both of these were unsound, and especially references
18+ //! had subtle but serious issues. In the future, they could be added back in, but we should clarify
19+ //! the rules for optimizations that rely on the aliasing model first.
2120//!
2221//!
2322//! # Notes
2827//! - The assignment logic in `State::assign_place_idx` assumes that the places are non-overlapping,
2928//! or identical. Note that this refers to place expressions, not memory locations.
3029//!
31- //! - Since pointers (and mutable references) are not tracked, but can be used to change the
32- //! underlying values, we are conservative and immediately flood the referenced place upon creation
33- //! of the pointer. Also, we have to uphold the invariant that the place must stay that way as long
34- //! as this mutable access could exist. However...
35- //!
36- //! - Without an aliasing model like Stacked Borrows (i.e., `-Zunsound-mir-opts` is not given),
37- //! such mutable access is never revoked. And even shared references could be used to obtain the
38- //! address of a value an modify it. When not assuming Stacked Borrows, we prevent such places from
39- //! being tracked at all. This means that the analysis itself can assume that writes to a *tracked*
40- //! place always invalidate all other means of mutable access, regardless of the aliasing model.
41- //!
42- //! - Likewise, the analysis itself assumes that if the value of a *tracked* place behind a shared
43- //! reference is changed, the reference may not be used to access that value anymore. This is true
44- //! for all places if the referenced type is `Freeze` and we assume Stacked Borrows. If we are not
45- //! assuming Stacking Borrows (or if the referenced type could be `!Freeze`), we again prevent such
46- //! places from being tracked at all, making this assertion trivially true.
30+ //! - Currently, places that have their reference taken cannot be tracked. Although this would be
31+ //! possible, it has to rely on some aliasing model, which we are not ready to commit to yet.
32+ //! Because of that, we can assume that the only way to change the value behind a tracked place is
33+ //! by direct assignment.
4734
4835use std:: fmt:: { Debug , Formatter } ;
4936
5037use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
5138use rustc_index:: vec:: IndexVec ;
5239use rustc_middle:: mir:: tcx:: PlaceTy ;
53- use rustc_middle:: mir:: visit:: { PlaceContext , Visitor } ;
40+ use rustc_middle:: mir:: visit:: { MutatingUseContext , PlaceContext , Visitor } ;
5441use rustc_middle:: mir:: * ;
5542use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
5643use rustc_target:: abi:: VariantIdx ;
@@ -96,10 +83,7 @@ pub trait ValueAnalysis<'tcx> {
9683 state. flood_with ( place. as_ref ( ) , self . map ( ) , Self :: Value :: bottom ( ) ) ;
9784 }
9885 StatementKind :: Retag ( ..) => {
99- // A retag modifies the provenance of references. Currently references are only
100- // tracked if `-Zunsound-mir-opts` is given, but this might change in the future.
101- // However, it is still unclear how retags should be handled:
102- // https://github.com/rust-lang/rust/pull/101168#discussion_r985304895
86+ // We don't track references.
10387 }
10488 StatementKind :: Nop
10589 | StatementKind :: FakeRead ( ..)
@@ -156,29 +140,23 @@ pub trait ValueAnalysis<'tcx> {
156140 & self ,
157141 rvalue : & Rvalue < ' tcx > ,
158142 state : & mut State < Self :: Value > ,
159- ) -> ValueOrPlaceOrRef < Self :: Value > {
143+ ) -> ValueOrPlace < Self :: Value > {
160144 self . super_rvalue ( rvalue, state)
161145 }
162146
163147 fn super_rvalue (
164148 & self ,
165149 rvalue : & Rvalue < ' tcx > ,
166150 state : & mut State < Self :: Value > ,
167- ) -> ValueOrPlaceOrRef < Self :: Value > {
151+ ) -> ValueOrPlace < Self :: Value > {
168152 match rvalue {
169- Rvalue :: Use ( operand) => self . handle_operand ( operand, state) . into ( ) ,
170- Rvalue :: Ref ( _, BorrowKind :: Shared , place) => self
171- . map ( )
172- . find ( place. as_ref ( ) )
173- . map ( ValueOrPlaceOrRef :: Ref )
174- . unwrap_or ( ValueOrPlaceOrRef :: top ( ) ) ,
175- Rvalue :: Ref ( _, _, place) | Rvalue :: AddressOf ( _, place) => {
176- // This is not a `&x` reference and could be used for modification.
177- state. flood ( place. as_ref ( ) , self . map ( ) ) ;
178- ValueOrPlaceOrRef :: top ( )
179- }
153+ Rvalue :: Use ( operand) => self . handle_operand ( operand, state) ,
180154 Rvalue :: CopyForDeref ( place) => {
181- self . handle_operand ( & Operand :: Copy ( * place) , state) . into ( )
155+ self . handle_operand ( & Operand :: Copy ( * place) , state)
156+ }
157+ Rvalue :: Ref ( ..) | Rvalue :: AddressOf ( ..) => {
158+ // We don't track such places.
159+ ValueOrPlace :: top ( )
182160 }
183161 Rvalue :: Repeat ( ..)
184162 | Rvalue :: ThreadLocalRef ( ..)
@@ -192,7 +170,7 @@ pub trait ValueAnalysis<'tcx> {
192170 | Rvalue :: Aggregate ( ..)
193171 | Rvalue :: ShallowInitBox ( ..) => {
194172 // No modification is possible through these r-values.
195- ValueOrPlaceOrRef :: top ( )
173+ ValueOrPlace :: top ( )
196174 }
197175 }
198176 }
@@ -247,14 +225,13 @@ pub trait ValueAnalysis<'tcx> {
247225 self . super_terminator ( terminator, state)
248226 }
249227
250- fn super_terminator ( & self , terminator : & Terminator < ' tcx > , state : & mut State < Self :: Value > ) {
228+ fn super_terminator ( & self , terminator : & Terminator < ' tcx > , _state : & mut State < Self :: Value > ) {
251229 match & terminator. kind {
252230 TerminatorKind :: Call { .. } | TerminatorKind :: InlineAsm { .. } => {
253231 // Effect is applied by `handle_call_return`.
254232 }
255- TerminatorKind :: Drop { place, .. } => {
256- // Place can still be accessed after drop, and drop has mutable access to it.
257- state. flood ( place. as_ref ( ) , self . map ( ) ) ;
233+ TerminatorKind :: Drop { .. } => {
234+ // We don't track dropped places.
258235 }
259236 TerminatorKind :: DropAndReplace { .. } | TerminatorKind :: Yield { .. } => {
260237 // They would have an effect, but are not allowed in this phase.
@@ -522,17 +499,17 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
522499 }
523500 }
524501
525- pub fn assign ( & mut self , target : PlaceRef < ' _ > , result : ValueOrPlaceOrRef < V > , map : & Map ) {
502+ pub fn assign ( & mut self , target : PlaceRef < ' _ > , result : ValueOrPlace < V > , map : & Map ) {
526503 if let Some ( target) = map. find ( target) {
527504 self . assign_idx ( target, result, map) ;
528505 } else {
529506 // We don't track this place nor any projections, assignment can be ignored.
530507 }
531508 }
532509
533- pub fn assign_idx ( & mut self , target : PlaceIndex , result : ValueOrPlaceOrRef < V > , map : & Map ) {
510+ pub fn assign_idx ( & mut self , target : PlaceIndex , result : ValueOrPlace < V > , map : & Map ) {
534511 match result {
535- ValueOrPlaceOrRef :: Value ( value) => {
512+ ValueOrPlace :: Value ( value) => {
536513 // First flood the target place in case we also track any projections (although
537514 // this scenario is currently not well-supported by the API).
538515 self . flood_idx ( target, map) ;
@@ -541,21 +518,7 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
541518 values[ value_index] = value;
542519 }
543520 }
544- ValueOrPlaceOrRef :: Place ( source) => self . assign_place_idx ( target, source, map) ,
545- ValueOrPlaceOrRef :: Ref ( source) => {
546- let StateData :: Reachable ( values) = & mut self . 0 else { return } ;
547- if let Some ( value_index) = map. places [ target] . value_index {
548- values[ value_index] = V :: top ( ) ;
549- }
550- // Instead of tracking of *where* a reference points to (as in, which memory
551- // location), we track *what* it points to (as in, what do we know about the
552- // target). For an assignment `x = &y`, we thus copy the info of `y` to `*x`.
553- if let Some ( target_deref) = map. apply ( target, TrackElem :: Deref ) {
554- // We know here that `*x` is `Freeze`, because we only track through
555- // dereferences if the target type is `Freeze`.
556- self . assign_place_idx ( target_deref, source, map) ;
557- }
558- }
521+ ValueOrPlace :: Place ( source) => self . assign_place_idx ( target, source, map) ,
559522 }
560523 }
561524
@@ -625,45 +588,27 @@ impl Map {
625588 filter : impl FnMut ( Ty < ' tcx > ) -> bool ,
626589 ) -> Self {
627590 let mut map = Self :: new ( ) ;
628-
629- // If `-Zunsound-mir-opts` is given, tracking through references, and tracking of places
630- // that have their reference taken is allowed. This would be "unsound" in the sense that
631- // the correctness relies on an aliasing model similar to Stacked Borrows (which is
632- // not yet guaranteed).
633- if tcx. sess . opts . unstable_opts . unsound_mir_opts {
634- // We might want to add additional limitations. If a struct has 10 boxed fields of
635- // itself, there will currently be `10.pow(max_derefs)` tracked places.
636- map. register_with_filter ( tcx, body, 2 , filter, & FxHashSet :: default ( ) ) ;
637- } else {
638- map. register_with_filter ( tcx, body, 0 , filter, & escaped_places ( body) ) ;
639- }
640-
591+ map. register_with_filter ( tcx, body, filter, & escaped_places ( body) ) ;
641592 debug ! ( "registered {} places ({} nodes in total)" , map. value_count, map. places. len( ) ) ;
642593 map
643594 }
644595
645- /// Register all non-excluded places that pass the filter, up to a certain dereference depth .
596+ /// Register all non-excluded places that pass the filter.
646597 fn register_with_filter < ' tcx > (
647598 & mut self ,
648599 tcx : TyCtxt < ' tcx > ,
649600 body : & Body < ' tcx > ,
650- max_derefs : u32 ,
651601 mut filter : impl FnMut ( Ty < ' tcx > ) -> bool ,
652602 exclude : & FxHashSet < Place < ' tcx > > ,
653603 ) {
654- // This is used to tell whether a type is `Freeze`.
655- let param_env = tcx. param_env_reveal_all_normalized ( body. source . def_id ( ) ) ;
656-
657604 let mut projection = Vec :: new ( ) ;
658605 for ( local, decl) in body. local_decls . iter_enumerated ( ) {
659606 self . register_with_filter_rec (
660607 tcx,
661- max_derefs,
662608 local,
663609 & mut projection,
664610 decl. ty ,
665611 & mut filter,
666- param_env,
667612 exclude,
668613 ) ;
669614 }
@@ -672,12 +617,10 @@ impl Map {
672617 fn register_with_filter_rec < ' tcx > (
673618 & mut self ,
674619 tcx : TyCtxt < ' tcx > ,
675- max_derefs : u32 ,
676620 local : Local ,
677621 projection : & mut Vec < PlaceElem < ' tcx > > ,
678622 ty : Ty < ' tcx > ,
679623 filter : & mut impl FnMut ( Ty < ' tcx > ) -> bool ,
680- param_env : ty:: ParamEnv < ' tcx > ,
681624 exclude : & FxHashSet < Place < ' tcx > > ,
682625 ) {
683626 if exclude. contains ( & Place { local, projection : tcx. intern_place_elems ( projection) } ) {
@@ -689,34 +632,14 @@ impl Map {
689632 // This might fail if `ty` is not scalar.
690633 let _ = self . register_with_ty ( local, projection, ty) ;
691634 }
692-
693- if max_derefs > 0 {
694- if let Some ( ty:: TypeAndMut { ty : deref_ty, .. } ) = ty. builtin_deref ( false ) {
695- // Values behind references can only be tracked if the target is `Freeze`.
696- if deref_ty. is_freeze ( tcx, param_env) {
697- projection. push ( PlaceElem :: Deref ) ;
698- self . register_with_filter_rec (
699- tcx,
700- max_derefs - 1 ,
701- local,
702- projection,
703- deref_ty,
704- filter,
705- param_env,
706- exclude,
707- ) ;
708- projection. pop ( ) ;
709- }
710- }
711- }
712635 iter_fields ( ty, tcx, |variant, field, ty| {
713636 if variant. is_some ( ) {
714637 // Downcasts are currently not supported.
715638 return ;
716639 }
717640 projection. push ( PlaceElem :: Field ( field, ty) ) ;
718641 self . register_with_filter_rec (
719- tcx, max_derefs , local, projection, ty, filter, param_env , exclude,
642+ tcx, local, projection, ty, filter, exclude,
720643 ) ;
721644 projection. pop ( ) ;
722645 } ) ;
@@ -875,7 +798,7 @@ impl<'a> Iterator for Children<'a> {
875798 }
876799}
877800
878- /// Used as the result of an operand.
801+ /// Used as the result of an operand or r-value .
879802pub enum ValueOrPlace < V > {
880803 Value ( V ) ,
881804 Place ( PlaceIndex ) ,
@@ -887,34 +810,11 @@ impl<V: HasTop> ValueOrPlace<V> {
887810 }
888811}
889812
890- /// Used as the result of an r-value.
891- pub enum ValueOrPlaceOrRef < V > {
892- Value ( V ) ,
893- Place ( PlaceIndex ) ,
894- Ref ( PlaceIndex ) ,
895- }
896-
897- impl < V : HasTop > ValueOrPlaceOrRef < V > {
898- pub fn top ( ) -> Self {
899- ValueOrPlaceOrRef :: Value ( V :: top ( ) )
900- }
901- }
902-
903- impl < V > From < ValueOrPlace < V > > for ValueOrPlaceOrRef < V > {
904- fn from ( x : ValueOrPlace < V > ) -> Self {
905- match x {
906- ValueOrPlace :: Value ( value) => ValueOrPlaceOrRef :: Value ( value) ,
907- ValueOrPlace :: Place ( place) => ValueOrPlaceOrRef :: Place ( place) ,
908- }
909- }
910- }
911-
912813/// The set of projection elements that can be used by a tracked place.
913814///
914- /// For now, downcast is not allowed due to aliasing between variants (see #101168) .
815+ /// Although only field projections are currently allowed, this could change in the future .
915816#[ derive( Copy , Clone , Debug , PartialEq , Eq , Hash ) ]
916817pub enum TrackElem {
917- Deref ,
918818 Field ( Field ) ,
919819}
920820
@@ -923,7 +823,6 @@ impl<V, T> TryFrom<ProjectionElem<V, T>> for TrackElem {
923823
924824 fn try_from ( value : ProjectionElem < V , T > ) -> Result < Self , Self :: Error > {
925825 match value {
926- ProjectionElem :: Deref => Ok ( TrackElem :: Deref ) ,
927826 ProjectionElem :: Field ( field, _) => Ok ( TrackElem :: Field ( field) ) ,
928827 _ => Err ( ( ) ) ,
929828 }
@@ -962,15 +861,19 @@ fn iter_fields<'tcx>(
962861
963862/// Returns all places, that have their reference or address taken.
964863///
965- /// This includes shared references.
864+ /// This includes shared references, and also drops and `InlineAsm` out parameters .
966865fn escaped_places < ' tcx > ( body : & Body < ' tcx > ) -> FxHashSet < Place < ' tcx > > {
967866 struct Collector < ' tcx > {
968867 result : FxHashSet < Place < ' tcx > > ,
969868 }
970869
971870 impl < ' tcx > Visitor < ' tcx > for Collector < ' tcx > {
972871 fn visit_place ( & mut self , place : & Place < ' tcx > , context : PlaceContext , _location : Location ) {
973- if context. is_borrow ( ) || context. is_address_of ( ) {
872+ if context. is_borrow ( )
873+ || context. is_address_of ( )
874+ || context. is_drop ( )
875+ || context == PlaceContext :: MutatingUse ( MutatingUseContext :: AsmOutput )
876+ {
974877 self . result . insert ( * place) ;
975878 }
976879 }
@@ -1032,7 +935,6 @@ fn debug_with_context_rec<V: Debug + Eq>(
1032935 for child in map. children ( place) {
1033936 let info_elem = map. places [ child] . proj_elem . unwrap ( ) ;
1034937 let child_place_str = match info_elem {
1035- TrackElem :: Deref => format ! ( "*{}" , place_str) ,
1036938 TrackElem :: Field ( field) => {
1037939 if place_str. starts_with ( "*" ) {
1038940 format ! ( "({}).{}" , place_str, field. index( ) )
0 commit comments