AST本身不检测死循环,但能定位无副作用空循环、恒真条件、递归无出口等可疑结构;因其仅描述语法,不含变量值、执行路径或控制流逻辑,故无法单靠AST判定是否真正死循环。

ast 本身不检测死循环,但能帮你定位可能构成死循环的结构;真正判断是否“死”得靠语义分析或运行时观察——不过用 ast 提取关键模式(比如无副作用的 for {}、条件恒真、递归无出口)是静态检查的第一步。
为什么不能单靠 ast 判断死循环
AST 只描述语法结构,不包含执行路径、变量值或控制流逻辑。例如 for { } 和 for i := 0; i 在 AST 中都是 *ast.ForStmt,但前者是明确死循环,后者不是——区别在 body 是否改变条件变量、条件表达式是否可求值为 false,这些信息 ast 没有。
-
*ast.ForStmt的Cond字段可能为nil(对应for {}),这是最可靠的死循环信号 - 若
Cond是常量 true(如&ast.BasicLit{Kind: token.BOOL, Value: "true"}),也高度可疑 - 但
Cond是变量或函数调用(如i )时,ast 无法知道它是否会变、是否终将为 false - 递归调用是否“有出口”,需结合函数体遍历 + 控制流图(CFG),超出 ast 能力范围
怎么用 ast.Inspect 找出高危 for 结构
重点盯 *ast.ForStmt 节点,检查其 Cond 字段是否缺失或恒真:
- 必须先判空:
if f.Cond == nil→ 直接标记为潜在死循环(for {}) - 再断言是否为布尔字面量:
if lit, ok := f.Cond.(*ast.BasicLit); ok && lit.Kind == token.BOOL && lit.Value == "true" - 别漏掉带标签的死循环:
loop: for { }中的f.Label是*ast.Ident,可提取名字用于报告 - 注意:parser 默认不解析注释,若想跳过 //nolint:deadcode 这类标记,得加
parser.ParseCommentsmode 并手动扫描f.Comments
示例片段:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
ast.Inspect(file, func(n ast.Node) bool {
if f, ok := n.(*ast.ForStmt); ok {
if f.Cond == nil {
fmt.Printf("L%d: suspicious infinite loop (no condition)\n", f.Pos().Line)
return true
}
if lit, ok := f.Cond.(*ast.BasicLit); ok && lit.Kind == token.BOOL && lit.Value == "true" {
fmt.Printf("L%d: infinite loop with constant true condition\n", f.Pos().Line)
}
}
return true
})
容易被忽略的坑:for-range 和 goto 伪装的死循环
静态分析容易漏掉两类隐蔽结构:
-
for range若迭代的是空切片或 channel 已关闭,行为是“立即退出”,但 ast 看不出 slice 长度或 chan 状态 → 无法判定,只能跳过 -
goto可能绕回 for 外部形成循环,而 ast 不建 CFG,*ast.BranchStmt的Label字段只是名字,找不到目标位置 → 必须配合符号表或走go/types -
select里全为default或无 case,等价于空循环,但它是*ast.SelectStmt,不是*ast.ForStmt,需单独处理 - init 函数里起 goroutine + channel 同步,可能造成“逻辑死循环”,但 panic 信息里只显示
all goroutines are asleep,和 ast 无关
实际项目中该怎么做
别指望纯 ast 实现 100% 死循环检测——staticcheck 的 SA4006 规则才是工业级方案,它结合了 ast + types + 数据流分析。你自己写检查器时:
- 优先捕获
for {}和for true {}这两种零误报模式 - 对
for i := 0; ; i++(Cond 为 nil)保持警惕,但需人工确认是否真有问题 - 避免尝试分析条件表达式是否“可能为 false”——那已进入定理证明范畴,不是 linter 该干的
- 如果真要深挖,直接集成 staticcheck,而不是重复造轮子;它的 SA4006 已覆盖 for/for-range/select/goto 多种情况
最易被忽略的一点:parser.ParseFile 时没加 parser.AllErrors,遇到语法错误就提前返回,导致部分 for 节点根本没进 AST,你以为漏检,其实是解析失败没看到。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










