Page 256 - 《软件学报》2026年第3期
P. 256
李清伟 等: Go 语言程序的内存性能与安全问题实证研究 1219
(1) 有其他对象引用被切对象
(1.1) 如果这个对象逃逸出函数作用域, 则报告错误.
(1.2) 否则按照没有对象引用被切对象的情况处理.
(2) 没有其他对象引用被切对象
(2.1) 如果切片表达式之后的执行路径可能到达 append 函数, 则报告错误.
(2.2) 如果没有 append 函数, 则报告正确.
上述标准中, 不同的报告有不同的原因. (1.1) 如果引用被切对象的对象 o 逃逸, 那么被切对象的生命期实际
上由 o 决定, 设置 nil 操作意义不明确, 报告错误. (2.1) 如果切片表达式后面的执行路径可能到达 append 函数, 那
么 slice 底层数组会扩容, slice 底层数组在进行扩容时会丢弃对原始数组的引用, 因此被切对象将被及时回收, 报
告存在错误. 这里只需要考虑切片的 append 操作. 这是因为 Go 语言规范 [33] 中定义的其他开发者可见的 slice 操作 (包
括索引表达式、切片表达式、copy 函数、clear 函数) 都只能在 slice 所定义的区间内进行修改、读取操作, 不会
让 slice 引用一个新底层数组并丢弃对旧底层数组的引用, 也就不会丢弃对被切对象引用, 从而可能引发内存泄漏,
需要被报告出来. (2.2) 如果切片表达式之后的执行路径不可能到达 append 函数, 那么 slice 底层数组不会扩容, 被
切对象仍然会被 slice 底层数组引用, 因此被切对象无法被及时回收, 报告正确.
虽然这 70 个报告中有较多的误报, 但是仍然有 6 个报告是正确的. 这表明代码检查工具具有实际效用, 同时
也表明 L2-3 模式在真实世界项目中依然存在, 且有进一步优化的空间. 此外, 人工分析的标准实际上可以作为改
进代码检查工具的参考, 通过引入指针分析、逃逸分析以及过程间控制流分析等分析技术加强对该问题的检查,
从而及时报告这类导致内存泄漏的代码模式. 但引入这些技术也会带来较高的代价. 这里的分析代价主要有几方
面因素构成. (1) 需要借助于逃逸分析, 这对于 CodeQL 来说是困难的. (2) 需要通过静态分析判断执行路径是否可
达 append 函数, 这需要过程间控制流分析的支持, 而过程间控制流分析可能引入新的不准确性以及更高的分析开
销. (3) 此外还需要添加对 append 模式的检查. 只有当 s4 = append(s5, _) 中的 s4、s5 和 s1 是相同的对象时, 对扩
容前底层数组的引用才会被丢弃, 此时代码检查工具报告程序不存在内存泄漏问题. 这需要通过指针分析判断 s4、
s5 和 s1 是否为相同的对象. 指针分析会带来更多的开销. 并且由于指针分析的精度限制, 可能无法识别出相同的
对象, 从而导致误报程序存在内存泄漏问题.
此外, 针对 6 个人工识别为正确的报告, 我们在 GitHub 上提交了 5 个 Issues, 并已收到 1 个肯定的回复 [69] . 问
题如代码 14 所示, 发生在删除列表迭代器往后遍历的过程中, 没有清除对 iter.objects[0] 所指向对象的引用, 从而
可能导致内存泄漏. 在该回复中, 仓库贡献者确认了设置 nil 并不会造成错误的影响, 在高负载情况下可能导致内
存泄漏. 这进一步表明, L2-3 问题模式在真实世界 Go 项目中仍然存在.
代码 14. aws-sdk-go 可能存在的内存泄漏问题.
1. // L2-3 aws/aws-sdk-go/issues/5327
2. // Next will use the S3API client to iterate through a list of objects.
3. func (iter *DeleteListIterator) Next() bool {
4. if len(iter.objects) > 0 {
5. iter.objects = iter.objects[1:]
6. }
...
7.
8. return len(iter.objects) > 0
9. }
6 有效性讨论
● 内部有效性威胁. 在使用 QLStat 统计不同模式的分布时, 由于语法树结点的多样性, 可能出现漏扫漏查或

