强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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()returnreturnStmt(hasReturnValue(expr()))
cxxRecordDecl()C++ classcxxRecordDecl(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 dumpclang -Xclang -ast-dump -fsyntax-only
AST Matcher声明式查询语言,用于模式匹配
clang-query交互式 Matcher 调试工具
libToolingC++ 框架,构建代码分析/转换工具
RecursiveASTVisitor递归遍历 AST 节点
clang-tidy可扩展的代码检查工具

扩展阅读

  1. Clang AST — AST 介绍
  2. AST Matcher Reference — Matcher 完整参考
  3. LibTooling Tutorial — libTooling 教程
  4. How to write a Clang-tidy check — 编写检查器

下一章: 第 7 章:LLVM Pass 框架 — 学习 LLVM 的分析 Pass、转换 Pass 和 PassManager。