Skip to content

Commit abd0f3c

Browse files
committed
drafting of upgrade
1 parent 7068bdf commit abd0f3c

File tree

8 files changed

+1480
-44
lines changed

8 files changed

+1480
-44
lines changed

lints/duplicate-mutable-accounts/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ crate-type = ["cdylib"]
1313
name = "insecure"
1414
path = "ui/insecure/src/lib.rs"
1515

16+
[[example]]
17+
name = "insecure-2"
18+
path = "ui/insecure-2/src/lib.rs"
19+
1620
[[example]]
1721
name = "secure"
1822
path = "ui/secure/src/lib.rs"
@@ -21,6 +25,8 @@ path = "ui/secure/src/lib.rs"
2125
clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "7b2896a8fc9f0b275692677ee6d2d66a7cbde16a" }
2226
dylint_linting = "2.0.1"
2327
if_chain = "1.0.2"
28+
proc-macro2 = "1.0.40"
29+
quote = "1.0.20"
2430
solana-lints = { path = "../../crate" }
2531

2632
[dev-dependencies]

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

Lines changed: 83 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ 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+
812
use rustc_ast::{
913
token::TokenKind,
1014
tokenstream::{TokenStream, TokenTree},
@@ -14,7 +18,7 @@ use rustc_hir::def::Res;
1418
use rustc_hir::*;
1519
use rustc_lint::{LateContext, LateLintPass};
1620
use rustc_span::{def_id::DefId, symbol::Symbol, Span};
17-
use std::collections::HashMap;
21+
use std::collections::{HashMap, HashSet, VecDeque};
1822
use std::default::Default;
1923

2024
use clippy_utils::{diagnostics::span_lint_and_help, ty::match_type};
@@ -46,7 +50,7 @@ dylint_linting::impl_late_lint! {
4650
#[derive(Default, Debug)]
4751
struct DuplicateMutableAccounts {
4852
accounts: HashMap<DefId, Vec<(Symbol, Span)>>,
49-
streams: Vec<Stream>,
53+
streams: Streams,
5054
}
5155

5256
impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
@@ -81,13 +85,16 @@ impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
8185
// println!("{:#?}", self.accounts);
8286
if_chain! {
8387
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
8589
// for some reason #[account] doesn't match when no args, maybe take away
8690
// the code to check name, and just check it has constraint args?
8791
if name.as_str() == "account";
8892
if let MacArgs::Delimited(_, _, token_stream) = &attr_item.args;
8993
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());
9198
// println!("{:#?}", attribute);
9299
}
93100
}
@@ -96,19 +103,22 @@ impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
96103
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
97104
// println!("{:#?}", self);
98105
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
102106
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+
}
112122
}
113123
}
114124
}
@@ -117,6 +127,7 @@ impl<'tcx> LateLintPass<'tcx> for DuplicateMutableAccounts {
117127

118128
fn get_anchor_account_type(segment: &PathSegment<'_>) -> Option<DefId> {
119129
if_chain! {
130+
// TODO: the following logic to get def_id is a repeated pattern
120131
if let Some(generic_args) = segment.args;
121132
if let GenericArg::Type(ty) = &generic_args.args[1]; // the account type is the second generic arg
122133
if let TyKind::Path(qpath) = &ty.kind;
@@ -130,34 +141,38 @@ fn get_anchor_account_type(segment: &PathSegment<'_>) -> Option<DefId> {
130141
}
131142
}
132143

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));
142165
}
143166
}
144-
return false;
167+
gen_set
145168
}
146169

147-
#[derive(Debug)]
148-
pub struct Stream(TokenStream);
170+
#[derive(Debug, Default)]
171+
pub struct Streams(Vec<TokenStream>);
149172

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)
161176
}
162177
}
163178

@@ -166,7 +181,36 @@ fn insecure() {
166181
dylint_testing::ui_test_example(env!("CARGO_PKG_NAME"), "insecure");
167182
}
168183

184+
#[test]
185+
fn insecure_2() {
186+
dylint_testing::ui_test_example(env!("CARGO_PKG_NAME"), "insecure-2");
187+
}
188+
169189
#[test]
170190
fn secure() {
171191
dylint_testing::ui_test_example(env!("CARGO_PKG_NAME"), "secure");
172192
}
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

Comments
 (0)