✨ 要点🔬 技术摘要
想象一下,运行你手机、汽车或互联网的软件就像一座巨大的、隐形的工厂。在这座工厂内部,有数百万个微小的工人(代码行),他们将你的高层指令转化为计算机能理解的机器语言。这座工厂被称为编译器 。如果哪怕只有一个工人偷懒、困惑或违反了规则,整个工厂都可能生产出垃圾产品——即崩溃、冻结或在暗中做错事的程序。由于这些工厂如此庞大且复杂,要确保每一个工人都被监视和测试是非常困难的。
于是,进入了软件测试 的世界。你可以把它想象成派出一支检查员队伍,来看看工厂是否运转正常。多年来,检查员一直使用两种主要技巧:要么向工厂投掷随机飞镖(随机测试),要么试图通过扭曲现有的指令来破坏事物(变异测试)。但问题在于,检查员总是击中工厂中那些容易触及的角落,而留下那些布满灰尘、黑暗且难以到达的后方房间无人问津。这些“盲点”正是最危险的漏洞(bug)隐藏的地方,它们正等待着制造麻烦。最近,科学家开始使用大语言模型(LLMs) ——能够编写代码的超智能人工智能——来协助检查工作。但即使是这些 AI 助手也经常漫无目的地游荡,不知道究竟哪些黑暗角落最需要光照。
这就是名为 GapForge 的新技术发挥作用的地方,它就像一个带着魔法地图的侦探。GapForge 不仅仅是投掷飞镖或靠猜测,它会观察工厂地图,看清哪些房间从未被造访过。然后,它利用自己的 AI 大脑,去推算开启那些锁着的门所需的特定“暗号”(一种特定的代码类型和特殊的设置)。研究人员在世界上两个最大的编译器工厂 GCC 和 LLVM 上测试了它。他们发现 GapForge 是寻找这些隐藏房间的高手。在短短 72 小时 内,它覆盖了 68.13% 的 GCC 核心代码和 69.11% 的 LLVM 代码。这听起来可能达不到 100%,但请记住,之前的最佳 AI 方法仅能达到约 64.62% 和 65.02% 。GapForge 不仅仅是小幅提升了数字;它还在 GCC 中找到了 24,736 行、在 LLVM 中找到了 19,798 行其他方法完全遗漏的代码。
它是如何做到的呢?把 GapForge 想象成一个三步走的流程。首先,它扫描工厂地图并挑选出“最脏”的房间——即拥有最多未测试代码的那个房间。第二,它不再只是观察整个房间,而是放大到具体的落尘点,并询问 AI:“我们需要什么样的钥匙来打开这里?”AI 会分析周围干净区域的情况,以此来推测达到该点所需的代码结构和工厂设置(比如打开某个特定的开关)的具体组合。第三,如果 AI 尝试了一把钥匙却没起作用,GapForge 会记住这次失败。它会告诉 AI:“不要再试那个了,试试别的,”然后带着更好的计划将它送回。这个循环不断重复,稳步地照亮每一个黑暗的角落。
结果令人印象深刻。GapForge 不仅比其他八种顶尖技术覆盖了更广的范围,还发现了 12 个隐藏在阴影中的真实世界漏洞 。其中包括 8 个崩溃 (编译器直接放弃并停止运行)和 4 个错误编译 (编译器静默地构建了一个看起来完美但实际上已损坏的程序)。研究人员证明,GapForge 过程中的每一个部分都至关重要:如果移除“地图阅读”、“钥匙猜测”或“从失败中学习”,结果都会显著变差。其他方法忙于生成数以千计的测试程序,而 GapForge 生成的程序虽然较少,但却更加聪明,这证明了在软件测试的世界里,质量和方向往往比单纯的数量更重要。
技术摘要:GapForge
问题陈述
现代编译器代码库(如 GCC 和 LLVM)具有极大的规模和复杂性,这使得实现全面的代码覆盖率变得非常困难。现有的测试生成技术——从基于语法和基于变异的方法到近期的大语言模型(LLM)方法——主要以“程序驱动”的方式运行。它们侧重于生成多样化的输入或触发 Bug,而没有明确建模哪些特定的编译器区域被执行。因此,这些方法往往会重复执行频繁触发的“热点”路径,从而在未充分测试的文件和难以触及的边缘区域留下大量的“长尾”覆盖率缺口。虽然像 WhiteFox 这样的白盒技术尝试通过总结优化代码来解决这一问题,但它们通常依赖于粗粒度的文件级总结,无法提供到达特定未覆盖区域所需的细粒度指导,特别是那些需要特定编译选项或程序结构的区域。
方法论:GapForge
GapForge 是一种针对性的、基于 LLM 的编译器测试生成技术,旨在通过对覆盖率缺口进行推理,系统性地提高源代码覆盖率。与通用模糊测试器不同,GapForge 将未覆盖的代码区域视为显式目标,并通过一个三步流程迭代生成能够执行这些特定缺口的测试程序。
1. 覆盖率驱动的目标选择
为了应对数千个源文件之间不均衡的覆盖潜力,GapForge 采用了概率选择机制而非随机采样。
探索得分: 每个文件 f f f 被分配一个得分 S f = L f × ( 1 − C f ) 2 S_f = L_f \times (1 - C_f)^2 S f = L f × ( 1 − C f ) 2 ,其中 L f L_f L f 是行数,C f C_f C f 是当前的覆盖率比例。二次项放大了低覆盖率文件的优先级。
选择概率: 为了防止高分文件无限期地主导选择过程,得分经过归一化并转换为选择概率 P f = 1 − ( 1 − W f ) k P_f = 1 - (1 - W_f)^k P f = 1 − ( 1 − W f ) k 。这种非线性变换确保了在偏向高潜力文件的同时,低潜力文件仍有机会被选中,从而平衡了探索与利用。
2. 针对性总结
GapForge 不会对整个文件进行总结,而是对特定的未覆盖行跨度进行细粒度分析。
上下文配对: 对于每个未覆盖区域,系统将其与其紧邻的“已覆盖上下文”(即缺口周围的代码)进行配对。
路径差异分析: LLM 分析已覆盖上下文与未覆盖目标之间的对比。它推断出:
功能角色: 该文件在编译器流水线中的高层用途。
触发要求: 需要什么样的特定程序结构(例如数据类型、控制流模式)才能引导执行进入未覆盖块。
编译选项: 激活该代码路径所需的特定标志(例如 -fsanitize、-fdump-ada-spec)。
输出: 此过程产生结构化的“目标要求”,为下一步的生成步骤提供精确指令。
3. 带有失败反思的提示词合成
GapForge 通过整合三个组件来合成用于测试生成的结构化提示词:
通用约束: 确保生成的程序有效、确定,且具有足够的结构复杂度以触发深层的编译器路径。
目标要求: 从针对性总结步骤中得出的特定目标和约束。
失败反思: 一个关于先前失败提示词(即导致编译错误或未能覆盖新区域的提示词)的逐文件日志。系统会检索最近的一个失败案例,并指示 LLM 避免生成结构相似的程序,从而创建一个迭代优化循环,引导生成过程远离无效策略。
核心贡献
GapForge 框架: 一种基于 LLM 的技术,通过覆盖率驱动的文件选择、细粒度区域分析(结合未覆盖代码与已覆盖上下文)以及失败感知提示词合成,显式地针对覆盖率缺口进行测试。
细粒度推理: 不同于以往将文件整体进行总结的白盒方法,GapForge 能为单个未覆盖的基本块推断出特定的触发要求(程序结构和编译选项)。
实证验证: 在 GCC 14.3.0 和 LLVM 19.1.0 上进行的广泛实验表明,其性能显著优于最先进的基准方法。
可复现性: 提供了一个包含该技术源代码和数据的公开软件包。
实验结果
作者在 72 小时内,在 GCC 和 LLVM 上将 GapForge 与八种最先进的技术(包括 Csmith、WhiteFox、LegoFuzz 和 Fuzz4All)进行了对比评估。
覆盖率提升: GapForge 在 GCC 核心模块上实现了 68.13% 的覆盖率,在 LLVM 核心模块上实现了 69.11% 的覆盖率。这超过了之前的最先进白盒技术(WhiteFox),在 GCC 中多覆盖了 24,736 行 ,在 LLVM 中多覆盖了 19,798 行 。
增量收益: GapForge 通过为 GCC 增加 3,452 个新覆盖行和为 LLVM 增加 531 个新覆盖行,改进了编译器官方测试套件,而若干基准方法在官方套件上的新增行数为零。
效率: 与 WhiteFox(2.30M tokens)和 LegoFuzz(3.63M tokens)相比,GapForge 以显著更低的 Token 消耗(GCC 为 976K tokens)实现了这些结果。
缺陷发现: 该技术发现了 12 个真实的编译器故障 (GCC 中 5 个,LLVM 中 7 个),包括 8 个崩溃(crash)和 4 个误编译(miscompilation)。
消融实验: 移除三个核心组件中的任何一个(目标选择、针对性总结或失败反思)都会导致可衡量的性能下降,证实了每个模块的必要性。具体而言,缺失针对性总结或编译选项建议会导致覆盖率出现最显著的下降。
重要性
论文指出,GapForge 解决了当前编译器测试中的一个关键局限:现有技术无法系统性地触及“长尾”覆盖率缺口。通过从“程序驱动”转向“覆盖率缺口驱动”,GapForge 提供了一种显式推理如何到达特定未测试代码区域的机制。结果表明,将覆盖率反馈与细粒度 LLM 推理相结合,可以更高效、更有效地测试像编译器这样的大规模、复杂软件系统,最终提高可靠性,并发现传统模糊测试方法难以察觉的微妙缺陷。
每周获取最佳 computer science 论文。
受到斯坦福、剑桥和法国科学院研究人员的信赖。
请查收邮箱确认订阅。
出了点问题,再试一次?
无垃圾邮件,随时退订。