Page 133 - 《软件学报》2026年第7期
P. 133
2818 软件学报 2026 年第 37 卷第 7 期
(a) TPL成分模块划分 (b) TPL依赖关系检测
OUTPUT
消除内部依赖 建立外部依赖
检测模块内部文件
INPUT TPL A module 引用关系, 隐藏仅存 检查不同模块
在内部引用关系的 之间的依赖关系
模块划分 文件
TPL A
根据目录结构及 ... ...
元数据划分
目标代码TPL清单 消除内部依赖 建立外部依赖 TPL B TPL C
检测模块内部文件
TPL B module 引用关系, 隐藏仅存 检查不同模块
在内部引用关系的 之间的依赖关系
文件 TPL D
图 7 TPL 依赖关系检测框架
● 标准 1: 基于目录命名的 TPL 组成部分判定. 若复用文件的路径中包含 TPL 名称, 则可以直接判定该文件
为该 TPL 的一部分. 例如, spirv-tools 复用文件路径“skia/third_party/externals/spirv-tools/source/opt/optimizer.cpp”中
包含“spirv-tools”, 因此该文件应归属于 spirv-tools 库.
● 标准 2: 基于 TPL 头文件位置的 TPL 组成部分判定. 若复用文件所在目录包含 TPL 的头文件 (.h 文件), 则
该目录下的文件可归属于该 TPL. 例如, 若目录中包含 libfoo.h 文件, 且该目录下文件与 libfoo 发生代码复用, 则该
目录下的复用文件应归属于 libfoo 库.
● 标准 3: 基于 TPL 元数据文件位置的 TPL 组成部分判定. 若复用文件所在目录包含 TPL 的元数据文件 (如
README、LICENSE、COPYING), 则该文件应归属于相应 TPL. 为提高判定的准确性, 我们将检查元数据文件的
内容, 验证其中是否包含 TPL 名称. 如果验证成功, 确认元数据文件属于该 TPL, 并将此目录下的复用文件归为该
TPL 的组成部分.
● 标准 4: 基于函数复用数量的 TPL 组成部分判定. 在缺乏明确线索的情况下, 我们依赖函数复用频率来判
定复用文件是否属于 TPL 的实际组成部分. 具体方法是统计每个复用文件所在目录的复用频率 (复用频率=该目
录中与 TPL 相关的复用函数总数/该目录中函数总数), 然后选择复用频率最高的目录, 将此目录下的复用文件归
为该 TPL 的组成部分.
对于每个 TPL, 若其某些复用文件已通过前 3 个标准之一归类, 则剩余未分类的文件不再视为该 TPL 的组成
部分. 由于这些文件与 TPL 的关联度较低, 因此不予归属. 仅当所有复用文件无法通过前 3 个标准判定时, 才使用
第 4 个标准来确定哪些文件属于该 TPL 的组成部分. 此外, 已归类的文件不会被重新划分.
3.3.2 TPL 依赖关系检测
在 CAnalyzer 中, 目标软件的每个源文件首先通过 TPL 检测阶段被映射至其所属的 TPL. 由此, 每个 TPL 可
抽象为一个包含多个源文件的“文件集”. 在依赖关系检测阶段, CAnalyzer 对文件级依赖 (包括头文件引用、符号
引用与链接依赖) 进行静态分析, 并将这些依赖关系在文件集层面进行聚合, 从而构建出 TPL 间的依赖关系图. 为
建立这一映射关系, 我们首先形式化定义软件项目中任意文件对之间的依赖判定规则.
(1) 文件对依赖关系分析. 在同一软件项目中, 文件 A 的执行依赖于文件 B 的存在或正确性时, 文件 A 对文件
B 就存在依赖关系. 此类依赖关系可以细分为以下几种.
(a) 直接依赖关系. 文件 A 通过显式引用文件 B 的资源而形成依赖, 通常采用以下两种方式.
● #include 指令声明. 文件 A 通过“#include”指令包含文件 B (如.h 或.hpp), 从而能够访问文件 B 中声明的变量、
函数或类型定义. 如图 8 所示, 在 C++项目中, Main.cpp 包含了 Utility.h, 形成直接依赖关系. 我们通过解析源码中
的“#include”语句, 并匹配头文件路径, 来确认文件 Main.cpp 是否依赖文件 Utility.h. 对于存在多个同名头文件的
情况, 遵循 GNU GCC [33] 的路径解析规则, 选择路径最近或明确指定的头文件. 如果路径冲突无法解决, 则将此类
冲突留待后续间接依赖分析.

