forge_lint/sol/low/
calls_loop.rs1use super::{
2 CallsLoop,
3 payable_loop::{LoopItem, for_each_loop_item},
4};
5use crate::{
6 linter::{LateLintPass, LintContext},
7 sol::{Severity, SolLint},
8};
9use solar::{
10 ast::StateMutability,
11 sema::{
12 Gcx,
13 builtins::Builtin,
14 hir::{Expr, ExprKind, Function},
15 ty::{TyFnKind, TyKind},
16 },
17};
18
19declare_forge_lint!(CALLS_LOOP, Severity::Low, "calls-loop", "external call inside a loop");
20
21impl<'gcx> LateLintPass<'gcx> for CallsLoop {
22 fn check_function(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, func: &'gcx Function<'gcx>) {
23 for_each_loop_item(gcx, func, false, |item| {
24 if let LoopItem::Expr(expr) = item
25 && let ExprKind::Call(callee, ..) = &expr.kind
26 && is_external_call(gcx, callee)
27 {
28 ctx.emit(&CALLS_LOOP, expr.span);
29 }
30 });
31 }
32}
33
34enum ExternalCall {
36 Opaque,
38 Static,
40 Member(StateMutability),
42}
43
44fn classify<'gcx>(gcx: Gcx<'gcx>, callee: &Expr<'gcx>) -> Option<ExternalCall> {
47 let callee = callee.peel_parens();
48 if matches!(
49 gcx.resolved_builtin(callee),
50 Some(Builtin::AddressPayableSend | Builtin::AddressPayableTransfer)
51 ) {
52 return Some(ExternalCall::Opaque);
53 }
54 let TyKind::Fn(function) = gcx.type_of_expr(callee.id)?.kind else { return None };
55 match function.kind() {
56 TyFnKind::External | TyFnKind::DelegateCall => {
57 Some(ExternalCall::Member(function.state_mutability))
58 }
59 TyFnKind::BareStaticCall => Some(ExternalCall::Static),
60 TyFnKind::BareCall | TyFnKind::BareDelegateCall | TyFnKind::Creation => {
61 Some(ExternalCall::Opaque)
62 }
63 _ => None,
64 }
65}
66
67pub(super) fn is_external_call<'gcx>(gcx: Gcx<'gcx>, callee: &Expr<'gcx>) -> bool {
69 classify(gcx, callee).is_some()
70}
71
72pub(super) fn is_state_mutating_external_call<'gcx>(gcx: Gcx<'gcx>, callee: &Expr<'gcx>) -> bool {
75 match classify(gcx, callee) {
76 Some(ExternalCall::Opaque) => true,
77 Some(ExternalCall::Member(mutability)) => {
78 !matches!(mutability, StateMutability::View | StateMutability::Pure)
79 }
80 Some(ExternalCall::Static) | None => false,
81 }
82}