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

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


                 性, 因而自动检测的难度较大.
                    接下来, 将介绍本文如何处理         L2-3  问题模式.
                  5.2   内存问题自动检测工具实践
                    根据  Staticcheck  的规则说明、 go vet 的帮助文档、golangci-lint 的  linter 文档以及其他代码检查工具的功能
                 描述, 目前并没有现成的规则用于检测             L2-3  模式. 由于该模式形式简单, 修复的代价较小, 同时可能造成内存泄
                 漏, 因此我们认为有必要对其进行检测. 本文基于              QLStat 实现了对   L2-3  问题模式的自动检测工具, 并在评测中对
                 966  个项目数据库进行了检查, 最终报告了          574  处问题. 在  574  处问题中, 人工审查了    70  个问题, 最终发现   6  个可
                 能正确的报告, 并通过      GitHub Issue 进行了反馈, 最终有   1  个问题被开发者确认为可能存在内存泄漏的问题.
                  5.2.1    设计与实现
                    图  7  展示了  L2-3  问题模式更加一般化的形式, 其中左半部分为正确的代码模式, 代码检查工具不应该报告;
                 右半部分为可能存在内存泄漏问题的代码模式. L2-3               问题模式的本质原因在于, 切片表达式            s2[start:] 中的被切部
                 分  s2[0:start] 所引用的对象  (即被切对象) 由于始终被       s2  的底层数组所引用, 导致无法被及时回收. 如果在             s3 =
                 s2[start:] 赋值之前, 可以将  s2[0:start] 及时设置为 nil, 就可以避免被切对象的回收需要延迟到           s2  底层数组被回收
                 的时刻.

                                                         Constraints
                                                  ① equal(s1, s2, s3)   ② start > 0  ^
                                                              ^
                                             ③ equal(type(s1), slice(pointer(T)))   é s1 [...] = nil
                                                                   ^
                                                  s1 [...] = nil  s1 [...] = nil
                                                                ④ Missing
                                                  s3 = s2 [start:]  s3 = s2 [start:]



                                                 图 7 L2-3 代码检查工具规则

                    根据上述原因, 为了检测图         7  右半部分的模式, 本文设计了相应的代码检查工具. 其使用的约束如图                    7  中数字
                 标号标识的约束所示: (1) s1、s2、s3      这  3  个  slice 值应该相等, 即使用相同的   slice; (2) 切片开始位置  start 应该大
                 于 0; (3) s1  切片的底层元素类型应该为指针类型; (4)       s1[...] = nil 和  s3 = s2[start:] 不应该同时出现. 在具体实现中,
                                                      [68]
                 针对约束   1, 采用了全局值编码 GVN  以及        SSA  的特性, 以尽可能多地识别运行时值相同的表达式. 针对约束                  4,
                                             [67]
                 采用“是否出现在相同基本块中”作为分析粒度.
                    利用以上约束, 代码检查工具在检测代码             13  时不会报告这段代码有问题. 这是因为在同一基本块中存在对元
                 素  x[0] 设置  nil 的操作, 这是正确的做法. 另外, 代码检查工具也识别了           x  和  a.f 在运行时是相同的值, 减少了误报
                 现象的发生.

                 代码  13. L2-3: 应消除的误报示例.

                 1. x := a.f
                 2. x[0] = nil
                 3. a.f = a.f[start:]

                  5.2.2    效果评估
                    在实现上述工具后, 本文使用该代码检查工具对                966  个项目数据库进行检查, 并最终报告了          574  处问题, 平均
                 每个仓库报告了      0.59  个问题.
                    为了进一步评估工具的准确率, 在这些报告中, 我们人工审查了                    70  个报告. 发现其中有    6  个为可能正确的报
                 告, 有  64  个为错误的报告. 在人工审查时, 我们制定了如下评估标准.
   250   251   252   253   254   255   256   257   258   259   260