Skip to content

Commit d6bdf42

Browse files
committed
implement lint6v2 to better handle more than 2 identical accounts
1 parent b713470 commit d6bdf42

File tree

2 files changed

+104
-76
lines changed

2 files changed

+104
-76
lines changed

lints/dup_mutable_accounts_2/src/lib.rs

Lines changed: 99 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use if_chain::if_chain;
1414
use 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
};
1919
use rustc_lint::{LateContext, LateLintPass};
2020
use 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]
179202
fn insecure() {
180203
dylint_testing::ui_test_example(env!("CARGO_PKG_NAME"), "insecure");

lints/dup_mutable_accounts_2/ui/secure/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ pub mod duplicate_mutable_accounts_secure {
1414
if ctx.accounts.user_a.key() == ctx.accounts.user_b.key() {
1515
return Err(ProgramError::InvalidArgument);
1616
}
17+
18+
// if ctx.accounts.user_a.key() != ctx.accounts.user_b.key() {
19+
// // do program stuff
20+
// }
21+
1722
let user_a = &mut ctx.accounts.user_a;
1823
let user_b = &mut ctx.accounts.user_b;
1924

0 commit comments

Comments
 (0)