如何遍历AST树

How to traverse AST tree

我正在尝试为 Groovy 创建静态分析。作为我上级的 POC,我只是尝试解析简单代码并检测 SQL 注入,这是最容易发现的一种。我在 Python 上成功做到了,这是我的主要语言,但我的公司主要使用 Grails(在 Groovy 上)。

这是我目前拥有的:

import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.ast.CodeVisitorSupport
import org.codehaus.groovy.ast.builder.AstBuilder

public class SecurityCheck extends CodeVisitorSupport {
    void visitBlockStatement(BlockStatement statement) {
        println "NEW BLOCK STATEMENT:"
        println statement.getText();
        //keep walking...
        statement.getStatements().each { ASTNode child ->
            println "CHILD FOUND: "
            println child.getText();
            child.visit(this)
        }
    }
}

def code = new File('groovy_source.groovy').text // get the code from the source file
def AstBuilder astBuilder = new AstBuilder()  // build an instance of the ast builder
def ast = astBuilder.buildFromString(CompilePhase.CONVERSION, code) // build from string when the compiler converts from tokens to AST
def SecurityCheck securityCheck = new SecurityCheck()  // create an instance of our security check class
println ast
println ast[0]
ast[0].visit(securityCheck)

groovy_source.groovy 文件非常简单,只包含一个极小的文件,其中有一个非常容易发现的漏洞:

def post(id) {
   query = "SELECT * FROM table WHERE id = " + id;
   result = sql.execute query
   return result;
}

据我了解,因为我继承自 CodeVisitorSupport,所以这只会访问 BlockStatement,然后对于该语句中的每个语句,它会使用 supper class 中的方法来访问它.

尽管如此,当我从 BlockStatement 打印文本时,它是一个空字符串,并且 for each 方法甚至从未被调用(我认为这一定意味着 AST 没有找到我的块语句的子项,即使函数中肯定有语句。

[org.codehaus.groovy.ast.stmt.BlockStatement@363a52f[]]  // println ast
org.codehaus.groovy.ast.stmt.BlockStatement@363a52f[]  // println ast[0]
NEW BLOCK STATEMENT:
{  }  // println statement.getText()

如有任何帮助,我们将不胜感激。谢谢!

我找到了答案。最后我并没有那么难,但是糟糕的文档并没有让它变得容易。如果你要遍历树,你需要给构造函数一个 false 布尔值作为第二个参数,像这样:

def ast = astBuilder.buildFromString(CompilePhase.CONVERSION, false, code)

然后您就可以按预期使用 visit* 方法了。