Skip to main content

forge_lint/sol/low/
delegatecall_loop.rs

1use super::{
2    DelegatecallLoop,
3    payable_loop::{expr_ty, is_address_ty, is_this_or_super, visit_payable_loop_expressions},
4};
5use crate::{
6    linter::{LateLintPass, LintContext},
7    sol::{Severity, SolLint},
8};
9use solar::{
10    interface::kw,
11    sema::{
12        Gcx,
13        hir::{Expr, ExprKind, Function, Hir},
14    },
15};
16use std::collections::HashSet;
17
18declare_forge_lint!(
19    DELEGATECALL_LOOP,
20    Severity::Low,
21    "delegatecall-loop",
22    "payable functions should not use `delegatecall` inside a loop"
23);
24
25impl<'hir> LateLintPass<'hir> for DelegatecallLoop {
26    fn check_function(
27        &mut self,
28        ctx: &LintContext,
29        gcx: Gcx<'hir>,
30        hir: &'hir Hir<'hir>,
31        func: &'hir Function<'hir>,
32    ) {
33        let mut emitted = HashSet::new();
34        visit_payable_loop_expressions(ctx, gcx, hir, func, |ctx, gcx, hir, expr| {
35            if is_delegatecall(gcx, hir, expr) && emitted.insert(expr.span) {
36                ctx.emit(&DELEGATECALL_LOOP, expr.span);
37            }
38        });
39    }
40}
41
42fn is_delegatecall<'hir>(gcx: Gcx<'hir>, hir: &'hir Hir<'hir>, expr: &'hir Expr<'hir>) -> bool {
43    let ExprKind::Call(call_expr, _, _) = &expr.kind else {
44        return false;
45    };
46    let ExprKind::Member(receiver, member) = &call_expr.peel_parens().kind else {
47        return false;
48    };
49    if member.name != kw::Delegatecall {
50        return false;
51    }
52    if is_this_or_super(receiver) {
53        return false;
54    }
55
56    expr_ty(gcx, hir, receiver).is_some_and(is_address_ty)
57}