@@ -14,7 +14,7 @@ use if_chain::if_chain;
1414use rustc_hir:: {
1515 def_id:: DefId ,
1616 intravisit:: { walk_expr, FnKind , Visitor } ,
17- Body , Expr , ExprKind , FnDecl , HirId , Mutability ,
17+ BinOpKind , Body , Expr , ExprKind , FnDecl , HirId , Mutability ,
1818} ;
1919use rustc_lint:: { LateContext , LateLintPass } ;
2020use rustc_middle:: ty:: TyKind ;
@@ -57,71 +57,80 @@ impl<'tcx> LateLintPass<'tcx> for DupMutableAccounts2 {
5757 _: HirId ,
5858 ) {
5959 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+ }
8779 }
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+ }
10081 }
10182 } ) ;
10283 }
10384 }
10485}
10586
106- struct AccountUses < ' cx , ' tcx > {
87+ struct Values < ' cx , ' tcx > {
10788 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 > ) > ,
10991}
11092
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+ }
122131}
123132
124- impl < ' cx , ' tcx > Visitor < ' tcx > for AccountUses < ' cx , ' tcx > {
133+ impl < ' cx , ' tcx > Visitor < ' tcx > for Values < ' cx , ' tcx > {
125134 fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) {
126135 if_chain ! {
127136 // get mutable reference expressions
@@ -137,44 +146,58 @@ impl<'cx, 'tcx> Visitor<'tcx> for AccountUses<'cx, 'tcx> {
137146 if let Some ( adt_def) = account_type. ty_adt_def( ) ;
138147 then {
139148 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) {
141150 let mut spanless_eq = SpanlessEq :: new( self . cx) ;
142151 // check that expr is not a duplicate within its particular key-pair
143152 if exprs. iter( ) . all( |e| !spanless_eq. eq_expr( e, mut_expr) ) {
144153 exprs. push( mut_expr) ;
145154 }
146155 } else {
147- self . uses . insert( def_id, vec![ mut_expr] ) ;
156+ self . accounts . insert( def_id, vec![ mut_expr] ) ;
148157 }
149158 }
150159 }
151- walk_expr ( self , expr) ;
152- }
153- }
154160
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
163162 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;
169169 then {
170- true
171- } else {
172- false
170+ // println!("{:#?}, {:#?}", expr, then);
171+ self . if_statements. push( ( left, right) ) ;
173172 }
174173 }
175- } )
174+ walk_expr ( self , expr) ;
175+ }
176176}
177177
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+
178201#[ test]
179202fn insecure ( ) {
180203 dylint_testing:: ui_test_example ( env ! ( "CARGO_PKG_NAME" ) , "insecure" ) ;
0 commit comments