You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何遍历匹配Alex&Happy生成的Solidity AST以构建代码分析规则引擎?

嘿,你已经搞定了最费劲的部分——用Alex和Happy搭好了Solidity的词法/语法分析器,还生成了AST!接下来要做的规则引擎,本质就是对AST做定向遍历和模式匹配,我来给你捋捋具体怎么搞:

先把Happy生成的AST结构摸透

不管啥规则,第一步得先搞懂你手里的AST每个节点到底对应Solidity里的啥语法元素。Happy生成的AST是Haskell数据类型,建议你先写个小工具把完整AST打印出来(用show函数就行),比如拿一段简单的Solidity代码(比如带函数调用的合约)生成AST,然后逐个节点拆解:

  • 比如FunctionDefinition节点会包含函数名、参数列表、函数体、修饰器这些字段
  • CallExpression节点会记录被调用的目标(比如是合约地址还是内置函数)和参数
  • 还有ModifierInvocation、EventDefinition这些和Solidity特性强相关的节点

把这些节点类型和字段都理清楚,后续写规则的时候才知道该匹配啥。

选择AST遍历的方式

遍历AST主要有两种路子,看你喜欢哪种:

1. 手动递归遍历(适合精准控制)

自己写递归函数,对每个节点类型做模式匹配,处理完当前节点再递归处理子节点。比如写个这样的Haskell函数:

data RuleViolation = RuleViolation { violationMsg :: String, sourceLoc :: SourceLocation }

processAST :: ASTNode -> [RuleViolation]
-- 处理函数定义节点,先检查函数相关规则,再遍历函数体
processAST (FunctionDefinition name params body modifiers loc) =
  let funcViolations = checkFunctionRules name modifiers loc
      bodyViolations = concatMap processAST body
  in funcViolations ++ bodyViolations
-- 处理调用表达式,检查危险调用规则
processAST (CallExpression callee args loc) =
  checkDangerousCalls callee args loc ++ concatMap processAST args
-- 其他节点直接递归处理子节点
processAST otherNode = concatMap processAST (getChildNodes otherNode)

这里的getChildNodes需要你自己实现,就是从每个AST节点里提取它的子节点列表(比如函数体里的语句、调用的参数都是子节点)。

2. 用Scrap Your Boilerplate(SYB)库减少重复代码

如果不想手动写每个节点的递归逻辑,可以用Haskell的Data.Generics库(SYB),它能自动遍历所有AST节点,帮你找到匹配特定类型的节点。比如找所有危险的空transfer调用:

import Data.Generics

findEmptyTransferCalls :: AST -> [RuleViolation]
findEmptyTransferCalls ast = everything (++) ([] `mkQ` checkEmptyTransfer) ast
  where
    checkEmptyTransfer :: CallExpression -> [RuleViolation]
    checkEmptyTransfer (CallExpression (Identifier "transfer") [] loc) =
      [RuleViolation "Empty transfer call has no effect" loc]
    checkEmptyTransfer _ = []

这个方式适合批量找某类节点,省得手动写大量递归代码。

把规则拆成独立的检查函数

规则引擎的核心就是一个个独立的检查规则,你可以把每个检查点封装成单独的函数,比如:

检查函数使用不当

比如检测没有加权限修饰器的public函数:

checkUnprotectedPublicFunctions :: FunctionDefinition -> [RuleViolation]
checkUnprotectedPublicFunctions (FunctionDefinition name _ _ modifiers loc)
  | isPublicFunction name && not (hasAuthModifier modifiers) =
      [RuleViolation ("Public function " ++ name ++ " has no access control") loc]
  | otherwise = []
  where
    isPublicFunction = ("public" `elem`) . functionVisibility
    hasAuthModifier = any (\(ModifierInvocation modName _) -> modName == "onlyOwner")

检查危险调用

比如检测低级别call/delegatecall没有检查返回值:

checkUncheckedLowLevelCalls :: CallExpression -> [RuleViolation]
checkUncheckedLowLevelCalls callExpr@(CallExpression callee _ loc)
  | isLowLevelCall callee && not (isReturnChecked callExpr) =
      [RuleViolation "Low-level call does not check return value" loc]
  | otherwise = []
  where
    isLowLevelCall (Identifier "call") = True
    isLowLevelCall (Identifier "delegatecall") = True
    isLowLevelCall _ = False
    -- 判断调用的返回值是否被检查(比如赋值给变量或用require判断)
    isReturnChecked = ... -- 这里需要根据AST结构实现逻辑

检查缺失特定元素

比如检测合约没有实现fallback函数:

checkMissingFallback :: ContractDefinition -> [RuleViolation]
checkMissingFallback (ContractDefinition name functions _ loc)
  | not (any isFallbackFunction functions) =
      [RuleViolation ("Contract " ++ name ++ " missing fallback function") loc]
  | otherwise = []
  where
    isFallbackFunction (FunctionDefinition "fallback" _ _ _ _) = True
    isFallbackFunction _ = False
别忘了处理上下文信息

很多规则需要上下文才能判断,比如“这个函数是不是在有继承的合约里?”“当前函数的可见性是啥?”这时候可以用Haskell的Reader或State monad来携带上下文,比如:

import Control.Monad.Reader

type AnalysisContext = { currentContract :: ContractDefinition, currentFunction :: Maybe FunctionDefinition }

processASTWithContext :: ASTNode -> Reader AnalysisContext [RuleViolation]
processASTWithContext (FunctionDefinition name params body modifiers loc) = do
  ctx <- ask
  let newCtx = ctx { currentFunction = Just (FunctionDefinition name params body modifiers loc) }
  funcViolations <- checkFunctionRules newCtx loc
  bodyViolations <- local (const newCtx) (concatMapM processASTWithContext body)
  return (funcViolations ++ bodyViolations)

这样在检查规则的时候就能拿到当前所在的合约、函数这些上下文信息了。

最后:测试迭代很重要

先写一些带问题的Solidity测试用例,比如:

contract BadContract {
  function unsafeFunc() public {
    // 没有权限控制
  }
  function doCall() {
    payable(address(0x123)).call{value: 1 ether}("");
    // 没检查返回值
  }
}

生成AST后跑你的规则引擎,看能不能检测到这两个问题,然后逐步完善规则的逻辑,覆盖更多Solidity的语法特性(比如继承、修饰器、事件、枚举这些)。

内容的提问来源于stack exchange,提问作者Sheldor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:33:25