1
1
//! This pass statically detects code which has undefined behaviour or is likely to be erroneous.
2
2
//! It can be used to locate problems in MIR building or optimizations. It assumes that all code
3
3
//! can be executed, so it has false positives.
4
+ use rustc_data_structures:: fx:: FxHashSet ;
4
5
use rustc_index:: bit_set:: BitSet ;
5
6
use rustc_middle:: mir:: visit:: { PlaceContext , Visitor } ;
6
7
use rustc_middle:: mir:: * ;
@@ -11,7 +12,6 @@ use rustc_mir_dataflow::{Analysis, ResultsCursor};
11
12
use std:: borrow:: Cow ;
12
13
13
14
pub fn lint_body < ' tcx > ( tcx : TyCtxt < ' tcx > , body : & Body < ' tcx > , when : String ) {
14
- let reachable_blocks = traversal:: reachable_as_bitset ( body) ;
15
15
let always_live_locals = & always_storage_live_locals ( body) ;
16
16
17
17
let maybe_storage_live = MaybeStorageLive :: new ( Cow :: Borrowed ( always_live_locals) )
@@ -24,17 +24,19 @@ pub fn lint_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, when: String) {
24
24
. iterate_to_fixpoint ( )
25
25
. into_results_cursor ( body) ;
26
26
27
- Lint {
27
+ let mut lint = Lint {
28
28
tcx,
29
29
when,
30
30
body,
31
31
is_fn_like : tcx. def_kind ( body. source . def_id ( ) ) . is_fn_like ( ) ,
32
32
always_live_locals,
33
- reachable_blocks,
34
33
maybe_storage_live,
35
34
maybe_storage_dead,
35
+ places : Default :: default ( ) ,
36
+ } ;
37
+ for ( bb, data) in traversal:: reachable ( body) {
38
+ lint. visit_basic_block_data ( bb, data) ;
36
39
}
37
- . visit_body ( body) ;
38
40
}
39
41
40
42
struct Lint < ' a , ' tcx > {
@@ -43,9 +45,9 @@ struct Lint<'a, 'tcx> {
43
45
body : & ' a Body < ' tcx > ,
44
46
is_fn_like : bool ,
45
47
always_live_locals : & ' a BitSet < Local > ,
46
- reachable_blocks : BitSet < BasicBlock > ,
47
48
maybe_storage_live : ResultsCursor < ' a , ' tcx , MaybeStorageLive < ' a > > ,
48
49
maybe_storage_dead : ResultsCursor < ' a , ' tcx , MaybeStorageDead < ' a > > ,
50
+ places : FxHashSet < PlaceRef < ' tcx > > ,
49
51
}
50
52
51
53
impl < ' a , ' tcx > Lint < ' a , ' tcx > {
@@ -67,7 +69,7 @@ impl<'a, 'tcx> Lint<'a, 'tcx> {
67
69
68
70
impl < ' a , ' tcx > Visitor < ' tcx > for Lint < ' a , ' tcx > {
69
71
fn visit_local ( & mut self , local : Local , context : PlaceContext , location : Location ) {
70
- if self . reachable_blocks . contains ( location . block ) && context. is_use ( ) {
72
+ if context. is_use ( ) {
71
73
self . maybe_storage_dead . seek_after_primary_effect ( location) ;
72
74
if self . maybe_storage_dead . get ( ) . contains ( local) {
73
75
self . fail ( location, format ! ( "use of local {local:?}, which has no storage here" ) ) ;
@@ -76,28 +78,38 @@ impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> {
76
78
}
77
79
78
80
fn visit_statement ( & mut self , statement : & Statement < ' tcx > , location : Location ) {
79
- match statement. kind {
80
- StatementKind :: StorageLive ( local) => {
81
- if self . reachable_blocks . contains ( location. block ) {
82
- self . maybe_storage_live . seek_before_primary_effect ( location) ;
83
- if self . maybe_storage_live . get ( ) . contains ( local) {
81
+ match & statement. kind {
82
+ StatementKind :: Assign ( box ( dest, rvalue) ) => {
83
+ if let Rvalue :: Use ( Operand :: Copy ( src) | Operand :: Move ( src) ) = rvalue {
84
+ // The sides of an assignment must not alias. Currently this just checks whether
85
+ // the places are identical.
86
+ if dest == src {
84
87
self . fail (
85
88
location,
86
- format ! ( "StorageLive({local:?}) which already has storage here" ) ,
89
+ "encountered `Assign` statement with overlapping memory" ,
87
90
) ;
88
91
}
89
92
}
90
93
}
94
+ StatementKind :: StorageLive ( local) => {
95
+ self . maybe_storage_live . seek_before_primary_effect ( location) ;
96
+ if self . maybe_storage_live . get ( ) . contains ( * local) {
97
+ self . fail (
98
+ location,
99
+ format ! ( "StorageLive({local:?}) which already has storage here" ) ,
100
+ ) ;
101
+ }
102
+ }
91
103
_ => { }
92
104
}
93
105
94
106
self . super_statement ( statement, location) ;
95
107
}
96
108
97
109
fn visit_terminator ( & mut self , terminator : & Terminator < ' tcx > , location : Location ) {
98
- match terminator. kind {
110
+ match & terminator. kind {
99
111
TerminatorKind :: Return => {
100
- if self . is_fn_like && self . reachable_blocks . contains ( location . block ) {
112
+ if self . is_fn_like {
101
113
self . maybe_storage_live . seek_after_primary_effect ( location) ;
102
114
for local in self . maybe_storage_live . get ( ) . iter ( ) {
103
115
if !self . always_live_locals . contains ( local) {
@@ -111,6 +123,28 @@ impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> {
111
123
}
112
124
}
113
125
}
126
+ TerminatorKind :: Call { args, destination, .. } => {
127
+ // The call destination place and Operand::Move place used as an argument might be
128
+ // passed by a reference to the callee. Consequently they must be non-overlapping.
129
+ // Currently this simply checks for duplicate places.
130
+ self . places . clear ( ) ;
131
+ self . places . insert ( destination. as_ref ( ) ) ;
132
+ let mut has_duplicates = false ;
133
+ for arg in args {
134
+ if let Operand :: Move ( place) = arg {
135
+ has_duplicates |= !self . places . insert ( place. as_ref ( ) ) ;
136
+ }
137
+ }
138
+ if has_duplicates {
139
+ self . fail (
140
+ location,
141
+ format ! (
142
+ "encountered overlapping memory in `Move` arguments to `Call` terminator: {:?}" ,
143
+ terminator. kind,
144
+ ) ,
145
+ ) ;
146
+ }
147
+ }
114
148
_ => { }
115
149
}
116
150
0 commit comments