@@ -5,6 +5,10 @@ extern crate rustc_ast;
5
5
extern crate rustc_hir;
6
6
extern crate rustc_span;
7
7
8
+ use proc_macro2:: * ;
9
+ use quote:: quote;
10
+ use std:: str:: FromStr ;
11
+
8
12
use rustc_ast:: {
9
13
token:: TokenKind ,
10
14
tokenstream:: { TokenStream , TokenTree } ,
@@ -14,7 +18,7 @@ use rustc_hir::def::Res;
14
18
use rustc_hir:: * ;
15
19
use rustc_lint:: { LateContext , LateLintPass } ;
16
20
use rustc_span:: { def_id:: DefId , symbol:: Symbol , Span } ;
17
- use std:: collections:: HashMap ;
21
+ use std:: collections:: { HashMap , HashSet , VecDeque } ;
18
22
use std:: default:: Default ;
19
23
20
24
use clippy_utils:: { diagnostics:: span_lint_and_help, ty:: match_type} ;
@@ -46,7 +50,7 @@ dylint_linting::impl_late_lint! {
46
50
#[ derive( Default , Debug ) ]
47
51
struct DuplicateMutableAccounts {
48
52
accounts : HashMap < DefId , Vec < ( Symbol , Span ) > > ,
49
- streams : Vec < Stream > ,
53
+ streams : Streams ,
50
54
}
51
55
52
56
impl < ' tcx > LateLintPass < ' tcx > for DuplicateMutableAccounts {
@@ -81,13 +85,16 @@ impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
81
85
// println!("{:#?}", self.accounts);
82
86
if_chain ! {
83
87
if let AttrKind :: Normal ( attr_item, _) = & attribute. kind;
84
- let name = attr_item. path. segments[ 0 ] . ident. name;
88
+ let name = attr_item. path. segments[ 0 ] . ident. name; // TODO: can use name_or_empty
85
89
// for some reason #[account] doesn't match when no args, maybe take away
86
90
// the code to check name, and just check it has constraint args?
87
91
if name. as_str( ) == "account" ;
88
92
if let MacArgs :: Delimited ( _, _, token_stream) = & attr_item. args;
89
93
then {
90
- self . streams. push( Stream ( token_stream. clone( ) ) ) ;
94
+ // TODO: figure out stream representation. At this point, may parse?
95
+ // TODO: filter mechanism: only insert constraints that match form "constraint = _.key() != _.key()"
96
+ // TODO: may need to parse each constraint as a separate stream, as comma-delimited
97
+ self . streams. 0 . push( token_stream. clone( ) ) ;
91
98
// println!("{:#?}", attribute);
92
99
}
93
100
}
@@ -96,19 +103,22 @@ impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
96
103
fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
97
104
// println!("{:#?}", self);
98
105
for ( _k, v) in self . accounts . iter ( ) {
99
- // if multiple same accounts, check that there is a tokenstream such that
100
- // all necessary expressions are in it: x.key(), y.key(), !=
101
- // where x and y are the field name from self.accounts
102
106
if v. len ( ) > 1 {
103
- if !has_satisfying_stream ( & self . streams , v) {
104
- span_lint_and_help (
105
- cx,
106
- DUPLICATE_MUTABLE_ACCOUNTS ,
107
- v[ 0 ] . 1 ,
108
- "identical account types" ,
109
- Some ( v[ 1 ] . 1 ) ,
110
- & format ! ( "add an anchor key check constraint: #[account(constraint = {}.key() != {}.key())]" , v[ 0 ] . 0 , v[ 1 ] . 0 )
111
- ) ;
107
+ // generate static set of possible constraints
108
+ let gen_constraints = generate_possible_expected_constraints ( v) ;
109
+
110
+ // assert the following checks:
111
+ for ( one, reflexive) in gen_constraints {
112
+ if !( self . streams . contains ( one) || self . streams . contains ( reflexive) ) {
113
+ // span_lint_and_help(
114
+ // cx,
115
+ // DUPLICATE_MUTABLE_ACCOUNTS,
116
+ // v[0].1,
117
+ // "identical account types",
118
+ // Some(v[1].1),
119
+ // &format!("add an anchor key check constraint: #[account(constraint = {}.key() != {}.key())]", v[0].0, v[1].0)
120
+ // );
121
+ }
112
122
}
113
123
}
114
124
}
@@ -117,6 +127,7 @@ impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
117
127
118
128
fn get_anchor_account_type ( segment : & PathSegment < ' _ > ) -> Option < DefId > {
119
129
if_chain ! {
130
+ // TODO: the following logic to get def_id is a repeated pattern
120
131
if let Some ( generic_args) = segment. args;
121
132
if let GenericArg :: Type ( ty) = & generic_args. args[ 1 ] ; // the account type is the second generic arg
122
133
if let TyKind :: Path ( qpath) = & ty. kind;
@@ -130,34 +141,38 @@ fn get_anchor_account_type(segment: &PathSegment<'_>) -> Option<DefId> {
130
141
}
131
142
}
132
143
133
- fn has_satisfying_stream ( streams : & Vec < Stream > , field_names : & Vec < ( Symbol , Span ) > ) -> bool {
134
- for stream in streams {
135
- if stream. contains ( TokenKind :: Ne )
136
- && field_names
137
- . iter ( )
138
- // TODO: if true, will not match. figure out what the bool signifies
139
- . all ( |( sym, _) | stream. contains ( TokenKind :: Ident ( * sym, false ) ) )
140
- {
141
- return true ;
144
+ /// Generates a static set of a possible expected key check constraints necessary for `values`.
145
+ fn generate_possible_expected_constraints ( values : & Vec < ( Symbol , Span ) > ) -> Vec < ( proc_macro2:: TokenStream , proc_macro2:: TokenStream ) > {
146
+ // TODO: may start with a VecDeque in the first place?
147
+ let mut deq = VecDeque :: from ( values. clone ( ) ) ;
148
+ let mut gen_set = Vec :: new ( ) ;
149
+
150
+ for _ in 0 ..deq. len ( ) - 1 {
151
+ let first = deq. pop_front ( ) . unwrap ( ) . 0 ;
152
+ // generate stream for all other values in vec
153
+ for ( other, _) in & deq {
154
+ let constraint = format ! ( "constraint = {}.key() != {}.key()" , first. as_str( ) , other. as_str( ) ) ;
155
+ let reflexive = format ! ( "constraint = {}.key() != {}.key()" , other. as_str( ) , first. as_str( ) ) ;
156
+
157
+ // using quote
158
+ // let stream = quote!(constraint = first.as_str().key() != other.as_str().key());
159
+
160
+ let stream: proc_macro2:: TokenStream = constraint. parse ( ) . unwrap ( ) ;
161
+ let reflex_stream: proc_macro2:: TokenStream = reflexive. parse ( ) . unwrap ( ) ;
162
+ // println!("{:#?}", stream);
163
+
164
+ gen_set. push ( ( stream, reflex_stream) ) ;
142
165
}
143
166
}
144
- return false ;
167
+ gen_set
145
168
}
146
169
147
- #[ derive( Debug ) ]
148
- pub struct Stream ( TokenStream ) ;
170
+ #[ derive( Debug , Default ) ]
171
+ pub struct Streams ( Vec < TokenStream > ) ;
149
172
150
- impl Stream {
151
- fn contains ( & self , x : TokenKind ) -> bool {
152
- for token_tree in self . 0 . trees ( ) {
153
- if let TokenTree :: Token ( t) = token_tree {
154
- if t. kind == x {
155
- // println!("The type {:?} matches {:?}", t.kind, x);
156
- return true ;
157
- }
158
- }
159
- }
160
- return false ;
173
+ impl Streams {
174
+ fn contains ( & self , other : TokenStream ) -> bool {
175
+ self . 0 . iter ( ) . any ( |stream| stream == & other)
161
176
}
162
177
}
163
178
@@ -166,7 +181,36 @@ fn insecure() {
166
181
dylint_testing:: ui_test_example ( env ! ( "CARGO_PKG_NAME" ) , "insecure" ) ;
167
182
}
168
183
184
+ #[ test]
185
+ fn insecure_2 ( ) {
186
+ dylint_testing:: ui_test_example ( env ! ( "CARGO_PKG_NAME" ) , "insecure-2" ) ;
187
+ }
188
+
169
189
#[ test]
170
190
fn secure ( ) {
171
191
dylint_testing:: ui_test_example ( env ! ( "CARGO_PKG_NAME" ) , "secure" ) ;
172
192
}
193
+
194
+ // fn has_satisfying_stream(streams: &Vec<Stream>, field_names: &Vec<(Symbol, Span)>) -> bool {
195
+ // for stream in streams {
196
+ // if stream.contains(TokenKind::Ne)
197
+ // && field_names
198
+ // .iter()
199
+ // // TODO: if true, will not match. figure out what the bool signifies
200
+ // .all(|(sym, _)| stream.contains(TokenKind::Ident(*sym, false)))
201
+ // {
202
+ // return true;
203
+ // }
204
+ // }
205
+ // return false;
206
+ // }
207
+
208
+ // Generates a TokenStream that matches `constraint = a.key() != b.key()` and its reflexive
209
+ // fn generate_key_check_constraint(a: Symbol, b: Symbol) -> (TokenStream, TokenStream) {
210
+ // let mut tree_and_spacing = vec![];
211
+ // // create token
212
+ // let tree = TokenTree::token(TokenKind::Ident(Symbol::intern("constraint"), false), span); // TODO: generate span somehow
213
+ // tree_and_spacing.push(TreeAndSpacing::from(tree));
214
+
215
+ // TokenStream::new(tree_and_spacing)
216
+ // }
0 commit comments