Skip to main content

forge_lint/sol/low/
require_revert_in_loop.rs

1use super::{
2    RequireRevertInLoop,
3    payable_loop::{LoopItem, for_each_loop_item},
4};
5use crate::{
6    linter::{LateLintPass, LintContext},
7    sol::{Severity, SolLint},
8};
9use solar::sema::{
10    Gcx,
11    builtins::Builtin,
12    hir::{Expr, ExprKind, Function, StmtKind},
13};
14
15declare_forge_lint!(
16    REQUIRE_REVERT_IN_LOOP,
17    Severity::Low,
18    "require-revert-in-loop",
19    "`require` or `revert` inside a loop"
20);
21
22impl<'gcx> LateLintPass<'gcx> for RequireRevertInLoop {
23    fn check_function(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, func: &'gcx Function<'gcx>) {
24        for_each_loop_item(gcx, func, false, |item| {
25            let reported = match item {
26                LoopItem::Stmt(stmt) => match stmt.kind {
27                    StmtKind::Revert(expr) => Some(expr),
28                    _ => None,
29                },
30                LoopItem::Expr(expr) => is_require_or_revert_call(gcx, expr).then_some(expr),
31            };
32            if let Some(expr) = reported {
33                ctx.emit(&REQUIRE_REVERT_IN_LOOP, expr.span);
34            }
35        });
36    }
37}
38
39/// `require(..)`, `revert(..)` or the Yul `revert(..)` builtin.
40fn is_require_or_revert_call(gcx: Gcx<'_>, expr: &Expr<'_>) -> bool {
41    let ExprKind::Call(callee, ..) = &expr.peel_parens().kind else { return false };
42    matches!(
43        gcx.resolved_builtin(callee),
44        Some(Builtin::Require | Builtin::Revert | Builtin::RevertMsg | Builtin::YulRevert)
45    )
46}