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 个为错误的报告. 在人工审查时, 我们制定了如下评估标准.

