Page 257 - 《软件学报》2026年第3期
P. 257

1220                                                       软件学报  2026  年第  37  卷第  3  期


                 多扫多查的情况. 为缓解该问题带来的影响, 本文采用了                 CodeQL  这一相对更加可靠的工具进行统计分析, 同时在
                 编写查询语句时, 我们遍历了         CodeQL  中和  Go  语言语法树结点相关的文档, 并添加详尽的测试以确保查询语句
                                                                                                      [33]
                 的准确性. 另外, 由于   Go 语言的语法糖, 某些访存操作可能会被遗漏. 针对这些问题, 我们仔细查阅了                   Go 语言的规范 ,
                 并在统计相关操作时包含了语法糖所隐藏的操作. 例如在统计解引用操作*a 时, 除了统计显式解引用操作之外,
                 还统计了语法糖      a.f 中包含的隐式解引用操作.
                    在统计连续域访问和连续解引用操作时, 由于查询语句底层基于抽象语法树实现, QLStat 在对形如                              a.f.g  和
                 **p  这类具有语法结构递归嵌套的模式进行查询时, 未考虑别名关系. 因此, 形如                     x = a.f; y = x.g  以及  x = *p; y =
                 *x  等通过别名引入的连续域访问和连续解引用操作, 会被视为多个独立的单次域访问或者解引用操作. 为了缓解
                 这个问题, 需要利用      CodeQL  解析器生成的    SSA  表示  [68]  识别变量的定义-使用  (def-use) 关系, 从而捕获上述两个
                 例子中出现的别名, 进而识别含别名的连续域访问和连续解引用操作. 此外, 对于更复杂的别名情况, 例如                                  x =
                 &a.f, y = (*x).g, 由于 CodeQL  缺少  Go  语言的别名分析支持, 因此当前建立在     CodeQL  之上的  QLStat 仍无法统计
                 这类包含更复杂情况的连续操作模式. 这使得统计得到的连续操作数量相比于真实数量偏少.
                    由于  GitHub REST API 的限制, 每次  Issue 搜索最多只能获取      1 000  个  Issues. 这就导致本文无法获取到所有
                 的  Issues, 可能会影响统计结果. 然而, 统计数据显示, 达到         1 000  个  Issues 的仓库只有  17  个仓库, 因此基于此的统
                 计分析结果仍然是可靠的. 在分析总结问题模式时, 由于模式是人工总结并分类的, 因此存在分类不准确的情况.
                 此外, 使用代码检查工具检测         L2-3  模式时, 也存在一定的局限性和有效性威胁. 静态分析的结果无法完全反映程
                 序的实际运行状态, 且可能存在误报和漏报的情况. 由于                 slice 的别名问题、数据流、控制流的复杂性, 针对             L2-3
                 问题模式的代码检查工具难以实现完全准确的报告, 可能存在漏报误报的情况. 对此, 本工作在检测中已使用如
                 GVN、SSA  等方式分析别名情况, 以减少漏报, 同时通过检测在同一基本块中不存在对元素设置为                             nil 操作的约
                 束减少误报, 最终将报告数量控制在每个仓库最多                23  个报告, 中位数  2  个报告的范围内.
                    在统计   PR  修复时间间隔时, 本文将       PR  的关闭时间作为修复结束时间. 这可能无法真实反映合并时间. 如果
                 一个  PR  被合并, 则该  PR  的关闭时间、被合并的时间以及修复结束时间是一致的. 但是, 如果一个                      PR  没有被合
                 并就关闭了, 那么此时应假设         Issue 的关闭时间为修复结束时间. 因此, “使用          PR  的关闭时间作为修复结束时间”
                 引入的有效性威胁在于, 可能会导致对应             PR  没有被合并的    Issue 的修复时间被低估. 但是在大多数         PR  被合并的
                 条件下, 以  PR  关闭时间作为修复结束时间的统计仍具有参考价值.
                    ● 外部有效性威胁. 本文分析的仓库并不能完全代表现实世界                    Go  项目中的所有内存性能与安全问题. 然而,
                 本文选择过去     1  年内更新的 996  个项目以反映现实世界中仍然活跃的项目中的内存性能与安全问题. 另外, 由于
                 通过关键词在     GitHub  中搜索  Issues/PRs 只会搜索包含关键词的     Issues/PRs, 因此这些  Issues/PRs 并不能完全反映
                 内存安全问题的全貌, 在这些         Issues/PRs 上的分析也并不能完全覆盖所有潜在问题.
                    在人工分析     3  类关键词的   Issue 时, NPD  问题只分析了   30  条. 这可能导致  NPD  内存问题模式不足以反映真
                 实世界的情况. NPD     问题的数量相较于其他问题          Issue 更少的原因在于, NPD    问题的发生主要与应用程序的特征
                 有关. 部分应用程序的      NPD  问题的上下文较为复杂. 由于缺乏领域知识, 较难总结               NPD  问题的模式.

                  7   相关工作
                    ● 内存安全问题检测与修复. 根据谷歌发布的报告                [70] , 内存相关问题有  50  多年的历史, 通常会导致系统运行
                 崩溃或者带来安全风险. 例如内存泄漏、空指针解引用问题可能会被攻击者利用, 通过程序崩溃来进行拒绝服务
                 攻击  [71] . 至今还有很多研究在试图缓解内存问题带来的影响. 很多研究已经在                   C、C++、Python、Rust 等编程语
                 言的内存安全问题方面开展, 例如          C/C++程序的内存泄漏检测        [72–74] , C/C++程序的并发安全  [75] , Python  程序中的内
                 存泄漏问题    [76] , Rust 在语言层次设计的所有权    [77]  和借用机制以防止    double free 等问题, Rust 语言层次设计的生
                 命期标注   [78] 和  Drop  机制以及时释放资源, Rust 程序上的内存问题和并发安全实证研究              [79] 等. 相比之下, Go  语言
                 的显著特点是原生支持并发, 这使得许多             Go  语言相关的研究聚焦于并发安全问题. 相关工作包括对真实世界中
                 Go  语言项目存在的并发问题的实证分析            [80] , 通过程序验证  [81,82] 、静态分析  [8] 、模糊测试  [83]  方式检测和修复  Go
   252   253   254   255   256   257   258   259   260   261   262