forge_lint/sol/low/
require_revert_in_loop.rs1use super::{RequireRevertInLoop, payable_loop::visit_loop_statements_and_expressions};
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use solar::sema::{
7 Gcx, Hir,
8 builtins::Builtin,
9 hir::{Expr, ExprKind, Function, Res, StmtKind},
10};
11use std::{cell::RefCell, collections::HashSet};
12
13declare_forge_lint!(
14 REQUIRE_REVERT_IN_LOOP,
15 Severity::Low,
16 "require-revert-in-loop",
17 "`require` or `revert` inside a loop"
18);
19
20impl<'hir> LateLintPass<'hir> for RequireRevertInLoop {
21 fn check_function(
22 &mut self,
23 ctx: &LintContext,
24 gcx: Gcx<'hir>,
25 hir: &'hir Hir<'hir>,
26 func: &'hir Function<'hir>,
27 ) {
28 let emitted = RefCell::new(HashSet::new());
29
30 visit_loop_statements_and_expressions(
31 ctx,
32 gcx,
33 hir,
34 func,
35 |ctx, _, _, stmt| {
36 if let StmtKind::Revert(expr) = stmt.kind {
37 let mut emitted = emitted.borrow_mut();
38 emit_once(ctx, &mut emitted, expr);
39 }
40 },
41 |ctx, _, _, expr| {
42 if is_require_or_revert_call(expr) {
43 let mut emitted = emitted.borrow_mut();
44 emit_once(ctx, &mut emitted, expr);
45 }
46 },
47 );
48 }
49}
50
51fn emit_once(ctx: &LintContext, emitted: &mut HashSet<solar::interface::Span>, expr: &Expr<'_>) {
52 if emitted.insert(expr.span) {
53 ctx.emit(&REQUIRE_REVERT_IN_LOOP, expr.span);
54 }
55}
56
57fn is_require_or_revert_call(expr: &Expr<'_>) -> bool {
58 let ExprKind::Call(callee, _, _) = &expr.peel_parens().kind else { return false };
59 let ExprKind::Ident(reses) = &callee.peel_parens().kind else { return false };
60
61 reses.iter().any(|res| {
62 matches!(
63 res,
64 Res::Builtin(
65 Builtin::Require | Builtin::Revert | Builtin::RevertMsg | Builtin::YulRevert
66 )
67 )
68 })
69}