我们将使用在第5.4节中获得的知识来创建一个自定义的CFG检查,该检查将使用Clang的CFG来计算循环复杂度。检查将在计算出的复杂度超过阈值时发出警告。这个阈值将作为一个配置参数设置,以便在测试期间进行更改。 让我们从创建项目框架开始。 使用cyclomaticcomplexity作为检查的名称,项目框架可以按照以下方式创建: $ $ ninja install-clang-tidy 假设使用图1.4中的构建配置,此命令将在\ /llvm-project/install/bin文件夹中安装Clang-Tidy二进制文件。 重要提示 如果使用带有动态库的构建配置(其中BUILDSHAREDLIBS标志设置为ON,如图1.
我们将使用在第5.4节中获得的知识来创建一个自定义的CFG检查,该检查将使用Clang的CFG来计算循环复杂度。检查将在计算出的复杂度超过阈值时发出警告。这个阈值将作为一个配置参数设置,以便在测试期间进行更改。
让我们从创建项目框架开始。
使用cyclomaticcomplexity作为检查的名称,项目框架可以按照以下方式创建:
$` ./clang-tools-extra/clang-tidy/add_new_check.py misc cyclomaticcomplexity
\end{shell}
\begin{center}
图6.3:为misc-cyclomaticcomplexity检查创建框架
\end{center}
运行结果,得到一些修改和新的文件。最重要的文件位于clang-tools-extra/clang-tidy/misc/文件夹中的两个文件:
\begin{itemize}
\item
misc/CyclomaticcomplexityCheck.h : 检查头文件
\item
misc/CyclomaticcomplexityCheck.cpp : 检查的实现
\end{itemize}
这些文件需要修改以实现检查所需的功能。
\mySubsubsection{6.4.2.}{检查实现}
对于头文件,添加一个私有函数来计算循环复杂度,需要插入以下代码:
\begin{cpp}
private:
unsigned calculateCyclomaticComplexity(const CFG *cfg);
\end{cpp}
\begin{center}
图6.4:对CyclomaticcomplexityCheck.h的修改
\end{center}
.cpp文件中,需要进行更大量的修改。我们将从实现registerMatchers方法开始:
\begin{cpp}
void CyclomaticcomplexityCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(functionDecl().bind("func"), this);
}
\end{cpp}
\begin{center}
图6.5:对CyclomaticcomplexityCheck.cpp的修改:registerMatchers实现
\end{center}
检查将仅应用于函数声明,即clang::FunctionDecl。代码还可以扩展以支持其他C++构造。
检查方法的实现如图6.6所示。第22-23行,对匹配的AST节点(clang::FunctionDecl)执行基本的检查。第25-26行,使用CFG::buildCFG方法创建CFG对象。前两个参数指定了声明(clang::Decl)和声明的语句(clang::Stmt)。第30行,使用阈值计算循环复杂度,该阈值可以通过我们检查的"Threshold"选项获得,这为测试不同的输入程序提供了灵活性。第31-34行包含检查结果打印的实现。
\begin{cpp}
void CyclomaticcomplexityCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Func = Result.Nodes.getNodeAs("func");
if (!Func || !Func->hasBody()) return;
std::unique_ptr cfg =
CFG::buildCFG(Func, Func->getBody(), Result.Context, CFG::BuildOptions());
if (!cfg) return;
unsigned Threshold = Options.get("Threshold", 5);
unsigned complexity = calculateCyclomaticComplexity(cfg.get());
if (complexity > Threshold) {
diag(Func->getLocation(), "function %0 has high cyclomatic complexity (%1)")
<< Func << complexity;
}
}
\end{cpp}
\begin{center}
图6.6:对CyclomaticcomplexityCheck.cpp的修改:检查实现
\end{center}
calculateCyclomaticComplexity方法用于计算循环复杂度,将创建的clang::CFG对象作为输入参数:
\begin{cpp}
unsigned CyclomaticcomplexityCheck::calculateCyclomaticComplexity(
const CFG *cfg) {
unsigned edges = 0;
unsigned nodes = 0;
for (const auto *block : *cfg) {
edges += block->succ_size();
++nodes;
}
return edges - nodes + 2; // Simplified formula
}
\end{cpp}
\begin{center}
图6.7:对CyclomaticcomplexityCheck.cpp的修改:calculateCyclomaticComplexity实现
\end{center}
第42-45行遍历所有CFG块。块的数量对应于图6.2中的节点数,表示为N。计算每个块的后续数量以计算边数,表示为E。假设连接组件的数量,表示为P,在简化的示例中等于1。
实现检查之后,需要构建并运行我们新的检查了;见图6.1。
\mySubsubsection{6.4.3.}{构建和测试循环复杂度检查}
使用图1.4中指定的基本构建配置来构建Clang-Tidy,使用图5.2中显示的标准命令:
\begin{shell}`$ ninja install-clang-tidy
假设使用图1.4中的构建配置,此命令将在<…>/llvm-project/install/bin文件夹中安装Clang-Tidy二进制文件。
重要提示
如果使用带有动态库的构建配置(其中BUILD_SHARED_LIBS标志设置为ON,如图1.12所示),可能需要使用ninja
install命令来安装和构建所有工件。
对我们的示例程序进行测试,该程序如图6.1所示。正如之前计算的,test的循环复杂度为2,低于在检查方法实现中的默认值5,如图6.6所示,第29行。因此,需要覆盖默认值以1,以便在我们的测试程序中看到警告。这可以通过使用我们之前用于classchecker检查测试的-config选项来完成,如图5.20所示。测试命令如下:
图6.8:在max.cpp示例上测试循环复杂度
图6.8的第2行表示我们只想运行一个Clang-Tidy检查:misc-cyclomaticcomplexity。第3-4行,我们设置了所需的阈值。第5行指定了要测试的文件名(max.cpp),最后一行,第6行,包含程序的一些编译标志。
如果运行图6.8中的命令,将得到以下输出:
图6.9:在max.cpp示例上测试循环复杂度的输出
以下问题可能出现:Clang如何构建CFG?我们可以使用调试器来调查该过程。