)
Foundryforge lint规则解析require-revert-in-loop循环内 require/revert 检测与修复【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundryrequire-revert-in-loop是 Foundry 内置forge lint检查器中的一条 Low 级别规则用于静态检测 Solidity 与 Yul 循环体内出现的require调用和revert语句。本文以该规则的官方文档crates/lint/docs/require-revert-in-loop.md为核心结合其在 crates/lint 中的 Rust 实现、共享遍历器与完整测试用例讲清该规则的触发条件、底层实现原理、修复方式以及在foundry.toml中的配置与抑制方法帮助读者写出对批量操作更健壮的合约代码。规则速览严重级别SeverityLow规则 IDrequire-revert-in-loop检测目标循环体含循环条件、更新表达式与 modifier 展开后中出现的require(...)调用、revert(...)语句以及 Yul 内联汇编中的revert(0, 0)等内建指令源码实现crates/lint/src/sol/low/require_revert_in_loop.rs该规则以declare_forge_lint!宏声明静态元数据并在 crates/lint/src/sol/low/mod.rs 中作为late pass注册require_revert_in_loop: (RequireRevertInLoop, late, (REQUIRE_REVERT_IN_LOOP))意味着它在 Solar 编译器完成语义分析、生成 HIR 之后运行可以依赖类型解析结果进行精确判断。规则检测什么What it does官方文档的界定非常明确该规则报告循环内部的require调用与 Solidity/Yul 的revert操作。关键点在于它不只是检查循环体字面上的语句还包含通过modifier间接到达的require/revertmodifier 中的循环展开_占位符后函数体被放入循环中执行从循环内调用的内部辅助函数internal helper中被执行的require/revert即使require语句本身写在被调用的辅助函数里位于循环条件与循环更新表达式中的requireYulassembly { revert(0, 0) }形式的内联汇编回退。从源码看require_revert_in_loop.rs 通过is_require_or_revert_call函数识别被解析为内建函数的调用matches!( gcx.resolved_builtin(callee), Some(Builtin::Require | Builtin::Revert | Builtin::RevertMsg | Builtin::YulRevert) )即它精确匹配四类内置构造require(..)、revert(..)含自定义错误revert BadItem(i)与字符串消息revert(zero)、revert()消息形式以及 Yul 内建的revert。而直接作为语句出现的StmtKind::Revert(expr)如revert BadItem(index);则由LoopItem::Stmt分支捕获require_revert_in_loop.rs。为什么限制这种写法Why restrict this文档给出的核心理由是失败原子性问题单个无效元素即可导致整个循环回退当一个元素校验失败时批量batched操作可能因此变得不可用。典型场景是批量转账、批量铸造或批量状态更新如果循环内对每个元素做require(values[i] ! 0, zero)之类的校验那么数组中任意一个非法元素都会让整批操作整体 revert前面已处理的元素全部回滚gas 与用户成本被放大用户体验也变差。文档同时给出边界判断建议原子性批量Atomic batches——要求每一笔都必须成功此时有意保留 revert 是合理的可在代码评审确认后抑制suppress该 lint可部分处理的批量——当跳过非法元素属于 API 预期行为时应改用条件分支跳过而不是整体 revert。也就是说该规则并不是断言“循环里用 require/revert 一定是 bug”而是提醒开发者显式思考批量操作的失败语义属于可评审后豁免的策略性提示这也是它被定为 Low 级别的原因。触发示例与推荐修复文档给出了可直接编译触发的示例。触发写法contract Batch { function process(uint256[] calldata values) external { for (uint256 i; i values.length; i) { require(values[i] ! 0, zero); } } }当批量允许部分处理时推荐改为跳过非法元素contract Batch { function process(uint256[] calldata values) external { for (uint256 i; i values.length; i) { if (values[i] 0) continue; // Process valid entries. } } }二者的行为差异值得强调continue版本只处理合法元素非法元素被静默跳过如果调用方需要感知“哪些元素被跳过”应在跳过分支中记录索引或通过返回值/事件暴露避免吞掉错误信息。源码级实现剖析如何穿透 modifier 与内部辅助函数这是该规则最有技术含量的部分。它依赖 payable_loop.rs 中共享的循环上下文遍历器for_each_loop_item。该遍历器同样服务于payable-loop、calls-loop、msg-value-loop等兄弟规则核心机制如下循环深度追踪LoopWalker维护loop_depth遇到StmtKind::Loop时深度加一离开时减一只有loop_depth 0时产生的语句与表达式才会被回调payable_loop.rs。modifier 链展开遍历visit_modifiers时对每个 modifier 递归进入其函数体并在遇到_占位符StmtKind::Placeholder时恢复“剩余 modifier 函数体”这一 continuation从而把函数体真正放到 modifier 的循环中执行payable_loop.rs。内部调用内联当循环内出现ExprKind::Call且被调函数是内部函数或 delegatecall 时callee会解析目标函数 ID含using for绑定、库限定调用与super虚分派然后递归visit_call进入被调函数体继续遍历payable_loop.rs。注释明确说明对合约类型值含this的调用被视为外部调用不会跟进——因为外部调用无法静态内联这也解释了测试中this.externalValidate(...)不被标记的原因。递归防护stack: VecFunctionId记录正在内联的 modifier/helper避免循环调用导致无限递归payable_loop.rs。因此即便require写在内部辅助函数里、或经过using ... for扩展函数、或位于 modifier 循环包裹的函数体只要其执行路径落入某个循环都会被准确捕获并上报。测试用例验证覆盖全部触发路径仓库提供了覆盖度极高的测试夹具 crates/lint/testdata/RequireRevertInLoop.sol文件头以//compile-flags: --only-lint require-revert-in-loop指定只运行本规则其中每个应触发的位置都带//~WARN:注解对应的 RequireRevertInLoop.stderr 则是期望的 13 条诊断输出。测试覆盖了场景说明循环内直接requirefor循环体内直接调用循环内revert自定义错误while循环中revert BadItem(i)循环内revert(zero)字符串内置 revert 消息形式内部辅助函数中的require循环调用validate(values[i])require在 helper 内内部辅助函数中的revert循环调用validateWithReverthelper 自身带循环validateLoop被循环外调用也会报告其内部循环共享 helper 多调用点sharedValidate被两个循环调用仅报告一次循环条件中的requirewhile (conditionWithRequire(...))循环更新表达式中的requirefor的i incrementWithRequire(i)using for扩展函数values[i].validateExtension()this外部自调用this.externalValidate(...)——不触发重载函数只有被调用的重载版本被标记modifier 占位符循环repeated(iterations)modifier 中_展开函数体require被标记Yul 内联汇编assembly { revert(0, 0) }被标记循环外require/ 无 require 的循环requireOutsideLoop、loopWithoutRequireOrRevert——不触发期望输出 RequireRevertInLoop.stderr 还展示了诊断的标准形态warning[require-revert-in-loop]: require or revert inside a loop并在代码下方用━━━波浪线标出触发表达式范围末尾附带官方帮助页链接https://getfoundry.sh/forge/linting/require-revert-in-loop该链接由declare_forge_lint!宏依据规则 ID 自动拼接见 crates/lint/src/sol/macros.rs。在 foundry.toml 中的配置与抑制forge lint的配置位于foundry.toml的[lint]表对应 crates/config/src/lint.rs 中的LinterConfig[lint] # 按严重级别过滤要运行的规则默认是 [high, med, low] severity [high, med, low] # 按规则 ID 排除特定规则例如排除本规则 exclude_lints [require-revert-in-loop] # 忽略匹配 glob 的文件 ignore [] # 是否在 forge build 时自动运行 lint默认 true lint_on_build true相关要点Severity枚举支持high、med、low、info、gas、codesize六档crates/config/src/lint.rs本规则属于low因此在默认severity [high, med, low]配置下默认启用若项目明确采用原子批量语义、循环内 revert 是刻意设计文档建议的做法是先经代码评审确认行为正确再通过exclude_lints在配置层抑制或在代码注释中说明理由避免后续维护者误改lint_on_build为false时forge build不再自动执行 lint但forge lint命令仍可显式运行配置解析有严格校验[lint]表内的未知键、未知嵌套键、未知子表都会报错见 crates/config/src/lib.rs 的解析测试配置错误会被尽早发现。编写规范与本规则的定位每条forge lint规则都必须在 crates/lint/docs 下提供与规则 ID 同名的 Markdown 文档本规则即require-revert-in-loop.md文档结构需遵循 crates/lint/docs/README.md 定义的顺序约束标题/严重级别/ID →What it does→ 一个理由小节 →Example含Use instead:分隔的 solidity 代码块。风格指南要求对“可合理放行的风格或策略选择”使用Why restrict this?而非Why is this bad?——本规则正是典型例子它提示的是批量操作的失败语义取舍而非确定性的安全漏洞因此采用 Low 级别并在理由中明确区分“原子批量”与“可部分处理”两种立场。小结require-revert-in-loop是一条定位精准、实现严谨的静态检查规则它借助 HIR 层的循环深度追踪与 modifier/内部辅助函数内联把“循环内是否会出现回退路径”这一问题剖析得很彻底同时通过测试夹具覆盖了直接语句、辅助函数、modifier、using for扩展、Yul 汇编等全部触发路径。对合约开发者而言理解并善用该规则可以在编写批量操作时更早、更自觉地决定失败语义——是整体原子回退还是跳过非法项继续处理从而避免批量功能在真实调用中因单个坏元素而整体不可用。【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考