LLVM 开发指南 / 第 6 章:Clang AST 与工具
第 6 章:Clang AST 与工具
“AST 是源代码的结构化表示,理解 AST 就理解了代码的语义。”
6.1 Clang AST 概述
Clang AST(Abstract Syntax Tree,抽象语法树)是源代码的树形结构表示,保留了丰富的语义信息,是 Clang 工具链的基石。
6.1.1 AST 与 Token 的区别
源代码: int x = 42 + y;
Token 流: int x = 42 + y ;
AST:
DeclStmt
└── VarDecl 'x' 'int'
└── BinaryOperator '+'
├── IntegerLiteral 42
└── DeclRefExpr 'y' 'int'
6.1.2 AST 三大节点类型
┌──────────────────────────────────────────────────┐
│ Clang AST 节点 │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Decl │ │ Stmt │ │ Type │ │
│ │ (声明) │ │ (语句) │ │ (类型) │ │
│ │ │ │ │ │ │ │
│ │ FunctionDecl│ │ CompoundStmt│ │ BuiltinType│ │
│ │ VarDecl │ │ IfStmt │ │ PointerType│ │
│ │ RecordDecl │ │ ForStmt │ │ RecordType │ │
│ │ TypedefDecl│ │ ReturnStmt │ │ ArrayType │ │
│ │ ... │ │ ... │ │ ... │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└──────────────────────────────────────────────────┘
6.2 导出和查看 AST
6.2.1 AST dump
# 导出完整 AST(文本形式)
clang -Xclang -ast-dump -fsyntax-only file.c
# 导出 AST(包含隐式节点)
clang -Xclang -ast-dump -Xclang -ast-dump-decl-types -fsyntax-only file.c
# 导出 AST 到 JSON
clang -Xclang -ast-dump=json -fsyntax-only file.c > ast.json
# 仅显示特定函数
clang -Xclang -ast-dump-filter=main -fsyntax-only file.c
6.2.2 查看简单程序的 AST
// simple.c
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(1, 2);
return result;
}
clang -Xclang -ast-dump -fsyntax-only simple.c
输出(简化):
TranslationUnitDecl
├── FunctionDecl 'add' 'int (int, int)'
│ ├── ParmVarDecl 'a' 'int'
│ ├── ParmVarDecl 'b' 'int'
│ └── CompoundStmt
│ └── ReturnStmt
│ └── BinaryOperator '+'
│ ├── ImplicitCastExpr 'int' <LValueToRValue>
│ │ └── DeclRefExpr 'a' 'int'
│ └── ImplicitCastExpr 'int' <LValueToRValue>
│ └── DeclRefExpr 'b' 'int'
└── FunctionDecl 'main' 'int ()'
└── CompoundStmt
├── DeclStmt
│ └── VarDecl 'result' 'int'
│ └── CallExpr 'int'
│ ├── ImplicitCastExpr <FunctionToPointerDecay>
│ │ └── DeclRefExpr 'add' 'int (int, int)'
│ ├── IntegerLiteral 1
│ └── IntegerLiteral 2
└── ReturnStmt
└── ImplicitCastExpr 'int' <LValueToRValue>
└── DeclRefExpr 'result' 'int'
6.3 AST 核心类层次
6.3.1 Decl(声明)层次
Decl (基类)
├── NamedDecl (有名字的声明)
│ ├── ValueDecl (有值的声明)
│ │ ├── DeclaratorDecl
│ │ │ ├── FunctionDecl // 函数声明/定义
│ │ │ │ ├── CXXMethodDecl // C++ 成员函数
│ │ │ │ ├── CXXConstructor // 构造函数
│ │ │ │ └── CXXDestructor // 析构函数
│ │ │ └── VarDecl // 变量声明
│ │ │ └── ParmVarDecl // 函数参数
│ │ └── EnumConstantDecl // 枚举值
│ ├── TypeDecl (类型声明)
│ │ ├── TagDecl
│ │ │ ├── RecordDecl // struct/union
│ │ │ │ └── CXXRecordDecl // C++ class
│ │ │ └── EnumDecl // 枚举
│ │ └── TypedefDecl / TypeAliasDecl // typedef/using
│ └── NamespaceDecl // 命名空间
├── TranslationUnitDecl // 顶层翻译单元
└── LinkageSpecDecl // extern "C"
6.3.2 Stmt(语句)层次
Stmt (基类)
├── DeclStmt // 声明语句
├── NullStmt // 空语句 ;
├── CompoundStmt // 复合语句 { ... }
├── LabelStmt // 标签
├── IfStmt // if 语句
├── ForStmt // for 循环
├── WhileStmt // while 循环
├── DoStmt // do-while 循环
├── SwitchStmt // switch 语句
├── CaseStmt // case 分支
├── DefaultStmt // default 分支
├── ReturnStmt // return
├── BreakStmt // break
├── ContinueStmt // continue
├── GotoStmt // goto
│
│ (继承自 Stmt 的 Expr)
├── Expr (表达式基类)
│ ├── IntegerLiteral // 整数字面量
│ ├── FloatingLiteral // 浮点字面量
│ ├── StringLiteral // 字符串字面量
│ ├── CharacterLiteral // 字符字面量
│ ├── DeclRefExpr // 变量引用
│ ├── BinaryOperator // 二元运算
│ ├── UnaryOperator // 一元运算
│ ├── CallExpr // 函数调用
│ ├── CastExpr // 类型转换
│ ├── MemberExpr // 成员访问 a.b
│ ├── ArraySubscriptExpr // 数组下标 a[i]
│ ├── ConditionalOperator // 三元运算 ?:
│ └── ...
6.3.3 Type(类型)层次
Type (基类)
├── BuiltinType // 内建类型 (int, float, ...)
├── PointerType // 指针类型
├── ReferenceType // 引用类型 (&)
├── ArrayType // 数组类型
│ ├── ConstantArrayType // 固定大小数组
│ ├── VariableArrayType // 变长数组 (VLA)
│ └── IncompleteArrayType // 不完整数组
├── RecordType // struct/union/class
├── EnumType // 枚举
├── TypedefType // typedef
├── FunctionType // 函数类型
│ └── FunctionNoProtoType // K&R 风格
│ └── FunctionProtoType // 带原型的函数类型
└── ElaboratedType // 带限定名的类型 (struct X)
6.4 AST Matcher 语言
AST Matcher 是 Clang 提供的声明式查询语言,用于在 AST 中查找特定模式。
6.4.1 基本概念
// Matcher 表达式的一般形式:
// matchKind(pattern, matcher1, matcher2, ...)
// matchKind:
// decl() — 匹配声明
// stmt() — 匹配语句/表达式
// type() — 匹配类型
// attr() — 匹配属性
// nestedNameSpecifier() — 匹配嵌套名说明符
// 组合器 (Narrowers/Binders):
// has() — 包含子节点
// hasAncestor() — 祖先节点
// hasParent() — 父节点
// forEach() — 对每个子节点
// unless() — 取反
// anyOf() — 或
// allOf() — 与
6.4.2 常用 Matcher 示例
// 匹配所有函数声明
functionDecl()
// 匹配名为 "main" 的函数
functionDecl(hasName("main"))
// 匹配返回 int 的函数
functionDecl(returns(asString("int")))
// 匹配有至少两个参数的函数
functionDecl(parameterCountIs(2))
// 匹配所有 if 语句
ifStmt()
// 匹配条件为 == 比较的 if
ifStmt(hasCondition(
binaryOperator(hasOperatorName("=="))
))
// 匹配所有调用 expr::func() 的调用
callExpr(callee(functionDecl(hasName("func"))))
// 匹配对变量 "x" 的赋值
binaryOperator(
hasOperatorName("="),
hasLHS(declRefExpr(to(varDecl(hasName("x")))))
)
// 匹配 C++ 的 new 表达式
cxxNewExpr()
// 匹配范围 for 循环
cxxForRangeStmt()
// 匹配所有未使用的变量
varDecl(unused())
// 匹配整数字面量 42
integerLiteral(equals(42))
// 匹配字符串字面量包含 "hello"
stringLiteral(hasSubstr("hello"))
6.4.3 Matcher 语言参考表
| Matcher | 说明 | 示例 |
|---|---|---|
functionDecl() | 函数声明 | functionDecl(hasName("foo")) |
varDecl() | 变量声明 | varDecl(hasType(asString("int"))) |
callExpr() | 函数调用 | callExpr(callee(functionDecl())) |
ifStmt() | if 语句 | ifStmt(hasCondition(expr())) |
forStmt() | for 循环 | forStmt(hasBody(compoundStmt())) |
binaryOperator() | 二元运算 | binaryOperator(hasOperatorName("+")) |
unaryOperator() | 一元运算 | unaryOperator(hasOperatorName("!")) |
memberExpr() | 成员访问 | memberExpr(member(hasName("x"))) |
castExpr() | 类型转换 | castExpr(hasDestinationType(asString("int"))) |
returnStmt() | return | returnStmt(hasReturnValue(expr())) |
cxxRecordDecl() | C++ class | cxxRecordDecl(hasName("MyClass")) |
cxxMethodDecl() | C++ 成员函数 | cxxMethodDecl(isOverride()) |
cxxConstructExpr() | 构造调用 | cxxConstructExpr(argumentCountIs(2)) |
6.4.4 使用 clang-query 交互式查询
# 启动 clang-query 交互模式
clang-query simple.c
# 查询示例
> match functionDecl()
# 匹配所有函数
> match varDecl(hasType(asString("int")))
# 匹配所有 int 变量
> match callExpr(callee(functionDecl(hasName("add"))))
# 匹配对 add() 的调用
> set output dump
# 设置输出为 AST dump 模式
> match ifStmt(hasCondition(binaryOperator(hasOperatorName("=="))))
# 匹配条件为 == 的 if 语句
> enable output diag
# 切换回诊断模式
> quit
# 使用 Matcher 文件
cat > match.m << 'EOF'
match functionDecl(hasName("main"), isExpansionInMainFile())
EOF
clang-query -f match.m simple.c
6.5 libTooling — C++ API
libTooling 是 Clang 提供的 C++ 框架,用于构建独立的代码分析和转换工具。
6.5.1 最简单的 libTooling 工具
// find_functions.cpp — 列出所有函数名
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/Support/CommandLine.h"
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::tooling;
static llvm::cl::OptionCategory MyToolCategory("my-tool options");
// 回调类:匹配成功时调用
class FunctionPrinter : public MatchFinder::MatchCallback {
public:
void run(const MatchFinder::MatchResult &Result) override {
const FunctionDecl *Func =
Result.Nodes.getNodeAs<FunctionDecl>("func");
if (Func) {
llvm::outs() << "函数: " << Func->getName()
<< " (" << Func->getReturnType().getAsString()
<< ")\n";
}
}
};
int main(int argc, const char **argv) {
auto ExpectedParser =
CommonOptionsParser::create(argc, argv, MyToolCategory);
if (!ExpectedParser) {
llvm::errs() << ExpectedParser.takeError();
return 1;
}
CommonOptionsParser &OptionsParser = ExpectedParser.get();
ClangTool Tool(OptionsParser.getCompilations(),
OptionsParser.getSourcePathList());
FunctionPrinter Printer;
MatchFinder Finder;
Finder.addMatcher(functionDecl(isExpansionInMainFile()).bind("func"),
&Printer);
return Tool.run(newFrontendActionFactory(&Finder).get());
}
6.5.2 CMake 构建配置
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyClangTool)
find_package(Clang REQUIRED CONFIG)
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found Clang ${CLANG_PACKAGE_VERSION}")
message(STATUS "Using ClangConfig.cmake in: ${Clang_DIR}")
include_directories(${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
add_executable(find-functions find_functions.cpp)
target_link_libraries(find-functions
clangTooling
clangASTMatchers
clangFrontend
clangSerialization
clangDriver
clangParse
clangSema
clangAnalysis
clangAST
clangBasic
clangEdit
clangLex
clangRewrite
)
# 如果使用 LLVM 共享库
# target_link_libraries(find-functions LLVM)
mkdir build && cd build
cmake -DLLVM_DIR=/opt/llvm/lib/cmake/llvm \
-DClang_DIR=/opt/llvm/lib/cmake/clang ..
make
# 运行工具
./find-functions test.c
6.5.3 代码重构工具
// add_override.cpp — 为 C++ 虚函数添加 override 关键字
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Rewrite/Core/Rewriter.h"
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::tooling;
class OverrideAdder : public MatchFinder::MatchCallback {
Rewriter &TheRewriter;
public:
OverrideAdder(Rewriter &R) : TheRewriter(R) {}
void run(const MatchFinder::MatchResult &Result) override {
const CXXMethodDecl *Method =
Result.Nodes.getNodeAs<CXXMethodDecl>("method");
if (Method && Method->isVirtual() && Method->size_overridden_methods() > 0) {
SourceLocation EndLoc = Method->getLocation();
// 在函数名后添加 override
// 实际实现需要更精确的位置计算
llvm::outs() << "需要添加 override: "
<< Method->getNameAsString() << "\n";
}
}
};
int main(int argc, const char **argv) {
auto ExpectedParser =
CommonOptionsParser::create(argc, argv, llvm::cl::GeneralCategory);
if (!ExpectedParser) return 1;
ClangTool Tool(ExpectedParser->getCompilations(),
ExpectedParser->getSourcePathList());
// 匹配有 override 但缺少 override 关键字的函数
auto Matcher = cxxMethodDecl(
isOverride(),
unless(hasName("~")) // 排除析构函数
).bind("method");
OverrideAdder Adder;
MatchFinder Finder;
Finder.addMatcher(Matcher, &Adder);
return Tool.run(newFrontendActionFactory(&Finder).get());
}
6.5.4 FrontendAction 框架
// 自定义 FrontendAction
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/CompilerInstance.h"
class MyASTConsumer : public ASTConsumer {
public:
void HandleTranslationUnit(ASTContext &Context) override {
// 获取翻译单元的顶层声明
TranslationUnitDecl *TU = Context.getTranslationUnitDecl();
for (auto *Decl : TU->decls()) {
if (const FunctionDecl *Func = dyn_cast<FunctionDecl>(Decl)) {
if (Func->isThisDeclarationADefinition()) {
llvm::outs() << "函数: " << Func->getName() << "\n";
// 遍历函数体
if (Stmt *Body = Func->getBody()) {
for (auto *Child : Body->children()) {
llvm::outs() << " 语句类型: "
<< Child->getStmtClassName() << "\n";
}
}
}
}
}
}
};
class MyFrontendAction : public ASTFrontendAction {
public:
std::unique_ptr<ASTConsumer> CreateASTConsumer(
CompilerInstance &CI, StringRef file) override {
return std::make_unique<MyASTConsumer>();
}
};
6.6 AST 遍历
6.6.1 RecursiveASTVisitor
// visitor.cpp — 递归 AST 遍历器
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
using namespace clang;
class MyVisitor : public RecursiveASTVisitor<MyVisitor> {
public:
// 访问每个函数声明
bool VisitFunctionDecl(FunctionDecl *Func) {
if (Func->isThisDeclarationADefinition()) {
llvm::outs() << "函数: " << Func->getName()
<< " 参数数: " << Func->getNumParams() << "\n";
}
return true; // 返回 true 继续遍历
}
// 访问每个变量声明
bool VisitVarDecl(VarDecl *Var) {
llvm::outs() << "变量: " << Var->getName()
<< " 类型: " << Var->getType().getAsString() << "\n";
return true;
}
// 访问每个二元运算
bool VisitBinaryOperator(BinaryOperator *Op) {
llvm::outs() << "运算: " << Op->getOpcodeStr().str() << "\n";
return true;
}
// 访问每个 if 语句
bool VisitIfStmt(IfStmt *If) {
llvm::outs() << "if 语句\n";
return true;
}
};
class MyConsumer : public ASTConsumer {
MyVisitor Visitor;
public:
void HandleTranslationUnit(ASTContext &Context) override {
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
}
};
class MyAction : public ASTFrontendAction {
public:
std::unique_ptr<ASTConsumer> CreateASTConsumer(
CompilerInstance &CI, StringRef file) override {
return std::make_unique<MyConsumer>();
}
};
int main(int argc, const char **argv) {
auto ExpectedParser =
tooling::CommonOptionsParser::create(argc, argv, llvm::cl::GeneralCategory);
if (!ExpectedParser) return 1;
tooling::ClangTool Tool(ExpectedParser->getCompilations(),
ExpectedParser->getSourcePathList());
return Tool.run(tooling::newFrontendActionFactory<MyAction>().get());
}
6.7 常见业务场景
6.7.1 代码统计工具
// 统计代码行数、函数数、注释比例
class CodeStats : public RecursiveASTVisitor<CodeStats> {
unsigned FunctionCount = 0;
unsigned VariableCount = 0;
unsigned StmtCount = 0;
ASTContext *Ctx;
public:
void setContext(ASTContext &C) { Ctx = &C; }
bool VisitFunctionDecl(FunctionDecl *F) {
if (F->isThisDeclarationADefinition()) FunctionCount++;
return true;
}
bool VisitVarDecl(VarDecl *V) { VariableCount++; return true; }
bool VisitStmt(Stmt *S) { StmtCount++; return true; }
void print() {
llvm::outs() << "函数: " << FunctionCount << "\n"
<< "变量: " << VariableCount << "\n"
<< "语句: " << StmtCount << "\n";
}
};
6.7.2 代码安全审计
// 检测潜在的安全问题
class SecurityChecker : public MatchFinder::MatchCallback {
public:
void run(const MatchFinder::MatchResult &Result) override {
// 检测 sprintf (应该用 snprintf)
if (const CallExpr *Call = Result.Nodes.getNodeAs<CallExpr>("sprintf")) {
llvm::errs() << "警告: 使用 sprintf,建议使用 snprintf\n"
<< " 位置: " << Call->getBeginLoc().printToString(
*Result.SourceManager) << "\n";
}
// 检测使用 malloc 但未检查返回值
if (const CallExpr *Call = Result.Nodes.getNodeAs<CallExpr>("malloc")) {
// 检查父节点是否是赋值并检查 NULL
llvm::errs() << "警告: 使用 malloc,请检查返回值\n";
}
}
};
6.7.3 自动代码变换
// 将 C 风格转换替换为 C++ 风格
// (int)x → static_cast<int>(x)
class CastReplacer : public MatchFinder::MatchCallback {
Replacements &Replace;
public:
CastReplacer(Replacements &R) : Replace(R) {}
void run(const MatchFinder::MatchResult &Result) override {
const CStyleCastExpr *Cast =
Result.Nodes.getNodeAs<CStyleCastExpr>("cast");
if (!Cast) return;
SourceManager &SM = *Result.SourceManager;
SourceLocation Loc = Cast->getLParenLoc();
std::string Replacement = "static_cast<" +
Cast->getTypeAsWritten().getAsString() + ">(";
// 替换左括号和类型
Replace.addReplacement(SM, Loc, Replacement);
}
};
6.8 clang-tidy 检查器
# 使用 clang-tidy
clang-tidy file.cpp -- -std=c++17
# 列出所有检查器
clang-tidy --list-checks
# 启用特定检查
clang-tidy -checks='-*,modernize-*' file.cpp
# 自动修复
clang-tidy -fix file.cpp
# 常用检查分组
clang-tidy -checks='bugprone-*,performance-*,modernize-*' file.cpp
6.8.1 编写自定义 clang-tidy 检查
// MyCheck.h
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MY_CHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MY_CHECK_H
#include "../ClangTidyCheck.h"
namespace clang::tidy::my {
class MyCheck : public ClangTidyCheck {
public:
MyCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
};
} // namespace clang::tidy::my
#endif
// MyCheck.cpp
#include "MyCheck.h"
#include "clang/ASTMatchers/ASTMatchers.h"
using namespace clang::ast_matchers;
namespace clang::tidy::my {
void MyCheck::registerMatchers(MatchFinder *Finder) {
// 匹配使用 NULL 而不是 nullptr
Finder->addMatcher(
implicitCastExpr(
hasSourceExpression(integerLiteral(equals(0))),
hasImplicitDestinationType(asString("nullptr_t"))
).bind("null"),
this
);
}
void MyCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Node = Result.Nodes.getNodeAs<Expr>("null");
if (Node) {
diag(Node->getBeginLoc(), "使用 nullptr 替代 NULL");
}
}
} // namespace clang::tidy::my
6.9 本章小结
| 概念 | 要点 |
|---|---|
| AST 节点 | Decl(声明)、Stmt(语句)、Type(类型) |
| AST dump | clang -Xclang -ast-dump -fsyntax-only |
| AST Matcher | 声明式查询语言,用于模式匹配 |
| clang-query | 交互式 Matcher 调试工具 |
| libTooling | C++ 框架,构建代码分析/转换工具 |
| RecursiveASTVisitor | 递归遍历 AST 节点 |
| clang-tidy | 可扩展的代码检查工具 |
扩展阅读
- Clang AST — AST 介绍
- AST Matcher Reference — Matcher 完整参考
- LibTooling Tutorial — libTooling 教程
- How to write a Clang-tidy check — 编写检查器
下一章: 第 7 章:LLVM Pass 框架 — 学习 LLVM 的分析 Pass、转换 Pass 和 PassManager。