forge_lint/sol/gas/
costly_loop.rs1use super::CostlyLoop;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint, analysis::write_target},
5};
6use solar::{
7 ast::DataLocation,
8 interface::data_structures::Never,
9 sema::{
10 Gcx, Hir,
11 builtins::Builtin,
12 hir::{self, Expr, ExprKind, Function, Stmt, StmtKind, Visit as _},
13 },
14};
15use std::ops::ControlFlow;
16
17declare_forge_lint!(COSTLY_LOOP, Severity::Gas, "costly-loop", "storage write inside a loop");
18
19impl<'gcx> LateLintPass<'gcx> for CostlyLoop {
20 fn check_function(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, func: &'gcx Function<'gcx>) {
21 let mut finder = LoopWriteFinder { ctx, gcx, loop_depth: 0 };
22 let _ = finder.visit_function(func);
23 }
24}
25
26struct LoopWriteFinder<'a, 'gcx> {
27 ctx: &'a LintContext<'a, 'a>,
28 gcx: Gcx<'gcx>,
29 loop_depth: u32,
30}
31
32impl<'gcx> hir::Visit<'gcx> for LoopWriteFinder<'_, 'gcx> {
33 type BreakValue = Never;
34
35 fn hir(&self) -> &'gcx Hir<'gcx> {
36 &self.gcx.hir
37 }
38
39 fn visit_stmt(&mut self, stmt: &'gcx Stmt<'gcx>) -> ControlFlow<Self::BreakValue> {
40 let is_loop = matches!(stmt.kind, StmtKind::Loop(..));
41 self.loop_depth += is_loop as u32;
42 let flow = self.walk_stmt(stmt);
43 self.loop_depth -= is_loop as u32;
44 flow
45 }
46
47 fn visit_expr(&mut self, expr: &'gcx Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
48 if self.loop_depth > 0
49 && write_target(expr).is_some_and(|lvalue| lvalue_is_state_var(self.gcx, lvalue))
50 {
51 self.ctx.emit(&COSTLY_LOOP, expr.span);
52 }
53 self.walk_expr(expr)
54 }
55}
56
57fn lvalue_is_state_var(gcx: Gcx<'_>, expr: &Expr<'_>) -> bool {
62 let expr = expr.peel_parens();
63 match &expr.kind {
64 ExprKind::Ident(_) => {
65 gcx.resolved_variable(expr).is_some_and(|id| gcx.hir.variable(id).is_state_variable())
66 }
67 ExprKind::Call(callee, ..) => {
68 gcx.resolved_builtin(callee) == Some(Builtin::ArrayPush0)
69 || gcx
70 .type_of_expr(expr.id)
71 .is_some_and(|ty| ty.loc() == Some(DataLocation::Storage))
72 }
73 ExprKind::Index(base, _)
74 | ExprKind::Slice(base, _, _)
75 | ExprKind::Member(base, _)
76 | ExprKind::Payable(base) => lvalue_is_state_var(gcx, base),
77 ExprKind::Tuple(exprs) => exprs.iter().flatten().any(|e| lvalue_is_state_var(gcx, e)),
78 _ => false,
79 }
80}