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 统计不同模式的分布时, 由于语法树结点的多样性, 可能出现漏扫漏查或
   251   252   253   254   255   256   257   258   259   260   261