@@ -14,7 +14,7 @@ use if_chain::if_chain;
14
14
use rustc_hir:: {
15
15
def_id:: DefId ,
16
16
intravisit:: { walk_expr, FnKind , Visitor } ,
17
- Body , Expr , ExprKind , FnDecl , HirId , Mutability ,
17
+ BinOpKind , Body , Expr , ExprKind , FnDecl , HirId , Mutability ,
18
18
} ;
19
19
use rustc_lint:: { LateContext , LateLintPass } ;
20
20
use rustc_middle:: ty:: TyKind ;
@@ -57,71 +57,80 @@ impl<'tcx> LateLintPass<'tcx> for DupMutableAccounts2 {
57
57
_: HirId ,
58
58
) {
59
59
if !span. from_expansion ( ) {
60
- let accounts = get_referenced_accounts ( cx, body) ;
61
-
62
- accounts. values ( ) . for_each ( |exprs| {
63
- // TODO: figure out handling of >2 accounts
64
- match exprs. len ( ) {
65
- 2 => {
66
- let first = exprs[ 0 ] ;
67
- let second = exprs[ 1 ] ;
68
- if !contains_key_call ( cx, body, first) {
69
- span_lint_and_help (
70
- cx,
71
- DUP_MUTABLE_ACCOUNTS_2 ,
72
- first. span ,
73
- "this expression does not have a key check but has the same account type as another expression" ,
74
- Some ( second. span ) ,
75
- "add a key check to make sure the accounts have different keys, e.g., x.key() != y.key()" ,
76
- ) ;
77
- }
78
- if !contains_key_call ( cx, body, second) {
79
- span_lint_and_help (
80
- cx,
81
- DUP_MUTABLE_ACCOUNTS_2 ,
82
- second. span ,
83
- "this expression does not have a key check but has the same account type as another expression" ,
84
- Some ( first. span ) ,
85
- "add a key check to make sure the accounts have different keys, e.g., x.key() != y.key()" ,
86
- ) ;
60
+ // get all mutable references to Accounts and if_statements in body
61
+ let mut values = Values :: new ( cx) ;
62
+ values. get_referenced_accounts_and_if_statements ( cx, body) ;
63
+ // println!("{:#?}", values.if_statements);
64
+
65
+ values. accounts . values ( ) . for_each ( |exprs| {
66
+ if exprs. len ( ) > 1 {
67
+ for current in 0 ..exprs. len ( ) - 1 {
68
+ for next in current + 1 ..exprs. len ( ) {
69
+ if !values. check_key_constraint ( exprs[ current] , exprs[ next] ) {
70
+ span_lint_and_help (
71
+ cx,
72
+ DUP_MUTABLE_ACCOUNTS_2 ,
73
+ exprs[ current] . span ,
74
+ "the following expressions have equivalent Account types, yet do not contain a proper key check." ,
75
+ Some ( exprs[ next] . span ) ,
76
+ "add a key check to make sure the accounts have different keys, e.g., x.key() != y.key()" ,
77
+ ) ;
78
+ }
87
79
}
88
- } ,
89
- n if n > 2 => {
90
- span_lint_and_note (
91
- cx,
92
- DUP_MUTABLE_ACCOUNTS_2 ,
93
- exprs[ 0 ] . span ,
94
- & format ! ( "the following expression has the same account type as {} other accounts" , exprs. len( ) ) ,
95
- None ,
96
- "might not check that each account has a unique key"
97
- )
98
- } ,
99
- _ => { }
80
+ }
100
81
}
101
82
} ) ;
102
83
}
103
84
}
104
85
}
105
86
106
- struct AccountUses < ' cx , ' tcx > {
87
+ struct Values < ' cx , ' tcx > {
107
88
cx : & ' cx LateContext < ' tcx > ,
108
- uses : HashMap < DefId , Vec < & ' tcx Expr < ' tcx > > > ,
89
+ accounts : HashMap < DefId , Vec < & ' tcx Expr < ' tcx > > > ,
90
+ if_statements : Vec < ( & ' tcx Expr < ' tcx > , & ' tcx Expr < ' tcx > ) > ,
109
91
}
110
92
111
- fn get_referenced_accounts < ' tcx > (
112
- cx : & LateContext < ' tcx > ,
113
- body : & ' tcx Body < ' tcx > ,
114
- ) -> HashMap < DefId , Vec < & ' tcx Expr < ' tcx > > > {
115
- let mut accounts = AccountUses {
116
- cx,
117
- uses : HashMap :: new ( ) ,
118
- } ;
119
-
120
- accounts. visit_expr ( & body. value ) ;
121
- accounts. uses
93
+ impl < ' cx , ' tcx > Values < ' cx , ' tcx > {
94
+ fn new ( cx : & ' cx LateContext < ' tcx > ) -> Self {
95
+ Values {
96
+ cx,
97
+ accounts : HashMap :: new ( ) ,
98
+ if_statements : Vec :: new ( ) ,
99
+ }
100
+ }
101
+
102
+ fn get_referenced_accounts_and_if_statements (
103
+ & mut self ,
104
+ cx : & ' cx LateContext < ' tcx > ,
105
+ body : & ' tcx Body < ' tcx > ,
106
+ ) -> & Self {
107
+ self . visit_expr ( & body. value ) ;
108
+ self
109
+ }
110
+
111
+ /// Checks if there is a valid key constraint for `first_account` and `second_account`.
112
+ /// NOTE: currently only considers `first.key() == second.key()` or the symmetric relation as valid constraints.
113
+ /// TODO: if == relation used, should return some error in the THEN block
114
+ fn check_key_constraint ( & self , first_account : & Expr < ' _ > , second_account : & Expr < ' _ > ) -> bool {
115
+ for ( left, right) in & self . if_statements {
116
+ if_chain ! {
117
+ if let ExprKind :: MethodCall ( path_seg_left, exprs_left, _span) = left. kind;
118
+ if let ExprKind :: MethodCall ( path_seg_right, exprs_right, _span) = right. kind;
119
+ if path_seg_left. ident. name. as_str( ) == "key" && path_seg_right. ident. name. as_str( ) == "key" ;
120
+ if !exprs_left. is_empty( ) && !exprs_right. is_empty( ) ;
121
+ let mut spanless_eq = SpanlessEq :: new( self . cx) ;
122
+ if ( spanless_eq. eq_expr( & exprs_left[ 0 ] , first_account) && spanless_eq. eq_expr( & exprs_right[ 0 ] , second_account) )
123
+ || ( spanless_eq. eq_expr( & exprs_left[ 0 ] , second_account) && spanless_eq. eq_expr( & exprs_right[ 0 ] , first_account) ) ;
124
+ then {
125
+ return true ;
126
+ }
127
+ }
128
+ }
129
+ return false ;
130
+ }
122
131
}
123
132
124
- impl < ' cx , ' tcx > Visitor < ' tcx > for AccountUses < ' cx , ' tcx > {
133
+ impl < ' cx , ' tcx > Visitor < ' tcx > for Values < ' cx , ' tcx > {
125
134
fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) {
126
135
if_chain ! {
127
136
// get mutable reference expressions
@@ -137,44 +146,58 @@ impl<'cx, 'tcx> Visitor<'tcx> for AccountUses<'cx, 'tcx> {
137
146
if let Some ( adt_def) = account_type. ty_adt_def( ) ;
138
147
then {
139
148
let def_id = adt_def. did( ) ;
140
- if let Some ( exprs) = self . uses . get_mut( & def_id) {
149
+ if let Some ( exprs) = self . accounts . get_mut( & def_id) {
141
150
let mut spanless_eq = SpanlessEq :: new( self . cx) ;
142
151
// check that expr is not a duplicate within its particular key-pair
143
152
if exprs. iter( ) . all( |e| !spanless_eq. eq_expr( e, mut_expr) ) {
144
153
exprs. push( mut_expr) ;
145
154
}
146
155
} else {
147
- self . uses . insert( def_id, vec![ mut_expr] ) ;
156
+ self . accounts . insert( def_id, vec![ mut_expr] ) ;
148
157
}
149
158
}
150
159
}
151
- walk_expr ( self , expr) ;
152
- }
153
- }
154
160
155
- /// Performs a walk on `body`, checking whether there exists an expression that contains
156
- /// a `key()` method call on `account_expr`.
157
- fn contains_key_call < ' tcx > (
158
- cx : & LateContext < ' tcx > ,
159
- body : & ' tcx Body < ' tcx > ,
160
- account_expr : & Expr < ' tcx > ,
161
- ) -> bool {
162
- visit_expr_no_bodies ( & body. value , |expr| {
161
+ // get if statements
163
162
if_chain ! {
164
- if let ExprKind :: MethodCall ( path_seg, exprs, _span) = expr. kind;
165
- if path_seg. ident. name. as_str( ) == "key" ;
166
- if !exprs. is_empty( ) ;
167
- let mut spanless_eq = SpanlessEq :: new( cx) ;
168
- if spanless_eq. eq_expr( & exprs[ 0 ] , account_expr) ;
163
+ if let ExprKind :: If ( wrapped_if_expr, then, _else_opt) = expr. kind;
164
+ if let ExprKind :: DropTemps ( if_expr) = wrapped_if_expr. kind;
165
+ if let ExprKind :: Binary ( op, left, right) = if_expr. kind;
166
+ // TODO: leaves out || or &&. Could implement something that pulls apart
167
+ // an if expr that is of this form into individual == or != comparisons
168
+ if let BinOpKind :: Ne | BinOpKind :: Eq = op. node;
169
169
then {
170
- true
171
- } else {
172
- false
170
+ // println!("{:#?}, {:#?}", expr, then);
171
+ self . if_statements. push( ( left, right) ) ;
173
172
}
174
173
}
175
- } )
174
+ walk_expr ( self , expr) ;
175
+ }
176
176
}
177
177
178
+ // /// Performs a walk on `body`, checking whether there exists an expression that contains
179
+ // /// a `key()` method call on `account_expr`.
180
+ // fn contains_key_call<'tcx>(
181
+ // cx: &LateContext<'tcx>,
182
+ // body: &'tcx Body<'tcx>,
183
+ // account_expr: &Expr<'tcx>,
184
+ // ) -> bool {
185
+ // visit_expr_no_bodies(&body.value, |expr| {
186
+ // if_chain! {
187
+ // if let ExprKind::MethodCall(path_seg, exprs, _span) = expr.kind;
188
+ // if path_seg.ident.name.as_str() == "key";
189
+ // if !exprs.is_empty();
190
+ // let mut spanless_eq = SpanlessEq::new(cx);
191
+ // if spanless_eq.eq_expr(&exprs[0], account_expr);
192
+ // then {
193
+ // true
194
+ // } else {
195
+ // false
196
+ // }
197
+ // }
198
+ // })
199
+ // }
200
+
178
201
#[ test]
179
202
fn insecure ( ) {
180
203
dylint_testing:: ui_test_example ( env ! ( "CARGO_PKG_NAME" ) , "insecure" ) ;
0 commit comments