Skip to main content

forge_lint/sol/med/
incorrect_strict_equality.rs

1use super::IncorrectStrictEquality;
2use crate::{
3    linter::{LateLintPass, LintContext},
4    sol::{Severity, SolLint, analysis::referenced_item},
5};
6use solar::{
7    ast::BinOpKind,
8    sema::{
9        Gcx,
10        builtins::Builtin,
11        hir::{Expr, ExprKind, ItemId},
12    },
13};
14use std::ops::ControlFlow;
15
16declare_forge_lint!(
17    INCORRECT_STRICT_EQUALITY,
18    Severity::Med,
19    "incorrect-strict-equality",
20    "dangerous strict equality check on an externally-influenced value"
21);
22
23impl<'gcx> LateLintPass<'gcx> for IncorrectStrictEquality {
24    fn check_expr(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, expr: &'gcx Expr<'gcx>) {
25        if let ExprKind::Binary(lhs, op, rhs) = &expr.kind
26            && matches!(op.kind, BinOpKind::Eq | BinOpKind::Ne)
27            && [lhs, rhs].into_iter().any(|side| {
28                side.visit(&mut |e| {
29                    if is_externally_influenced(gcx, e) {
30                        ControlFlow::Break(())
31                    } else {
32                        ControlFlow::Continue(())
33                    }
34                })
35                .is_break()
36            })
37        {
38            ctx.emit(&INCORRECT_STRICT_EQUALITY, expr.span);
39        }
40    }
41}
42
43/// `<address>.balance` or `<non-library>.balanceOf(...)`.
44///
45/// `.balance` is only flagged when the receiver is provably an address, so that struct fields named
46/// `balance` do not trigger it. `balanceOf` is matched by name (it is overwhelmingly an ERC-20
47/// method), skipping static library calls to avoid internal helpers of the same name.
48fn is_externally_influenced<'gcx>(gcx: Gcx<'gcx>, expr: &Expr<'gcx>) -> bool {
49    match &expr.peel_parens().kind {
50        ExprKind::Member(..) => gcx.resolved_builtin(expr) == Some(Builtin::AddressBalance),
51        ExprKind::Call(callee, ..) => {
52            matches!(&callee.peel_parens().kind, ExprKind::Member(base, m)
53                if m.as_str() == "balanceOf"
54                    && !matches!(referenced_item(gcx, base), Some(ItemId::Contract(cid))
55                        if gcx.hir.contract(cid).kind.is_library()))
56        }
57        _ => false,
58    }
59}