13
13
//! can be registered. The [`State`] can be queried to retrieve the abstract value stored for a
14
14
//! certain place by passing the map.
15
15
//!
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.
21
20
//!
22
21
//!
23
22
//! # Notes
28
27
//! - The assignment logic in `State::assign_place_idx` assumes that the places are non-overlapping,
29
28
//! or identical. Note that this refers to place expressions, not memory locations.
30
29
//!
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.
47
34
48
35
use std:: fmt:: { Debug , Formatter } ;
49
36
50
37
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
51
38
use rustc_index:: vec:: IndexVec ;
52
39
use rustc_middle:: mir:: tcx:: PlaceTy ;
53
- use rustc_middle:: mir:: visit:: { PlaceContext , Visitor } ;
40
+ use rustc_middle:: mir:: visit:: { MutatingUseContext , PlaceContext , Visitor } ;
54
41
use rustc_middle:: mir:: * ;
55
42
use rustc_middle:: ty:: { self , Ty , TyCtxt } ;
56
43
use rustc_target:: abi:: VariantIdx ;
@@ -96,10 +83,7 @@ pub trait ValueAnalysis<'tcx> {
96
83
state. flood_with ( place. as_ref ( ) , self . map ( ) , Self :: Value :: bottom ( ) ) ;
97
84
}
98
85
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.
103
87
}
104
88
StatementKind :: Nop
105
89
| StatementKind :: FakeRead ( ..)
@@ -156,29 +140,23 @@ pub trait ValueAnalysis<'tcx> {
156
140
& self ,
157
141
rvalue : & Rvalue < ' tcx > ,
158
142
state : & mut State < Self :: Value > ,
159
- ) -> ValueOrPlaceOrRef < Self :: Value > {
143
+ ) -> ValueOrPlace < Self :: Value > {
160
144
self . super_rvalue ( rvalue, state)
161
145
}
162
146
163
147
fn super_rvalue (
164
148
& self ,
165
149
rvalue : & Rvalue < ' tcx > ,
166
150
state : & mut State < Self :: Value > ,
167
- ) -> ValueOrPlaceOrRef < Self :: Value > {
151
+ ) -> ValueOrPlace < Self :: Value > {
168
152
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) ,
180
154
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 ( )
182
160
}
183
161
Rvalue :: Repeat ( ..)
184
162
| Rvalue :: ThreadLocalRef ( ..)
@@ -192,7 +170,7 @@ pub trait ValueAnalysis<'tcx> {
192
170
| Rvalue :: Aggregate ( ..)
193
171
| Rvalue :: ShallowInitBox ( ..) => {
194
172
// No modification is possible through these r-values.
195
- ValueOrPlaceOrRef :: top ( )
173
+ ValueOrPlace :: top ( )
196
174
}
197
175
}
198
176
}
@@ -247,14 +225,13 @@ pub trait ValueAnalysis<'tcx> {
247
225
self . super_terminator ( terminator, state)
248
226
}
249
227
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 > ) {
251
229
match & terminator. kind {
252
230
TerminatorKind :: Call { .. } | TerminatorKind :: InlineAsm { .. } => {
253
231
// Effect is applied by `handle_call_return`.
254
232
}
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.
258
235
}
259
236
TerminatorKind :: DropAndReplace { .. } | TerminatorKind :: Yield { .. } => {
260
237
// They would have an effect, but are not allowed in this phase.
@@ -522,17 +499,17 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
522
499
}
523
500
}
524
501
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 ) {
526
503
if let Some ( target) = map. find ( target) {
527
504
self . assign_idx ( target, result, map) ;
528
505
} else {
529
506
// We don't track this place nor any projections, assignment can be ignored.
530
507
}
531
508
}
532
509
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 ) {
534
511
match result {
535
- ValueOrPlaceOrRef :: Value ( value) => {
512
+ ValueOrPlace :: Value ( value) => {
536
513
// First flood the target place in case we also track any projections (although
537
514
// this scenario is currently not well-supported by the API).
538
515
self . flood_idx ( target, map) ;
@@ -541,21 +518,7 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
541
518
values[ value_index] = value;
542
519
}
543
520
}
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) ,
559
522
}
560
523
}
561
524
@@ -625,45 +588,27 @@ impl Map {
625
588
filter : impl FnMut ( Ty < ' tcx > ) -> bool ,
626
589
) -> Self {
627
590
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) ) ;
641
592
debug ! ( "registered {} places ({} nodes in total)" , map. value_count, map. places. len( ) ) ;
642
593
map
643
594
}
644
595
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.
646
597
fn register_with_filter < ' tcx > (
647
598
& mut self ,
648
599
tcx : TyCtxt < ' tcx > ,
649
600
body : & Body < ' tcx > ,
650
- max_derefs : u32 ,
651
601
mut filter : impl FnMut ( Ty < ' tcx > ) -> bool ,
652
602
exclude : & FxHashSet < Place < ' tcx > > ,
653
603
) {
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
-
657
604
let mut projection = Vec :: new ( ) ;
658
605
for ( local, decl) in body. local_decls . iter_enumerated ( ) {
659
606
self . register_with_filter_rec (
660
607
tcx,
661
- max_derefs,
662
608
local,
663
609
& mut projection,
664
610
decl. ty ,
665
611
& mut filter,
666
- param_env,
667
612
exclude,
668
613
) ;
669
614
}
@@ -672,12 +617,10 @@ impl Map {
672
617
fn register_with_filter_rec < ' tcx > (
673
618
& mut self ,
674
619
tcx : TyCtxt < ' tcx > ,
675
- max_derefs : u32 ,
676
620
local : Local ,
677
621
projection : & mut Vec < PlaceElem < ' tcx > > ,
678
622
ty : Ty < ' tcx > ,
679
623
filter : & mut impl FnMut ( Ty < ' tcx > ) -> bool ,
680
- param_env : ty:: ParamEnv < ' tcx > ,
681
624
exclude : & FxHashSet < Place < ' tcx > > ,
682
625
) {
683
626
if exclude. contains ( & Place { local, projection : tcx. intern_place_elems ( projection) } ) {
@@ -689,34 +632,14 @@ impl Map {
689
632
// This might fail if `ty` is not scalar.
690
633
let _ = self . register_with_ty ( local, projection, ty) ;
691
634
}
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
- }
712
635
iter_fields ( ty, tcx, |variant, field, ty| {
713
636
if variant. is_some ( ) {
714
637
// Downcasts are currently not supported.
715
638
return ;
716
639
}
717
640
projection. push ( PlaceElem :: Field ( field, ty) ) ;
718
641
self . register_with_filter_rec (
719
- tcx, max_derefs , local, projection, ty, filter, param_env , exclude,
642
+ tcx, local, projection, ty, filter, exclude,
720
643
) ;
721
644
projection. pop ( ) ;
722
645
} ) ;
@@ -875,7 +798,7 @@ impl<'a> Iterator for Children<'a> {
875
798
}
876
799
}
877
800
878
- /// Used as the result of an operand.
801
+ /// Used as the result of an operand or r-value .
879
802
pub enum ValueOrPlace < V > {
880
803
Value ( V ) ,
881
804
Place ( PlaceIndex ) ,
@@ -887,34 +810,11 @@ impl<V: HasTop> ValueOrPlace<V> {
887
810
}
888
811
}
889
812
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
-
912
813
/// The set of projection elements that can be used by a tracked place.
913
814
///
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 .
915
816
#[ derive( Copy , Clone , Debug , PartialEq , Eq , Hash ) ]
916
817
pub enum TrackElem {
917
- Deref ,
918
818
Field ( Field ) ,
919
819
}
920
820
@@ -923,7 +823,6 @@ impl<V, T> TryFrom<ProjectionElem<V, T>> for TrackElem {
923
823
924
824
fn try_from ( value : ProjectionElem < V , T > ) -> Result < Self , Self :: Error > {
925
825
match value {
926
- ProjectionElem :: Deref => Ok ( TrackElem :: Deref ) ,
927
826
ProjectionElem :: Field ( field, _) => Ok ( TrackElem :: Field ( field) ) ,
928
827
_ => Err ( ( ) ) ,
929
828
}
@@ -962,15 +861,19 @@ fn iter_fields<'tcx>(
962
861
963
862
/// Returns all places, that have their reference or address taken.
964
863
///
965
- /// This includes shared references.
864
+ /// This includes shared references, and also drops and `InlineAsm` out parameters .
966
865
fn escaped_places < ' tcx > ( body : & Body < ' tcx > ) -> FxHashSet < Place < ' tcx > > {
967
866
struct Collector < ' tcx > {
968
867
result : FxHashSet < Place < ' tcx > > ,
969
868
}
970
869
971
870
impl < ' tcx > Visitor < ' tcx > for Collector < ' tcx > {
972
871
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
+ {
974
877
self . result . insert ( * place) ;
975
878
}
976
879
}
@@ -1032,7 +935,6 @@ fn debug_with_context_rec<V: Debug + Eq>(
1032
935
for child in map. children ( place) {
1033
936
let info_elem = map. places [ child] . proj_elem . unwrap ( ) ;
1034
937
let child_place_str = match info_elem {
1035
- TrackElem :: Deref => format ! ( "*{}" , place_str) ,
1036
938
TrackElem :: Field ( field) => {
1037
939
if place_str. starts_with ( "*" ) {
1038
940
format ! ( "({}).{}" , place_str, field. index( ) )
0 commit comments