Clang Static Analyzer
基于符号执行的源代码级静态分析工具

LLVM 项目的一部分 · 100% 开源 · 支持 C / C++ / Objective-C

1. 概述

Clang Static Analyzer(CSA)是 LLVM 项目中的一个源代码分析工具,能够在不运行程序的情况下,通过对源代码的深度语义分析找出 Bug。它实现了路径敏感(path-sensitive)的、过程间(inter-procedural)的符号执行(symbolic execution)

🔍 路径敏感

沿着程序的每一条执行路径进行推理,追踪分支条件对程序状态的影响。与简单的语法检查器不同,CSA 真正「理解」if/else、循环、函数调用对程序的影响。

🔗 过程间分析

自动内联函数调用,跨越函数边界追踪 Bug。一个在辅助函数中产生的空指针解引用,即使发生在五层调用栈之下,也会被 CSA 捕获。

🧠 符号执行

用符号值代替具体值执行程序,并为每条路径维护约束条件。这允许 CSA 发现那些在具体测试中极少触发的深层 Bug。

🧩 可扩展架构

检查器(Checker)以插件形式注册到分析引擎。开发者可以编写自定义检查器,聚焦特定领域的 Bug 模式。

2. 架构总览

C/C++/ObjC 源码 Clang 前端词法 → 语法 → AST → CFG 静态分析引擎(Analysis Engine)ExplodedGraph · CoreEngine · WorkList · ProgramState · StoreManager · ConstraintManager 检查器层 Checker 1 Checker 2 Checker 3 ... Checker N core · cplusplus security · deadcode nullability · optin alpha (实验性) ExplodedGraph符号执行状态空间 ProgramStateStore + Environment + GDM ConstraintManagerRangeConstraints / Z3 StoreManagerRegionStore Bug Reports: HTML · Plist · SARIF · 终端彩色输出 每条报告附完整执行路径追溯
图1:Clang Static Analyzer 架构。Clang 前端将源码编译为 AST 和控制流图(CFG);分析引擎在 CFG 上执行符号执行,构建 ExplodedGraph;检查器订阅分析事件;Bug 报告附完整路径追溯。

分析流程的核心步骤:

  1. Clang 前端解析源码,生成 AST(抽象语法树)和 CFG(控制流图)。
  2. CoreEngine 沿 CFG 边进行符号执行,维护 ProgramState(包含符号存储、环境映射和检查器通用数据)。
  3. 每个程序点产生一个 ExplodedNode,全部节点构成 ExplodedGraph——即符号执行的状态空间。
  4. Checker 通过访问者模式注册回调(如 checkPreCallcheckBindcheckDeadSymbols),在符号执行的关键节点被唤醒。
  5. Checker 发现问题后调用 BugReporter,生成包含完整执行路径的 Bug 报告。

3. 检查器(Checkers)

检查器是 CSA 真正「找 Bug」的模块。它们被组织为多个包(package),每个包各有侧重。

3.1 默认检查器(Default Checkers)

典型检查器检测的 Bug 类型
corecore.NullDereference
core.DivideZero
core.StackAddressEscape
core.uninitialized.*
空指针解引用、除零、栈地址逃逸、未初始化变量使用
cpluspluscplusplus.NewDeleteLeaks
cplusplus.Move
cplusplus.SelfAssignment
内存泄漏、use-after-move、自赋值
deadcodedeadcode.DeadStores死存储(写入后从未读取的值)
securitysecurity.ArrayBound
security.FloatLoopCounter
security.insecureAPI.*
数组越界、浮点循环计数器、gets/bcopy 等不安全 API
nullabilitynullability.NullPassedToNonnull
nullability.NullReturnedFromNonnull
将 null 传给 nonnull 参数、nonnull 函数返回 null
optinoptin.performance.Padding
optin.taint.TaintedAlloc
optin.cplusplus.UninitializedObject
结构体填充浪费、污点数据导致的分配问题、C++对象未初始化

3.2 实验性检查器(Alpha Checkers)

⚠️ 实验性——这些检查器仍在开发中,默认关闭,可能产生更多误报或崩溃。使用时需显式启用。
检查器用途
alpha.security.ArrayBoundV2更精确的数组边界检查(V2 算法)
alpha.unix.Stream检测 fopen/fclose 等流 API 的误用(double-close、use-after-close)
alpha.unix.cstring.BufferOverlap检测 memcpy 等重叠缓冲区的未定义行为
alpha.core.CastSize检测因类型转换导致的截断
alpha.clone.CloneChecker检测代码克隆(copy-paste Bug)

3.3 检查器选择指南

# 仅使用默认检查器(安全、适合CI)
scan-build make

# 启用所有核心检查器
scan-build --enable-checker core,deadcode,nullability make

# 启用所有检查器(含alpha)
scan-build --enable-all-checkers make

# 精细控制:启用安全包,禁用特定检查器
scan-build -enable-checker security -disable-checker security.insecureAPI.gets make

4. 使用方法

4.1 scan-build —— 最简入门

# 安装 clang 和 scan-build
sudo apt install clang clang-tools   # Debian/Ubuntu
brew install llvm                     # macOS

# 分析一个项目
scan-build cmake -B build .
scan-build cmake --build build

# 查看 HTML 报告
scan-view /tmp/scan-build-2026-07-06-*/

4.2 直接调用 clang --analyze

# 单文件分析
clang --analyze -Xanalyzer -analyzer-checker=core file.c

# 启用多检查器
clang --analyze -Xanalyzer -analyzer-checker=core,security,deadcode file.c

# 输出为 plist 格式(供 Xcode 使用)
clang --analyze -Xanalyzer -analyzer-output=plist file.c

4.3 CodeChecker —— 大规模分析

# CodeChecker 提供 Web UI、增量分析、结果存储
pip install codechecker

# 创建编译数据库
CodeChecker log -b "make" -o compilation_database.json

# 运行分析
CodeChecker analyze compilation_database.json -o reports

# 启动 Web 查看器
CodeChecker server &
CodeChecker store reports --name my_project
# 打开 http://localhost:8001

4.4 抑制误报

// 方法1: 源码注解 —— 抑制特定检查器的警告
#ifdef __clang_analyzer__
#define SUPPRESS __attribute__((suppress))
#else
#define SUPPRESS
#endif

void example() {
  int *p SUPPRESS = NULL;  // 此处不报告 core.NullDereference
}

// 方法2: 断言引导分析器
#include <assert.h>
void safe_use(int *p) {
  assert(p != NULL);  // 分析器利用此断言,不再报告空指针
  *p = 42;
}

5. 内部原理

5.1 符号执行引擎

Clang Static Analyzer 的核心是一种路径敏感、流敏感的符号执行。关键组件:

组件职责
CFG(控制流图)由 Clang AST 构建的有向图,节点为基本块和语句,边表示控制流转移。
ExplodedGraph符号执行的状态空间图。每个节点 = (CFG位置, ProgramState)。
CoreEngine驱动符号执行的主循环:从 WorkList 取出节点,评估 CFG 边的转移,产生新节点。
ProgramState三元组:Store(内存模型)、Environment(变量→值的映射)、GenericDataMap(检查器私有数据)。
StoreManager / RegionStore管理符号内存模型。RegionStore 以「区域」(Region)为单位建模内存,支持字段敏感推理。
ConstraintManager管理路径约束。默认使用 RangeConstraintManager,基于范围的约束求解;可选集成 Z3 进行更精确的约束求解。

5.2 路径爆炸与缓解策略

符号执行面临的核心挑战是路径爆炸——随着分支和循环,执行路径指数增长。CSA 通过以下策略缓解:

5.3 跨翻译单元分析(CTU)

默认情况下,CSA 对每个翻译单元(.c 文件)独立分析。对于跨文件 Bug(如在一个文件中分配、在另一个文件中释放),这有局限。CTU 分析在调用外部函数时加载其函数摘要进行分析,极大地扩展了分析范围。

# 分两步启用 CTU
# 步骤1: 生成 AST 并收集外部定义
clang --analyze -Xanalyzer -analyzer-config experimental-enable-naive-ctu-analysis=true \
      -Xanalyzer -analyzer-config ctu-dir=ctu-dir file.c

# 步骤2 (CodeChecker): 自动处理
CodeChecker analyze compile_commands.json --ctu

6. 编写自定义检查器

Clang Static Analyzer 的一个核心优势是其可扩展性。编写一个新检查器只需继承 Checker 基类并注册回调。

6.1 最小检查器示例

// 检查所有超过 64 个字符的函数名
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"

using namespace clang;
using namespace ento;

namespace {
class LongFunctionNameChecker : public Checker<check::ASTDecl<FunctionDecl>> {
  std::unique_ptr<BugType> BT;
public:
  LongFunctionNameChecker()
    : BT(std::make_unique<BugType>(this, "过长函数名", "代码风格")) {}

  void checkASTDecl(const FunctionDecl *FD, AnalysisManager &Mgr,
                    BugReporter &BR) const {
    if (FD->getName().size() <= 64) return;
    PathDiagnosticLocation Loc =
        PathDiagnosticLocation::create(FD, Mgr.getSourceManager());
    BR.EmitBasicReport(FD, this, "过长函数名",
                       "代码风格",
                       "函数名超过64字符,建议缩短。",
                       Loc);
  }
};
}

// 注册检查器
void ento::registerLongFunctionNameChecker(CheckerManager &Mgr) {
  Mgr.registerChecker<LongFunctionNameChecker>();
}

6.2 常用的 Checker 回调

回调触发时机典型用途
check::PreCall每次函数调用前检查参数合法性(如 malloc(0))
check::PostCall每次函数调用后标记返回值(如 malloc 返回的指针可能为 null)
check::Bind每次赋值时追踪污点数据传播
check::PreStmt<ReturnStmt>每次 return 前检查返回值是否违反合约
check::DeadSymbols符号不再可达时检测资源泄漏(malloc 的指针丢失)
check::EndFunction函数分析结束时汇总分析结果
eval::Call遇到函数调用时建模函数行为(内联效果)
💡 提示:LLVM 源码中的 clang/lib/StaticAnalyzer/Checkers/ 目录包含 100+ 个检查器实现,是学习编写自定义检查器的最佳参考。

7. 与其他工具对比

维度Clang Static AnalyzerGCC -fanalyzerCoverityCppcheck
分析深度路径敏感、过程间路径敏感、过程间路径敏感、过程间路径不敏感
符号执行✅ 完整✅ 完整✅ 完整❌ 基于模式
误报率中-低中-低低(商业优化)高(浅层匹配)
可扩展性✅ Checker API✅ GCC 插件❌ 闭源❌ 硬编码规则
开源✅ Apache 2.0✅ GPL❌ 商业✅ GPL
跨 TU 分析✅ CTU(实验性)✅ -fanalyzer✅ 完整❌ 不支持
CI 集成✅ scan-build / CodeChecker✅ -fanalyzer✅ 商业平台✅ 命令行

与 LLVM 体系内其他工具的关系

🧹 Clang-Tidy

互补工具。Clang-Tidy 是语法级(AST 匹配器)的 lint 工具,不执行符号执行。CSA 做深层次的路径敏感分析。两者可在同一项目中同时使用。

🛡️ UBSan / ASan

互补工具。Sanitizer(UBSan、ASan)是动态检测工具,在运行时插桩检测 Bug。CSA 是静态的,不运行程序,两者结合可覆盖编译时+运行时缺陷。

🔧 Clangd (LSP)

间接关联。Clangd 使用同一套 Clang 前端基础设施,提供 IDE 级别的代码补全和诊断。CSA 也可以通过 clangd 集成显示分析警告。

8. 污点分析(Taint Analysis)

CSA 内置污点分析框架,可用于追踪来自不可信源(用户输入、网络数据等)的数据流,检测注入类漏洞。

// 需要启用实验性检查器
clang --analyze -Xanalyzer \
  -analyzer-checker=optin.taint.TaintedAlloc,\
alpha.security.ArrayBoundV2 file.c

污点传播路径示例:

// CSA 能追踪以下完整路径并报告 Bug
int size = atoi(argv[1]);        // 污点源:外部输入
int *buf = malloc(size * 4);     // TaintedAlloc:污点数据控制分配大小
buf[size] = 0;                   // ArrayBoundV2:可能的越界写入

9. 最佳实践

1️⃣ CI 集成

scan-buildCodeChecker 加入 CI 管线,每次提交自动分析。

scan-build --status-bugs make
有 Bug 时返回非零退出码。

2️⃣ 渐进启用

从默认检查器开始,逐步启用 alpha 实验性检查器,评估误报率后决定是否长期使用。

3️⃣ 与编译器警告互补

启用 -Wall -Wextra 编译器警告 + CSA 深度分析,形成多层防线。还可以叠加 ASan/UBSan 在运行时检测。

4️⃣ 定期基线化

首次分析后记录 Bug 数量作为基线。之后每次分析只关注新增 Bug,避免被历史遗留问题淹没。
CodeChecker 内置此功能。


参考资源