Skip to content

Commit e42f644

Browse files
committed
merge lint-6v2 into lint-6v1, creating single lint
1 parent d6bdf42 commit e42f644

File tree

3 files changed

+295
-289
lines changed

3 files changed

+295
-289
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use std::collections::HashMap;
2+
3+
use rustc_hir::{
4+
def_id::DefId,
5+
intravisit::{walk_expr, Visitor},
6+
BinOpKind, Body, Expr, ExprKind, Mutability,
7+
};
8+
use rustc_lint::LateContext;
9+
use rustc_middle::ty::TyKind as MiddleTyKind;
10+
11+
use crate::ANCHOR_ACCOUNT_GENERIC_ARG_COUNT;
12+
use clippy_utils::{ty::match_type, SpanlessEq};
13+
use if_chain::if_chain;
14+
use solana_lints::paths;
15+
16+
pub struct Values<'cx, 'tcx> {
17+
cx: &'cx LateContext<'tcx>,
18+
pub accounts: HashMap<DefId, Vec<&'tcx Expr<'tcx>>>,
19+
pub if_statements: Vec<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
20+
}
21+
22+
impl<'cx, 'tcx> Values<'cx, 'tcx> {
23+
pub fn new(cx: &'cx LateContext<'tcx>) -> Self {
24+
Values {
25+
cx,
26+
accounts: HashMap::new(),
27+
if_statements: Vec::new(),
28+
}
29+
}
30+
31+
pub fn get_referenced_accounts_and_if_statements(&mut self, body: &'tcx Body<'tcx>) -> &Self {
32+
self.visit_expr(&body.value);
33+
self
34+
}
35+
36+
/// Checks if there is a valid key constraint for `first_account` and `second_account`.
37+
/// NOTE: currently only considers `first.key() == second.key()` or the symmetric relation as valid constraints.
38+
/// TODO: if == relation used, should return some error in the THEN block
39+
pub fn check_key_constraint(
40+
&self,
41+
first_account: &Expr<'_>,
42+
second_account: &Expr<'_>,
43+
) -> bool {
44+
for (left, right) in &self.if_statements {
45+
if_chain! {
46+
if let ExprKind::MethodCall(path_seg_left, exprs_left, _span) = left.kind;
47+
if let ExprKind::MethodCall(path_seg_right, exprs_right, _span) = right.kind;
48+
if path_seg_left.ident.name.as_str() == "key" && path_seg_right.ident.name.as_str() == "key";
49+
if !exprs_left.is_empty() && !exprs_right.is_empty();
50+
let mut spanless_eq = SpanlessEq::new(self.cx);
51+
if (spanless_eq.eq_expr(&exprs_left[0], first_account) && spanless_eq.eq_expr(&exprs_right[0], second_account))
52+
|| (spanless_eq.eq_expr(&exprs_left[0], second_account) && spanless_eq.eq_expr(&exprs_right[0], first_account));
53+
then {
54+
return true;
55+
}
56+
}
57+
}
58+
false
59+
}
60+
}
61+
62+
impl<'cx, 'tcx> Visitor<'tcx> for Values<'cx, 'tcx> {
63+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
64+
if_chain! {
65+
// get mutable reference expressions
66+
if let ExprKind::AddrOf(_, mutability, mut_expr) = expr.kind;
67+
if let Mutability::Mut = mutability;
68+
// check type of expr == Account<'info, T>
69+
let middle_ty = self.cx.typeck_results().expr_ty(mut_expr);
70+
// let mut_expr_def_id = self.cx.tcx.hir().local_def_id(mut_expr.hir_id).to_def_id();
71+
// let middle_ty = self.cx.tcx.type_of(mut_expr_def_id);
72+
if match_type(self.cx, middle_ty, &paths::ANCHOR_ACCOUNT);
73+
// grab T generic parameter
74+
if let MiddleTyKind::Adt(_adt_def, substs) = middle_ty.kind();
75+
if substs.len() == ANCHOR_ACCOUNT_GENERIC_ARG_COUNT;
76+
let account_type = substs[1].expect_ty();
77+
if let Some(adt_def) = account_type.ty_adt_def();
78+
then {
79+
let def_id = adt_def.did();
80+
if let Some(exprs) = self.accounts.get_mut(&def_id) {
81+
let mut spanless_eq = SpanlessEq::new(self.cx);
82+
// check that expr is not a duplicate within its particular key-pair
83+
if exprs.iter().all(|e| !spanless_eq.eq_expr(e, mut_expr)) {
84+
exprs.push(mut_expr);
85+
}
86+
} else {
87+
self.accounts.insert(def_id, vec![mut_expr]);
88+
}
89+
}
90+
}
91+
92+
// get if statements
93+
if_chain! {
94+
if let ExprKind::If(wrapped_if_expr, _then, _else_opt) = expr.kind;
95+
if let ExprKind::DropTemps(if_expr) = wrapped_if_expr.kind;
96+
if let ExprKind::Binary(op, left, right) = if_expr.kind;
97+
// TODO: leaves out || or &&. Could implement something that pulls apart
98+
// an if expr that is of this form into individual == or != comparisons
99+
if let BinOpKind::Ne | BinOpKind::Eq = op.node;
100+
then {
101+
// println!("{:#?}, {:#?}", expr, then);
102+
self.if_statements.push((left, right));
103+
}
104+
}
105+
walk_expr(self, expr);
106+
}
107+
}
+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use std::default::Default;
2+
3+
use rustc_ast::{
4+
token::{Delimiter, Token, TokenKind},
5+
tokenstream::{DelimSpan, TokenStream, TokenTree, TreeAndSpacing},
6+
};
7+
use rustc_hir::{def::Res, FieldDef, GenericArg, QPath, TyKind};
8+
use rustc_span::{
9+
def_id::DefId,
10+
symbol::{Ident, Symbol},
11+
DUMMY_SP,
12+
};
13+
14+
use crate::ANCHOR_ACCOUNT_GENERIC_ARG_COUNT;
15+
use if_chain::if_chain;
16+
17+
/// Returns the `DefId` of the anchor account type, ie, `T` in `Account<'info, T>`.
18+
/// Returns `None` if the type of `field` is not an anchor account.
19+
pub fn get_anchor_account_type_def_id(field: &FieldDef) -> Option<DefId> {
20+
if_chain! {
21+
if let TyKind::Path(qpath) = &field.ty.kind;
22+
if let QPath::Resolved(_, path) = qpath;
23+
if !path.segments.is_empty();
24+
if let Some(generic_args) = path.segments[0].args;
25+
if generic_args.args.len() == ANCHOR_ACCOUNT_GENERIC_ARG_COUNT;
26+
if let GenericArg::Type(hir_ty) = &generic_args.args[1];
27+
then {
28+
get_def_id(hir_ty)
29+
} else {
30+
None
31+
}
32+
}
33+
}
34+
35+
/// Returns the `DefId` of `ty`, an hir type. Returns `None` if cannot resolve type.
36+
pub fn get_def_id(ty: &rustc_hir::Ty) -> Option<DefId> {
37+
if_chain! {
38+
if let TyKind::Path(qpath) = &ty.kind;
39+
if let QPath::Resolved(_, path) = qpath;
40+
if let Res::Def(_, def_id) = path.res;
41+
then {
42+
Some(def_id)
43+
} else {
44+
None
45+
}
46+
}
47+
}
48+
49+
/// Returns a `TokenStream` of form: `a`.key() != `b`.key().
50+
pub fn create_key_check_constraint_tokenstream(a: Symbol, b: Symbol) -> TokenStream {
51+
// TODO: may be more efficient way to do this, since the stream is effectively fixed
52+
// and determined. Only two tokens are variable.
53+
let constraint = vec![
54+
TreeAndSpacing::from(create_token_from_ident(a.as_str())),
55+
TreeAndSpacing::from(TokenTree::Token(Token::new(TokenKind::Dot, DUMMY_SP))),
56+
TreeAndSpacing::from(create_token_from_ident("key")),
57+
TreeAndSpacing::from(TokenTree::Delimited(
58+
DelimSpan::dummy(),
59+
Delimiter::Parenthesis,
60+
TokenStream::new(vec![]),
61+
)),
62+
TreeAndSpacing::from(TokenTree::Token(Token::new(TokenKind::Ne, DUMMY_SP))),
63+
TreeAndSpacing::from(create_token_from_ident(b.as_str())),
64+
TreeAndSpacing::from(TokenTree::Token(Token::new(TokenKind::Dot, DUMMY_SP))),
65+
TreeAndSpacing::from(create_token_from_ident("key")),
66+
TreeAndSpacing::from(TokenTree::Delimited(
67+
DelimSpan::dummy(),
68+
Delimiter::Parenthesis,
69+
TokenStream::new(vec![]),
70+
)),
71+
];
72+
73+
TokenStream::new(constraint)
74+
}
75+
76+
/// Returns a `TokenTree::Token` which has `TokenKind::Ident`, with the string set to `s`.
77+
fn create_token_from_ident(s: &str) -> TokenTree {
78+
let ident = Ident::from_str(s);
79+
TokenTree::Token(Token::from_ast_ident(ident))
80+
}
81+
82+
#[derive(Debug, Default)]
83+
pub struct Streams(pub Vec<TokenStream>);
84+
85+
impl Streams {
86+
/// Returns true if `self` has a TokenStream that `other` is a substream of
87+
pub fn contains(&self, other: &TokenStream) -> bool {
88+
self.0
89+
.iter()
90+
.any(|token_stream| Self::is_substream(token_stream, other))
91+
}
92+
93+
/// Returns true if `other` is a substream of `stream`. By substream we mean in the
94+
/// sense of a substring.
95+
// NOTE: a possible optimization is when a match is found, to remove the matched
96+
// TokenTrees from the TokenStream, since the constraint has been "checked" so it never
97+
// needs to be validated again. This cuts down the number of comparisons.
98+
fn is_substream(stream: &TokenStream, other: &TokenStream) -> bool {
99+
let other_len = other.len();
100+
for i in 0..stream.len() {
101+
for (j, other_token) in other.trees().enumerate() {
102+
match stream.trees().nth(i + j) {
103+
Some(token_tree) => {
104+
if !token_tree.eq_unspanned(other_token) {
105+
break;
106+
}
107+
// reached last index, so we have a match
108+
if j == other_len - 1 {
109+
return true;
110+
}
111+
}
112+
None => return false, // reached end of stream
113+
}
114+
}
115+
}
116+
false
117+
}
118+
}

0 commit comments

Comments
 (0)