CFG是Clang工具进行高级静态分析的基本数据结构。Clang从其AST为函数构建CFG,识别基本块和控制流边。Clang的CFG构建处理各种C/C++构造,包括循环、条件语句、switch案例,以及setjmp/longjmp和C++异常等复杂构造。考虑使用图6.1的示例来研究这个过程。 图6.1的示例具有五个节点,如图6.2所示。我们将使用调试器来调查该过程: $ $ lldb \ /llvm-project/install/bin/clang-tidy – -checks="-\,misc-cyclomaticcomplexity" -config="CheckOptions: \[key: misc-cyclomaticcomplexity.
CFG是Clang工具进行高级静态分析的基本数据结构。Clang从其AST为函数构建CFG,识别基本块和控制流边。Clang的CFG构建处理各种C/C++构造,包括循环、条件语句、switch案例,以及setjmp/longjmp和C++异常等复杂构造。考虑使用图6.1的示例来研究这个过程。
图6.1的示例具有五个节点,如图6.2所示。我们将使用调试器来调查该过程:
$`lldb <...>/llvm-project/install/bin/clang-tidy --
-checks="-*,misc-cyclomaticcomplexity"
-config="{CheckOptions:
[{key: misc-cyclomaticcomplexity.Threshold, value: '1'}]}"
max.cpp
-- -std=c++17 -Wno-all
\end{shell}
\begin{center}
图6.10:使用调试器调查CFG创建过程的演示
\end{center}
使用了与图6.8相同的命令,但将第一行命令更改为通过调试器运行检查。还更改了最后一行,以抑制编译器生成的所有警告。
\begin{myNotic}{重要提示}
高级静态分析是语义分析的一部分。如果Clang检测到不可达代码,将打印警告,由-Wunreachable-code选项控制。检测器是Clang语义分析的一部分,并使用CFG以及AST作为基本数据结构来检测此类问题。可以通过指定特殊的-Wno-all命令行选项来抑制这些警告,从而在Clang中禁用CFG初始化,该选项抑制由编译器生成的所有警告。
\end{myNotic}
我们将断点设置在CFGBuilder::createBlock函数上,该函数创建CFG块。
\begin{shell}`$ lldb <...>/llvm-project/install/bin/clang-tidy –
-checks="-*,misc-cyclomaticcomplexity" -config="CheckOptions: [key:
misc-cyclomaticcomplexity.Threshold, value: ’1’]" max.cpp –
-std=c++17 -Wno-all ... (lldb) b CFGBuilder::createBlock Breakpoint 1:
where = ...CFGBuilder::createBlock(bool) const ...
图6.11:运行调试器并为CFGBuilder::createBlock设置断点
如果运行调试器,会看到该函数调用了五次,所以max函数创建了五个CFG块:
图6.12:创建CFG块,断点已显示
图6.12中显示的调试器演示可以视为CFG创建过程的入口点。现在,是时候深入探讨实现细节了。
块是以逆序创建的,如图 6.13 所示。首先创建的是退出块,如图 6.13 第 4
行所示。CFG 构造器遍历作为参数传递的 clang::Stmt 对象(第 9
行),入口块最后创建(第 12 行):
std::unique_ptr<CFG> CFGBuilder::buildCFG(const Decl *D, Stmt
*Statement) ... // Create an empty block that will serve as the exit
block for the CFG. Succ = createBlock(); assert(Succ ==
&cfg->getExit()); Block = nullptr; // the EXIT block is empty. ... ...
// Visit the statements and create the CFG. CFGBlock *B =
Visit(Statement, ...); ... // Create an empty entry block that has no
predecessors. cfg->setEntry(createBlock()); ... return std::move(cfg);
图 6.13: 从 clang/lib/Analysis/CFG.cpp 中简化的 buildCFG 实现
访问器使用 clang::Stmt::getStmtClass
方法,实现一个基于语句类型的自定义访问器:
CFGBlock *CFGBuilder::Visit(Stmt * S, ...) ... switch
(S->getStmtClass()) ... case Stmt::CompoundStmtClass: return
VisitCompoundStmt(cast<CompoundStmt>(S), ...); ... case
Stmt::IfStmtClass: return VisitIfStmt(cast<IfStmt>(S)); ... case
Stmt::ReturnStmtClass: ... return VisitReturnStmt(S); ...
图 6.14:语句访问器实现;用于我们的示例的案例已突出显示,代码取自
clang/lib/Analysis/CFG.cpp
示例包含两个返回语句和一个 if
语句,组合成一个复合语句。访问器的相关部分如图 6.14 所示。
本例中,传递的语句是一个复合语句;因此,图 6.14 的第 6
行激活。然后执行以下代码:
CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C, ...)
... CFGBlock *LastBlock = Block;
for (Stmt *S : llvm::reverse(C->body()))
// If we hit a segment of code just containing ’;’ (NullStmts), we can
// get a null block back. In such cases, just use the LastBlock CFGBlock
*newBlock = Visit(S, ...);
if (newBlock) LastBlock = newBlock;
if (badCFG) return nullptr; ...
return LastBlock;
图 6.15:复合语句访问器,代码取自 clang/lib/Analysis/CFG.cpp
在示例创建 CFG 的过程中,访问了多个构造。第一个是 clang::IfStmt:
CFGBlock *CFGBuilder::VisitIfStmt(IfStmt *I)
... // Process the true branch. CFGBlock *ThenBlock; Stmt *Then =
I->getThen(); ... ThenBlock = Visit(Then, ...); ...
// Specially handle "if (expr1 || ...)" and "if (expr1 && ...)" // ...
if (Cond && Cond->isLogicalOp()) ... else // Now create a new block
containing the if statement. Block = createBlock(false); ... ...
图 6.16: if 语句访问器,代码取自 clang/lib/Analysis/CFG.cpp
第 18 行为 if 语句创建了一个特殊块,我们也访问了第 8 行的"then"条件。
"then"条件访问一个返回语句:
CFGBlock *CFGBuilder::VisitReturnStmt(Stmt *S) // Create the new
block. Block = createBlock(false); ... // Visit children if (ReturnStmt
*RS = dyn_cast<ReturnStmt>(S)) if (Expr *O = RS->getRetValue())
return Visit(O, ...); return Block; ...
图 6.17:返回语句访问器,代码取自 clang/lib/Analysis/CFG.cpp
对于我们的示例,在第 3 行创建了一个块,并在第 8
行访问了返回表达式。返回表达式是一个简单的表达式,不需要创建新的块。
图 6.13 至图 6.17
中呈现的代码仅展示了块创建过程。为了简化,省略了一些重要的部分。构建过程包括以下内容:
边的创建:
典型的块可以有一个或多个后继者。每个块的节点列表及其后继者(边)列表维护了整个图形结构,表示符号程序执行。
存储元信息:
每个块存储与其相关的额外元信息。例如,每个块保存了一组块内的语句。
处理特殊情况: C++
是一种复杂的语言,有许多不同的语言结构需要特殊处理。
控制流图(CFG)是高级代码分析的基本数据结构。Clang 使用 CFG
创建了几个工具。让我们简要地了解一下它们。