2.5. 跨翻译单元 (CTU) 分析¶
通常,静态分析在单个翻译单元 (TU) 的边界内进行。但是,通过额外的步骤和配置,我们可以使分析能够从另一个 TU 内联函数的定义。
2.5.1. 概述¶
CTU 分析可以以多种方式使用。导入外部 TU 定义可以与预先转储的 PCH 文件一起工作,或者在分析主 TU 期间按需生成必要的 AST 结构。静态分析的驱动也可以通过多种方式实现。最直接的方法是手动指定 Clang 前端的必要命令行选项(并手动生成特定导入方法的先决条件依赖项)。此过程可以通过其他工具(如 CodeChecker 和 scan-build-py(优先考虑前者))自动化。
2.5.2. 基于 PCH 的分析¶
分析需要项目中使用到的所有翻译单元的 PCH 转储。这些可以由 Clang 前端本身生成,并且必须在文件系统中以特定方式排列。索引(将符号的 USR 名称映射到包含它们的 PCH 转储)也必须由 clang-extdef-mapping 生成。如果目标是使用基于 PCH 的分析,则索引中的条目必须具有 .ast 后缀,因为缺少该扩展名表示该条目将用作源文件,并且将按需解析。此工具使用 编译数据库 来确定使用的编译标志。分析调用必须提供包含转储和映射文件的目录。
2.5.2.1. 手动 CTU 分析¶
让我们考虑在我们最小的示例中这些源文件
// main.cpp
int foo();
int main() {
return 3 / foo();
}
// foo.cpp
int foo() {
return 0;
}
以及编译数据库
[
{
"directory": "/path/to/your/project",
"command": "clang++ -c foo.cpp -o foo.o",
"file": "foo.cpp"
},
{
"directory": "/path/to/your/project",
"command": "clang++ -c main.cpp -o main.o",
"file": "main.cpp"
}
]
我们想分析 main.cpp 并发现除以零的错误。为了能够从 foo.cpp 内联 foo 的定义,我们首先必须生成 foo.cpp 的 AST(或 PCH)文件
$ pwd $ /path/to/your/project
$ clang++ -emit-ast -o foo.cpp.ast foo.cpp
$ # Check that the .ast file is generated:
$ ls
compile_commands.json foo.cpp.ast foo.cpp main.cpp
$
下一步是创建一个 CTU 索引文件,该文件以 <USR-Length>:<USR> <File-Path> 格式保存源文件中的外部定义的 USR 名称和位置
$ clang-extdef-mapping -p . foo.cpp
9:c:@F@foo# /path/to/your/project/foo.cpp
$ clang-extdef-mapping -p . foo.cpp > externalDefMap.txt
我们必须修改 externalDefMap.txt 以包含 .ast 文件的名称,而不是源文件
$ sed -i -e "s/.cpp/.cpp.ast/g" externalDefMap.txt
我们还需要进一步修改 externalDefMap.txt 文件以包含相对路径
$ sed -i -e "s|$(pwd)/||g" externalDefMap.txt
现在所有内容都可用于 CTU 分析。我们必须为 Clang 提供特定于 CTU 的额外参数
$ pwd
/path/to/your/project
$ clang++ --analyze \
-Xclang -analyzer-config -Xclang experimental-enable-naive-ctu-analysis=true \
-Xclang -analyzer-config -Xclang ctu-dir=. \
-Xclang -analyzer-output=plist-multi-file \
main.cpp
main.cpp:5:12: warning: Division by zero
return 3 / foo();
~~^~~~~~~
1 warning generated.
$ # The plist file with the result is generated.
$ ls -F
compile_commands.json externalDefMap.txt foo.ast foo.cpp foo.cpp.ast main.cpp main.plist
$
这种手动过程容易出错并且不可扩展,因此建议使用 CodeChecker 或 scan-build-py 来分析真实项目。
2.5.2.2. 使用 CodeChecker 进行自动 CTU 分析¶
CodeChecker 项目完全支持使用 Clang 进行自动 CTU 分析。一旦我们设置了 PATH 环境变量并激活了 python venv,就完成了所有步骤
$ CodeChecker analyze --ctu compile_commands.json -o reports
$ ls -F
compile_commands.json foo.cpp foo.cpp.ast main.cpp reports/
$ tree reports
reports
├── compile_cmd.json
├── compiler_info.json
├── foo.cpp_53f6fbf7ab7ec9931301524b551959e2.plist
├── main.cpp_23db3d8df52ff0812e6e5a03071c8337.plist
├── metadata.json
└── unique_compile_commands.json
0 directories, 6 files
$
plist 文件包含分析结果,可以使用常规分析工具查看。例如,可以使用 CodeChecker parse 在命令行中查看结果
$ CodeChecker parse reports
[HIGH] /home/egbomrt/ctu_mini_raw_project/main.cpp:5:12: Division by zero [core.DivideZero]
return 3 / foo();
^
Found 1 defect(s) in main.cpp
----==== Summary ====----
-----------------------
Filename | Report count
-----------------------
main.cpp | 1
-----------------------
-----------------------
Severity | Report count
-----------------------
HIGH | 1
-----------------------
----=================----
Total number of reports: 1
----=================----
或者我们可以使用 CodeChecker parse -e html 将结果导出到 HTML 格式
$ CodeChecker parse -e html -o html_out reports
$ firefox html_out/index.html
2.5.2.3. 使用 scan-build-py 进行自动 CTU 分析(不要这样做)¶
我们积极地开发使用 CodeChecker 作为驱动程序的 CTU,scan-build-py 并未积极地为 CTU 开发。scan-build-py 有各种错误和问题,预计它只适用于最基本的项目。
scan-build-py 的示例用法
$ /your/path/to/llvm-project/clang/tools/scan-build-py/bin/analyze-build --ctu
analyze-build: Run 'scan-view /tmp/scan-build-2019-07-17-17-53-33-810365-7fqgWk' to examine bug reports.
$ /your/path/to/llvm-project/clang/tools/scan-view/bin/scan-view /tmp/scan-build-2019-07-17-17-53-33-810365-7fqgWk
Starting scan-view at: http://127.0.0.1:8181
Use Ctrl-C to exit.
[6336:6431:0717/175357.633914:ERROR:browser_process_sub_thread.cc(209)] Waited 5 ms for network service
Opening in existing browser session.
^C
$
2.5.3. 按需分析¶
分析在分析过程中生成外部 TU 的必要 AST 结构。这需要每个 TU 的精确编译器调用,这些调用可以通过手动生成,也可以通过驱动分析器的工具生成。编译器调用是一个 shell 命令,可用于编译 TU 的主源文件。从 TU 的绝对源文件路径到用于编译该 TU 的编译命令段列表的映射以 YAML 格式给出,称为 调用列表,并且必须作为分析器配置参数传递。索引(将函数 USR 名称映射到包含它们的源文件)也必须由 clang-extdef-mapping 生成。如果目标是使用按需分析,则索引中的条目不能具有 .ast 后缀,因为该扩展名表示该条目将用作 PCH 转储。外部定义的映射隐式地使用 编译数据库 来确定使用的编译标志。分析调用必须提供包含映射文件和 调用列表 的目录,该列表用于确定编译标志。
2.5.3.1. 手动 CTU 分析¶
让我们考虑在我们最小的示例中这些源文件
// main.cpp
int foo();
int main() {
return 3 / foo();
}
// foo.cpp
int foo() {
return 0;
}
编译数据库
[
{
"directory": "/path/to/your/project",
"command": "clang++ -c foo.cpp -o foo.o",
"file": "foo.cpp"
},
{
"directory": "/path/to/your/project",
"command": "clang++ -c main.cpp -o main.o",
"file": "main.cpp"
}
]
调用列表
"/path/to/your/project/foo.cpp":
- "clang++"
- "-c"
- "/path/to/your/project/foo.cpp"
- "-o"
- "/path/to/your/project/foo.o"
"/path/to/your/project/main.cpp":
- "clang++"
- "-c"
- "/path/to/your/project/main.cpp"
- "-o"
- "/path/to/your/project/main.o"
我们想分析 main.cpp 并发现除以零的错误。由于我们使用的是按需模式,因此我们只需要创建一个 CTU 索引文件,该文件以 <USR-Length>:<USR> <File-Path> 格式保存源文件中的外部定义的 USR 名称和位置
$ clang-extdef-mapping -p . foo.cpp
9:c:@F@foo# /path/to/your/project/foo.cpp
$ clang-extdef-mapping -p . foo.cpp > externalDefMap.txt
现在所有内容都可用于 CTU 分析。我们必须为 Clang 提供特定于 CTU 的额外参数
$ pwd
/path/to/your/project
$ clang++ --analyze \
-Xclang -analyzer-config -Xclang experimental-enable-naive-ctu-analysis=true \
-Xclang -analyzer-config -Xclang ctu-dir=. \
-Xclang -analyzer-config -Xclang ctu-invocation-list=invocations.yaml \
-Xclang -analyzer-output=plist-multi-file \
main.cpp
main.cpp:5:12: warning: Division by zero
return 3 / foo();
~~^~~~~~~
1 warning generated.
$ # The plist file with the result is generated.
$ ls -F
compile_commands.json externalDefMap.txt foo.cpp main.cpp main.plist
$
这种手动过程容易出错并且不可扩展,因此建议使用 CodeChecker 或 scan-build-py 来分析真实项目。
2.5.3.2. 使用 CodeChecker 进行自动 CTU 分析¶
CodeChecker 项目完全支持使用 Clang 进行自动 CTU 分析。一旦我们设置了 PATH 环境变量并激活了 python venv,就完成了所有步骤
$ CodeChecker analyze --ctu --ctu-ast-loading-mode on-demand compile_commands.json -o reports
$ ls -F
compile_commands.json foo.cpp main.cpp reports/
$ tree reports
reports
├── compile_cmd.json
├── compiler_info.json
├── foo.cpp_53f6fbf7ab7ec9931301524b551959e2.plist
├── main.cpp_23db3d8df52ff0812e6e5a03071c8337.plist
├── metadata.json
└── unique_compile_commands.json
0 directories, 6 files
$
plist 文件包含分析结果,可以使用常规分析工具查看。例如,可以使用 CodeChecker parse 在命令行中查看结果
$ CodeChecker parse reports
[HIGH] /home/egbomrt/ctu_mini_raw_project/main.cpp:5:12: Division by zero [core.DivideZero]
return 3 / foo();
^
Found 1 defect(s) in main.cpp
----==== Summary ====----
-----------------------
Filename | Report count
-----------------------
main.cpp | 1
-----------------------
-----------------------
Severity | Report count
-----------------------
HIGH | 1
-----------------------
----=================----
Total number of reports: 1
----=================----
或者我们可以使用 CodeChecker parse -e html 将结果导出到 HTML 格式
$ CodeChecker parse -e html -o html_out reports
$ firefox html_out/index.html
2.5.3.3. 使用 scan-build-py 进行自动 CTU 分析(不要这样做)¶
我们积极地开发使用 CodeChecker 作为驱动程序的 CTU,scan-build-py 并未积极地为 CTU 开发。scan-build-py 有各种错误和问题,预计它只适用于最基本的项目。
目前,scan-build-py 不支持按需分析。