Skip to content

Commit bc4a212

Browse files
committed
complete working impl of lint-6 for anchor constraint checks
1 parent abd0f3c commit bc4a212

File tree

5 files changed

+126
-64
lines changed

5 files changed

+126
-64
lines changed

lints/duplicate-mutable-accounts/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ name = "secure"
2222
path = "ui/secure/src/lib.rs"
2323

2424
[dependencies]
25-
clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "7b2896a8fc9f0b275692677ee6d2d66a7cbde16a" }
25+
clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "0cb0f7636851f9fcc57085cf80197a2ef6db098f" }
2626
dylint_linting = "2.0.1"
2727
if_chain = "1.0.2"
2828
proc-macro2 = "1.0.40"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[toolchain]
2-
channel = "nightly-2022-02-24"
2+
channel = "nightly-2022-06-30"
33
components = ["llvm-tools-preview", "rustc-dev"]

lints/duplicate-mutable-accounts/src/lib.rs

Lines changed: 118 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,24 @@ extern crate rustc_ast;
55
extern crate rustc_hir;
66
extern crate rustc_span;
77

8-
use proc_macro2::*;
9-
use quote::quote;
10-
use std::str::FromStr;
11-
128
use rustc_ast::{
13-
token::TokenKind,
14-
tokenstream::{TokenStream, TokenTree},
9+
token::{Delimiter, Token, TokenKind},
10+
tokenstream::{CursorRef, DelimSpan, TokenStream, TokenTree, TreeAndSpacing},
1511
AttrKind, Attribute, MacArgs,
1612
};
1713
use rustc_hir::def::Res;
1814
use rustc_hir::*;
1915
use rustc_lint::{LateContext, LateLintPass};
20-
use rustc_span::{def_id::DefId, symbol::Symbol, Span};
21-
use std::collections::{HashMap, HashSet, VecDeque};
16+
use rustc_span::{
17+
def_id::DefId,
18+
symbol::{Ident, Symbol},
19+
Span, DUMMY_SP,
20+
};
21+
use std::collections::{HashMap, VecDeque};
2222
use std::default::Default;
2323

24+
25+
2426
use clippy_utils::{diagnostics::span_lint_and_help, ty::match_type};
2527
use if_chain::if_chain;
2628
use solana_lints::paths;
@@ -54,6 +56,27 @@ struct DuplicateMutableAccounts {
5456
}
5557

5658
impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
59+
// fn check_mod(
60+
// &mut self,
61+
// cx: &LateContext<'tcx>,
62+
// _: &'tcx Mod<'tcx>,
63+
// span: Span,
64+
// _: HirId
65+
// ) {
66+
// println!("new");
67+
// for _ in 0..3 {
68+
// println!("linting");
69+
// span_lint_and_help(
70+
// cx,
71+
// DUPLICATE_MUTABLE_ACCOUNTS,
72+
// span,
73+
// "dummy",
74+
// None,
75+
// ""
76+
// );
77+
// }
78+
// }
79+
5780
fn check_struct_def(&mut self, cx: &LateContext<'tcx>, variant_data: &'tcx VariantData<'tcx>) {
5881
if let VariantData::Struct(fields, _) = variant_data {
5982
fields.iter().for_each(|field| {
@@ -91,11 +114,11 @@ impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
91114
if name.as_str() == "account";
92115
if let MacArgs::Delimited(_, _, token_stream) = &attr_item.args;
93116
then {
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());
98-
// println!("{:#?}", attribute);
117+
for split in split(token_stream.trees(), TokenKind::Comma) {
118+
// println!("{:#?}", split);
119+
self.streams.0.push(split);
120+
}
121+
99122
}
100123
}
101124
}
@@ -107,17 +130,29 @@ impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
107130
// generate static set of possible constraints
108131
let gen_constraints = generate_possible_expected_constraints(v);
109132

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-
// );
133+
for ((one, symmetric), symbols) in gen_constraints {
134+
// println!("{:#?}\n {:#?}", one, symmetric);
135+
if !(self.streams.contains(one) || self.streams.contains(symmetric)) {
136+
println!("lint for {} {}", symbols.0, symbols.1);
137+
138+
// stupid way to get spans for offending types
139+
let mut spans: Vec<Span> = Vec::new();
140+
for (sym, span) in v {
141+
if &symbols.0 == sym || &symbols.1 == sym {
142+
spans.push(span.clone());
143+
}
144+
}
145+
146+
// TODO: for some reason, will only print out 2 messages, not 3
147+
// println!("{:?}", spans);
148+
span_lint_and_help(
149+
cx,
150+
DUPLICATE_MUTABLE_ACCOUNTS,
151+
spans[0],
152+
"identical account types without a key check constraint",
153+
Some(spans[1]),
154+
&format!("add an anchor key check constraint: #[account(constraint = {}.key() != {}.key())]", symbols.0, symbols.1)
155+
);
121156
}
122157
}
123158
}
@@ -141,8 +176,29 @@ fn get_anchor_account_type(segment: &PathSegment<'_>) -> Option<DefId> {
141176
}
142177
}
143178

179+
// collect elements into a TokenStream until encounter delim then stop collecting. create a new vec.
180+
// continue until reaching end of stream
181+
fn split(stream: CursorRef, delimiter: TokenKind) -> Vec<TokenStream> {
182+
let mut split_streams: Vec<TokenStream> = Vec::new();
183+
let mut temp: Vec<TreeAndSpacing> = Vec::new();
184+
let delim = TokenTree::Token(Token::new(delimiter, DUMMY_SP));
185+
186+
stream.for_each(|t| {
187+
if t.eq_unspanned(&delim) {
188+
split_streams.push(TokenStream::new(temp.clone()));
189+
temp.clear();
190+
} else {
191+
temp.push(TreeAndSpacing::from(t.to_owned()));
192+
}
193+
});
194+
split_streams.push(TokenStream::new(temp));
195+
split_streams
196+
}
197+
144198
/// 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)> {
199+
fn generate_possible_expected_constraints(
200+
values: &Vec<(Symbol, Span)>,
201+
) -> Vec<((TokenStream, TokenStream), (Symbol, Symbol))> {
146202
// TODO: may start with a VecDeque in the first place?
147203
let mut deq = VecDeque::from(values.clone());
148204
let mut gen_set = Vec::new();
@@ -151,28 +207,55 @@ fn generate_possible_expected_constraints(values: &Vec<(Symbol, Span)>) -> Vec<(
151207
let first = deq.pop_front().unwrap().0;
152208
// generate stream for all other values in vec
153209
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();
210+
let stream = create_key_check_constraint_tokenstream(&first, other);
211+
let symmetric_stream = create_key_check_constraint_tokenstream(other, &first);
162212
// println!("{:#?}", stream);
163213

164-
gen_set.push((stream, reflex_stream));
214+
gen_set.push(((stream, symmetric_stream), (first, other.clone())));
165215
}
166216
}
167217
gen_set
168218
}
169219

220+
// TODO: figure out more efficient way to do this
221+
fn create_key_check_constraint_tokenstream(a: &Symbol, b: &Symbol) -> TokenStream {
222+
let constraint = vec![
223+
// TODO: test string matching by changing some string
224+
TreeAndSpacing::from(create_token("constraint")),
225+
TreeAndSpacing::from(TokenTree::Token(Token::new(TokenKind::Eq, DUMMY_SP))),
226+
TreeAndSpacing::from(create_token(a.as_str())),
227+
TreeAndSpacing::from(TokenTree::Token(Token::new(TokenKind::Dot, DUMMY_SP))),
228+
TreeAndSpacing::from(create_token("key")),
229+
TreeAndSpacing::from(TokenTree::Delimited(
230+
DelimSpan::dummy(),
231+
Delimiter::Parenthesis,
232+
TokenStream::new(vec![]),
233+
)),
234+
TreeAndSpacing::from(TokenTree::Token(Token::new(TokenKind::Ne, DUMMY_SP))),
235+
TreeAndSpacing::from(create_token(b.as_str())),
236+
TreeAndSpacing::from(TokenTree::Token(Token::new(TokenKind::Dot, DUMMY_SP))),
237+
TreeAndSpacing::from(create_token("key")),
238+
TreeAndSpacing::from(TokenTree::Delimited(
239+
DelimSpan::dummy(),
240+
Delimiter::Parenthesis,
241+
TokenStream::new(vec![]),
242+
)),
243+
];
244+
245+
TokenStream::new(constraint)
246+
}
247+
248+
fn create_token(s: &str) -> TokenTree {
249+
let ident = Ident::from_str(s);
250+
TokenTree::Token(Token::from_ast_ident(ident))
251+
}
252+
170253
#[derive(Debug, Default)]
171254
pub struct Streams(Vec<TokenStream>);
172255

173256
impl Streams {
174257
fn contains(&self, other: TokenStream) -> bool {
175-
self.0.iter().any(|stream| stream == &other)
258+
self.0.iter().any(|stream| stream.eq_unspanned(&other))
176259
}
177260
}
178261

@@ -190,27 +273,3 @@ fn insecure_2() {
190273
fn secure() {
191274
dylint_testing::ui_test_example(env!("CARGO_PKG_NAME"), "secure");
192275
}
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-
// }

lints/duplicate-mutable-accounts/ui/insecure-2/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ pub mod duplicate_mutable_accounts_insecure {
2222

2323
#[derive(Accounts)]
2424
pub struct Update<'info> {
25-
#[account(constraint = user_a.key() != user_b.key())]
25+
#[account(constraint = user_a.key() != user_b.key(), constraint = user_b.key() != user_c.key())]
2626
user_a: Account<'info, User>,
27-
#[account(constraint = user_c.key() != user_b.key())]
2827
user_b: Account<'info, User>,
2928
user_c: Account<'info, User>,
3029
}

lints/duplicate-mutable-accounts/ui/secure/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
66
pub mod duplicate_mutable_accounts_secure {
77
use super::*;
88

9-
pub fn update_secure(ctx: Context<Update>, a: u64, b: u64) -> anchor_lang::solana_program::entrypoint::ProgramResult {
9+
pub fn update_secure(
10+
ctx: Context<Update>,
11+
a: u64,
12+
b: u64,
13+
) -> anchor_lang::solana_program::entrypoint::ProgramResult {
1014
let user_a = &mut ctx.accounts.user_a;
1115
let user_b = &mut ctx.accounts.user_b;
1216

0 commit comments

Comments
 (0)