ContrastRepair:通过对比测试用例对增强基于对话的自动化程序修复

基本信息

ACM Transactions on Software Engineering and Methodology 2025

博客贡献人

谷雨

作者
JIAOLONG KONG, XIAOFEI XIE, and MINGFEI CHENG, Singapore Management University,
Singapore, Singapore
SHANGQING LIU, Nanjing University, Nanjing, China
XIAONING DU, Monash University, Melbourne, Australia
QI GUO, Tianjin University, Tianjin, China
标签

Program Repair, Large Language Model

程序修复,大语言模型

CCS 分类概念

软件与软件工程 → 软件测试与调试

摘要

        自动化程序修复(APR)旨在自动生成补丁修复软件漏洞,基于大语言模型(LLM)的对话式 APR 是当前研究热点,但其效果高度依赖反馈信息质量。本文提出ContrastRepair,一种新型的基于对话的 APR 方法,通过为 LLM 提供对比测试用例对(失败测试用例 + 高度相似的通过测试用例)补充正负反馈,精准隔离漏洞根因,提升修复效果。该方法以 ChatGPT 为基础模型,通过最小化失败与通过测试用例的差异构建对比对,若无现成相似通过用例则通过类型感知的变异策略生成,并结合漏洞函数的依赖上下文构建提示词,与 LLM 迭代交互直至生成有效补丁。在 Defects4J、QuixBugs、HumanEval-Java 三大基准数据集上的评估表明,ContrastRepair 显著优于现有 APR 方法,创下程序修复新的最优性能 —— 在 Defects4J 1.2 和 2.0 的 337 个漏洞中成功修复 143 个,相较最优基线提升 15.32%,同时平均减少 20.91% 的 LLM API 调用,修复效率大幅提升。消融实验验证了对比测试用例对的相似度筛选、依赖上下文信息对修复性能的关键作用。

1 引言

        随着软件复杂度的不断提升,漏洞和缺陷的存在已成为必然现象。这些问题可能导致系统故障、安全漏洞,还会降低用户体验。人工调试和修复此类问题耗时费力,需要开发人员投入大量的资源和精力。有数据显示,全球每年用于漏洞检测和修复的支出高达数十亿美元,开发人员约 50% 的工作时间都投入到调试和修复错误这一关键过程中。在此背景下,自动化程序修复(APR)成为一种极具前景的解决方案,能够自动生成补丁以修复软件漏洞。

        近年来,自动化程序修复领域的研究成果颇丰。传统的自动化程序修复技术可分为基于模板、基于启发式和基于约束三类方法。然而,这些传统方法的效果仍不尽如人意。例如,基于模板的自动化程序修复需要人工设计模板,耗费大量人力且对领域知识要求较高,因此泛化能力有限,仅对其设计针对的特定类型漏洞修复效果较好。

        近年来,机器学习技术,尤其是基于深度学习的自动化程序修复方法逐渐成为研究热点。这类深度学习模型的优势在于,能从海量数据中学习各类漏洞问题的模式,性能优于传统方法。尽管如此,基于深度学习的自动化程序修复技术仍面临诸多挑战。其中一个关键问题是对训练数据的依赖:若训练数据中未包含某些类型的漏洞,模型仍难以对未见过的漏洞实现有效泛化。此外,构建用于训练深度学习模型的完备漏洞修复数据集,需要投入大量的精力和资源。同时,尽管基于深度学习的方法在自动化程序修复中展现出潜力,但其效果仍存在局限,往往会生成大量候选补丁,导致验证过程耗时,这也成为该技术落地应用的重大障碍。

        为克服上述挑战,近年来的研究中出现了一种极具前景的方法 —— 利用大语言模型(LLM)。大语言模型基于海量数据集训练而成,包括大规模代码语料库,且在各类任务中均展现出卓越的性能。其性能出众的核心原因,在于具备强大的程序语义理解能力。在自动化程序修复领域的相关研究表明,即便不进行微调,大语言模型的修复能力也能与传统的基于深度学习的自动化程序修复技术相抗衡,而在相关数据上对大语言模型进行微调后,其修复能力还能得到显著提升。

        此前,大部分相关研究仅单独调用大语言模型,并未融入对话机制。随着 ChatGPT 等基于对话的大语言模型的发展,近期基于 ChatGPT 的对话式自动化程序修复方法取得了全新的最优性能。该方法的核心思路是以对话的形式生成补丁,在每一轮对话中,系统将之前生成的错误补丁与测试失败信息相结合,提示大语言模型生成新的补丁。这种对话式自动化程序修复方法在引导大语言模型提出更有效的修复方案方面,表现出优异的性能。

        本文主要研究大语言模型在对话式自动化程序修复中的应用。将大语言模型应用于该场景的核心挑战,在于设计高质量的提示词 —— 提示词在引导大语言模型理解程序并进而修复漏洞的过程中,发挥着关键作用。尽管近期的研究已证明大语言模型在该领域的应用潜力,但现有方法主要依靠为对话提供错误反馈信息,而这类反馈往往无法为有效修复提供具体、有信息量的提示。

        为此,本文提出一种方法,旨在设计更具体、更具信息量的提示词,提升大语言模型精准定位漏洞并生成高质量修复方案的能力。我们的核心研究发现是,仅依靠失败测试产生的负面反馈(即错误信息),往往不足以让大语言模型精准定位漏洞。因此,本文提出融入通过测试产生的正面反馈,对负面反馈进行补充。通过提供对比输入对,将两类测试的结果并列呈现,大语言模型能更有效地定位漏洞的根本原因。

        本文提出一种全新的对话式方法 ——ContrastRepair,该方法通过融合正负反馈生成提示词。给定一个失败测试用例和存在漏洞的函数,我们生成一个与该失败用例高度相似的通过测试用例,组成对比对并输入大语言模型。尽管本方法具有通用性,但在本研究中,我们选择 ChatGPT 作为实验模型,原因是其在处理对话式交互方面表现出当前最优的性能。通过提供这类成对信息,大语言模型能获得更具体、更有信息量的线索,从而更好地定位漏洞的根本原因并生成准确的修复方案。

        然而,本方法面临一个挑战:如何从大量通过测试用例中,选择合适的用例与失败用例配对。我们的核心思路是,通过最小化失败测试用例与通过测试用例之间的差异构建测试对,如此一来,导致二者一个失败、一个通过的差异点,就能精准定位并分离漏洞成因。具体而言,我们提出一种方法,通过对失败测试用例进行最小化修改,生成对应的通过测试用例。若已有一批通过测试用例,则从其中选取与失败用例最相似的一个。在该过程中,我们设计了一种基于编辑距离的度量指标来计算相似度得分,并设置阈值以判定是否保留构建的测试对。

        对于无现成通过测试用例的漏洞,我们设计了一系列变异策略,对失败测试用例进行变异以生成新的通过测试用例,再将失败测试用例与这些新生成的通过测试用例进行配对。此外,部分漏洞与其他函数存在强依赖关系,这也是修复过程中的重要上下文信息。我们通过从失败测试用例的回溯信息中筛选相关函数,将这类上下文信息融入修复过程。

        最终,输入大语言模型的提示词包含:存在漏洞的函数、相关依赖函数、测试对以及失败测试用例的回溯信息。大语言模型根据该提示词输出补丁函数,若生成的补丁被判定为无效,我们将采用类似的方法迭代生成下一个补丁。这一迭代过程将持续进行,直至生成能通过所有测试的合理补丁,或耗尽修复预算。

        我们在三个不同的数据集(Defects4J、QuixBugs 和 HumanEval-Java)上对 ContrastRepair 进行了评估,同时将其性能与当前最优的方法对比,包括基于学习的自动化程序修复方法和以 CHATREPAIR 为代表的对话式自动化程序修复方法。评估结果清晰表明,ContrastRepair 的性能显著优于基线方法:该方法成功修复了 581 个漏洞中的 360 个,而当前最优的基于 ChatGPT 的方法 CHATREPAIR 仅修复了 334 个。此外,研究发现,与 CHATREPAIR 相比,ContrastRepair 的 API 调用效率显著提升,平均调用次数减少 20.91%。同时,消融实验也证实,测试对的选择和上下文信息的融入,对提升 ContrastRepair 的性能具有重要意义。

综上,本文的主要贡献如下:

  1. 提出一种全新的自动化程序修复方法,以对话式方式利用大语言模型,通过融合正负反馈,让大语言模型能够生成高质量的修复补丁。
  2. 提出一种构建并优先选择合适测试对的方法,该测试对由失败测试用例和对应的通过测试用例组成,能让输入大语言模型的提示词更具信息量,助力精准的漏洞定位和更有效的补丁生成。
  3. 对该工具的有效性进行了全面评估,结果表明 ContrastRepair 在正确修复漏洞的数量上达到了新的最优水平,相较于最优基线方法提升了 15.32%,性能超越现有所有自动化程序修复工具。所有实验数据和源代码均已公开。

2 示例验证

图 1. 一个激励性示例

-我们通过全面评估验证了该工具的有效性。实验结果表明,ContrastRepair在错误修复方面取得了新的前沿性能,其正确修复率较最佳基线提升15.32%,超越现有APR工具。所有实验数据及源代码均已公开。

        图 1 展示了一个示例验证案例(来自 Defects4J 的 Lang-16 漏洞),包含一个存在漏洞的函数和一组用于评估该函数的测试用例。通过输入不同类型的测试用例,我们展示了 ChatGPT 的修复结果,包括修复后的代码及修复说明。具体而言,createNumber 函数的功能是解析给定的字符串 str,并返回对应的 Number 对象。该函数代码中存在一个细微的漏洞:当 str 为十六进制整数的字符串时,函数能正确处理以小写 “0x” 或 “-0x” 开头的情况,却忽略了大写开头的输入,导致对边界情况的处理不完整。因此,当用三个测试用例对 createNumber 函数进行测试时,T2 和 T3 测试通过,而 T1 测试失败。

        在修复该程序时,修复人员需要先定位存在漏洞的代码行,再针对性地解决这一被忽略的边界情况。尽管 ChatGPT 对程序语义具备良好的理解能力,但为其提供额外的任务引导仍能提升修复效果。虽然提供失败测试用例 T1 能为修复过程提供线索,缩小漏洞代码行和潜在修复方案的搜索范围,但仅依靠失败测试用例的作用有限,因此需要更有效的引导以实现更好的修复效果。

        另一方面,若为修复人员提供设计合理的对比测试对 <T1, T3>(如 “-0Xfade” 和 “-0xfade”),能大幅降低修复人员的推理难度,使其轻易发现字符 “X” 和 “x” 可能是导致漏洞的关键因素,进而精准定位处理该问题的漏洞代码行,并提出合适的修复方案。这一案例表明,融合正负反馈的对比信息,能为程序修复提供更精准的引导,让修复过程更高效、更有效。

        但需要注意的是,并非所有测试对都能为修复过程提供有价值的引导,部分测试对的对比引导可能间接甚至存在错误。例如,测试对 <T1, T2>(即 “-0Xfade” 和 “0xfade”)可能会让 ChatGPT 错误推断符号 “-” 是漏洞成因,从而对修复产生误导。因此,如何更好地选择和设计对比测试用例对,是保证修复引导有效性的关键。

3 方法

3.1 ContrastRepair 整体框架

图 2. ContrastRepair概述

        图 2 展示了 ContrastRepair 的整体框架,该方法本质上是一个对话过程:先利用对比测试用例对构建提示词,将其输入 ChatGPT 等大语言模型,再根据模型返回的结果生成修复后的代码。

        具体而言,在对话的每一轮迭代中,ContrastRepair 的输入为待验证的程序,以及包含多个测试用例的测试套件,初始输入的程序为存在漏洞的代码。首先,ContrastRepair 利用测试套件对程序进行评估:若所有测试均通过,则得到合理的补丁,并由人工进一步验证;若存在触发程序漏洞的失败测试用例 f,则捕获对应的回溯日志 T_f,同时收集程序能正确处理的所有通过测试用例 P,为后续的测试对构建做准备。

        接下来,ContrastRepair 构建包含失败测试用例 f 和其他通过测试用例的测试对。为保证这些测试对能提供有效的对比引导,我们提出一种基于相似度的选择方法。受 Delta 调试思想的启发 —— 该方法通过最小化失败测试用例来分离漏洞成因,我们选择与失败测试用例差异最小的通过测试用例,让 ChatGPT 能通过对比二者的细微差异,分离漏洞成因。若不存在与 f 高度相似的现有测试用例,我们则引入一种类型感知的测试变异技术,基于失败测试用例的类型生成符合要求的通过测试用例,尽可能减少对失败测试用例的变异,从而增强有助于分离漏洞成因的对比信息。

        随后,我们遵循业内最佳实践构建 ChatGPT 的提示词。为收集漏洞的依赖信息,我们从失败测试用例的回溯信息中识别出依赖函数。最终的提示词包含:失败测试用例 f 的回溯日志、构建的测试对、依赖函数、目标漏洞函数以及修复需求说明。ChatGPT 根据该提示词生成修复后的代码,并在后续迭代中对其进行验证。这一迭代过程将持续至生成合理的补丁,或达到修复预算上限(如最大迭代次数)。

3.2 对比测试用例对的构建

        程序调试的一个常识是:“遇到漏洞的开发人员,往往会花费大量时间研究,对输入文件做出哪些修改能消除漏洞,哪些修改则不会产生影响”。能消除漏洞的修改被称为关键修改,这类修改包含理解漏洞根本原因的重要线索,是实现精准的漏洞定位和有效修复的关键。因此,本文的研究目标是,识别这类关键修改信息并提供给大语言模型。

        为让大语言模型能清晰捕捉到这些关键修改,本文提出一种构建对比测试用例对的方法,该测试对由一个失败测试用例和一个对应的通过测试用例组成。我们确保失败测试用例和通过测试用例具有高度的相似性,让大语言模型能通过对比二者导致漏洞触发的细微差异,推导出关键修改。

        具体而言,构建对比测试用例对的步骤为:首先构建通过测试用例集 P,再从 P 中筛选出与给定失败测试用例 f 相似度较高的合适通过测试用例 S,即 S={p | p∈P ∧ δ(f, p)>θ}。其中,δ 为失败测试用例 f 与通过测试用例 p 之间的相似度度量指标,θ 为预设的阈值。该过程需要解决两个核心问题:相似度 δ 的度量方法,以及通过测试用例集 P 的构建方法。

3.2.1 相似度度量(δ)

        可用于度量相似度的指标有多种,但由于本文的核心目标是挖掘失败测试用例中存在的关键修改,基于字符串的相似度度量指标与该任务的契合度更高。因此,本文选择达默 - 莱文斯坦距离作为相似度度量指标。达默 - 莱文斯坦距离通过量化将一个字符串转换为另一个字符串所需的最少操作次数,来衡量两个字符串的相似度,该指标包含插入、删除、替换和换位四种编辑操作。达默 - 莱文斯坦距离越小,表明两个测试用例的相似度越高,更有助于大语言模型分离漏洞的根本原因。

        我们将相似度得分归一化至 (0,1) 区间,计算公式为:

        其中,n 为测试用例中参数的数量,arg_{i}表示第 i 个参数,d_{i}为失败测试用例 f 和通过测试用例 p 中第 i 个参数的距离,len () 用于计算转换为字符串类型后的参数长度。

图1. 节选

        以图 1 中的测试用例为例,该漏洞函数仅有一个参数,因此 n=1。失败测试用例 T1 的参数值为 “-0Xfade”,通过测试用例 T2 和 T3 的参数值分别为 “0xfade” 和 “-0xfade”。经计算,T1 与 T2 的达默 - 莱文斯坦距离为 2,T1 与 T3 的达默 - 莱文斯坦距离为 1,因此可得 δ(T1, T2)=1-2/7=5/7,δ(T1, T3)=1-1/7=6/7。

        需要说明的是,测试用例的参数可能为不同类型,因此在利用达默 - 莱文斯坦距离计算相似度前,我们将非字符串类型的测试参数转换为字符串表示形式。对于对象或数组类型,我们将其每个元素递归转换为对应的字符串表示形式后,再计算相似度。

3.2.2 通过测试用例集(P)的构建

        本文采用两种策略收集通过测试用例:利用现有通过测试用例、生成新的通过测试用例。Defects4J 等众多项目中都包含单元测试,用于验证单个函数的正确性。具体而言,我们将测试用例视为漏洞函数的输入,通过 Java 工具库 Javassist 在漏洞函数的起始位置植入插桩代码,当开发人员提供的原始测试驱动运行测试套件中的每个单元测试时,即可捕获参数值。我们运行所有单元测试,收集每个函数对应的通过测试用例。

        若部分函数无单元测试,或现有单元测试与失败测试用例 f 的相似度较低,我们则提出一种类型感知的变异技术,基于失败测试用例 f 生成新的通过测试用例,且尽可能减少对 f 的修改。在对变异生成的测试用例进行验证时,我们同样通过对漏洞函数植入插桩代码,将原始参数替换为变异后的参数。

类型感知的变异

        类型感知的变异包含两个核心部分:通用的基于字符串的变异和针对特定类型的变异。由于相似度基于字符串表示形式计算,我们首先将失败测试用例 f 转换为字符串表示形式,再进行字符串层面的变异,并限制变异的程度,最后基于 Python 工具库 javaobj,将字符串变异后的结果转换回原始类型。同时,我们采用针对特定类型的变异策略,提升生成测试用例的多样性。具体的变异策略如下:

  1. 字符串变异:随机字符替换、随机子串替换、随机字符插入、随机字符删除、子串换位、大小写转换、截断 / 扩展。
  2. 整数、双精度浮点数、单精度浮点数变异:随机扰动(增加或减少一个小随机值)、缩放(乘以一个比例因子)、符号翻转(正负转换)、幅度扰动(增加或减少数值幅度的一个小百分比)。
  3. 字符变异:随机替换(将原始字符替换为随机选择的其他字符)。
  4. 布尔值变异:取反(对布尔值进行非运算)。
  5. 对象变异:基于上述基本类型的变异策略,对对象元素进行逐元素变异。
  6. 数组、列表变异:逐元素变异、元素换位、元素插入、元素删除、元素打乱。

        由于函数可能包含多个参数,我们将每个参数视为对象的一个元素或属性,进而将参数集视为对象类型的测试用例,以此实现对象层面的变异和相似度对比。

测试预言机

        由于缺乏测试预言机,收集通过测试用例的过程存在挑战,对于逻辑漏洞而言更是如此。我们将漏洞分为两类:异常漏洞逻辑漏洞。其中,异常漏洞可根据是否抛出异常确定测试预言机,而逻辑漏洞仅能通过显式的断言捕获。对于异常漏洞,我们采用类型感知的变异生成通过测试用例;而对于逻辑漏洞,本文主要依靠现有的基于断言的测试用例作为测试预言机。若不存在现成的通过测试用例,则仅使用单个失败测试用例而非测试对进行修复。将现有解决方案整合并探索应用于逻辑漏洞预言机构建(如构建蜕变关系),将作为本文的后续研究方向。

        算法 1 展示了对多参数进行变异以生成通过测试用例的流程,该算法的输入为:失败测试用例 f、测试用例中的参数总数 m、最大变异次数预算;输出为:按相似度排序的通过测试用例集。核心思路为:随机选择 1 至 m 个参数(n),对其值进行变异(第 4-7 行)。具体而言,对每个选中的参数,采用上述类型感知的变异策略生成新的测试用例,并设置变异预算作为最大允许的变异次数上限,以限制变异程度。完成变异后,计算变异用例集 MCases 中每个用例与失败用例 f 的相似度(见 3.2.1 节,第 8 行),最后过滤掉失败用例,将剩余的通过测试用例按相似度得分排序(第 9 行)。

3.3 对话式修复

        修复过程采用迭代方法,通过与大语言模型交互,逐步修复存在漏洞的函数。修复过程中可采用两种核心策略:

  1. 基于前序补丁继续修复:在每一轮迭代中,基于上一轮生成的仍未正确修复漏洞的补丁,继续开展修复工作。该方法能基于之前的修复尝试不断优化,利用大语言模型的持续反馈逐步完善补丁,但存在修复方向偏差的风险,可能导致补丁质量持续下降。
  2. 从原始漏洞函数重启修复:在每一轮迭代中,重新从原始的漏洞函数开始修复。该方法能让每一次修复尝试都从无偏差的初始状态开始,降低前序迭代错误叠加的风险,但同时也会丢失有助于补丁优化的持续反馈信息。

        为平衡上述两种策略的优劣,我们对连续修复的深度设置上限:若在指定的最大深度内仍无法修复函数,则从原始漏洞函数重启修复,并选择不同的测试对。该方法能探索多种修复路径,提升生成准确补丁的概率。

        算法 2 展示了 ContrastRepair 的整体修复流程,该算法的输入为:漏洞函数、用于评估函数正确性的测试用例集 α;输出为:若修复成功,返回合理的补丁。首先,ContrastRepair 通过运行所有可用测试,收集所有失败测试用例和通过测试用例(第 1 行)。若通过测试用例数量不足,或与失败测试用例的相似度较低,Collect 函数会通过类型感知的变异为异常漏洞生成额外的通过测试用例。随后,筛选出所有相似度达标的对比测试用例对(第 2 行)。修复过程同时包含从原始代码重启修复(第 4 行)和基于前序补丁连续修复(第 8 行)两种模式,修复尝试的总次数限制为 m×n(m 为最大重启修复次数,n 为最大连续修复次数)。

        在每一轮重启修复的迭代中,选择 k 个测试对用于构建提示词(第 5 行)。SelectPair 函数的设计思路是,根据测试对的历史选择频率进行优先级排序,将被选择次数较少的测试对设为高优先级,避免同一测试对被过度选择。针对选中的测试对 ρ,算法在 n 次尝试内开展连续修复。k 个测试对中的失败测试用例 \rho _{f},其漏洞成因可能相同也可能不同,我们运行所有失败测试用例以收集漏洞回溯日志,并去除冗余的重复信息,减少输入大语言模型的令牌数量(第 9 行)。

        若漏洞与其他函数存在依赖关系,ContrastRepair 会从回溯日志中识别出依赖函数,将其整合为依赖函数集 D(第 10 行)。随后构建提示词(第 11 行),包含:漏洞函数 tmp、测试对 ρ、回溯信息 t、依赖函数 d 以及具体的修复需求。将该提示词输入 ChatGPT,得到修复后的函数(第 12 行)。

        利用所有测试用例对补丁进行评估(第 13 行):若所有测试均通过,则生成合理的补丁。参考现有研究方法,我们让 ChatGPT 基于已生成的合理补丁,生成更多备选的合理补丁,避免生成的补丁虽未完全正确但已接近正确结果的情况(第 15 行)。若测试未通过,则基于当前修复后的函数及其对应的测试对,继续开展修复工作(第 17-19 行)。若在给定预算内修复失败,则返回原始的漏洞函数。

提示词的构建

        提示词的构建是有效引导 ChatGPT 进行程序修复的关键步骤。遵循业内最佳实践,我们在提示词中融入核心要素,为 ChatGPT 提供上下文、任务描述和输出约束。我们通过 ChatGPT 网页版对多种备选提示词进行了人工测试,并最终选择了效果最优的版本。

        如图 3 所示,提示词的开头为 ChatGPT 设定上下文,将其定义为 “Java 程序修复专家”;随后加入需要修复的漏洞函数代码,以及所有构建完成的对比测试用例对;再加入失败测试的回溯信息中包含的测试失败诊断结果,为漏洞修复补充更多上下文和线索;若漏洞与其他函数存在依赖关系,则加入这些依赖函数的代码。

图 3. 提示语的构建示例

        具体而言,我们提取同时出现在错误信息回溯日志和项目源代码中的函数名,将直接调用漏洞函数或被漏洞函数调用的函数视为依赖函数;若某一依赖函数还依赖其他函数,也将其纳入依赖函数集,直至提示词达到长度上限。依赖函数能为 ChatGPT 提供更多信息,助力其理解存在漏洞的程序和漏洞的根本原因。提示词的最后为具体的修复要求,指令 ChatGPT 生成完整的修复后函数。

4 评估

        本文的评估旨在解答以下四个研究问题:

RQ1:与当前最优的自动化程序修复技术相比,ContrastRepair 的效果如何?我们通过将 ContrastRepair 与传统自动化程序修复方法、基于深度学习的自动化程序修复技术以及近期的对话式自动化程序修复工具对比,评估其性能。

RQ2:ContrastRepair 在未见过的数据集上的表现如何?考虑到大语言模型存在数据泄露的风险,我们在 ChatGPT 训练过程中未使用过的全新数据集上,对 ContrastRepair 进行评估。

RQ3:不同超参数对 ContrastRepair 的修复性能有何影响?我们开展专项研究,分析测试对数量、连续修复阈值、重启修复阈值等各类超参数对 ContrastRepair 效果的影响。

RQ4:ContrastRepair 的不同组件对修复效果的提升有何贡献?我们旨在明确 ContrastRepair 各组件的作用,包括对比测试用例对和依赖函数。

4.1 实验设置

4.1.1 配置

        在本文的实验中,我们选择 ChatGPT 的 gpt-3.5-turbo-0301 模型,作为 ContrastRepair 的预训练大语言模型,并通过其 API 与 ChatGPT 服务交互。为扩大潜在的搜索范围并生成多样化的补丁,我们参考 CHATREPAIR 的设置,将采样温度设为 1,随机种子使用 GPT-3.5 的默认值。最大连续修复尝试次数 n 和最大重启修复尝试次数 m 分别设为 3 和 40,因此每个漏洞函数的最大对话次数为 120。

        需要说明的是,在调用 ChatGPT 进行漏洞修复的过程中,未设置超时,当调用次数耗尽时,修复过程终止。一旦生成合理的补丁,我们会通过补丁扩充,利用已生成的合理补丁提示 ChatGPT 生成更多合理补丁,该过程的调用次数上限设为 40。基于对变异后相似度分布的初步研究(见 RQ1),我们将相似度阈值 θ 设为 0.5,在保证可选择测试对数量的同时,最大化测试对的相似度。

        我们通过对失败测试用例进行由小到大程度的变异,或收集现有通过测试用例,生成测试用例集;对失败测试用例进行变异时,随机生成 1000 个新的测试用例用于验证和筛选。对新生成的测试用例进行评估时设置了超时,具体而言,为每个生成的测试用例设置 30 秒的最大评估时间,验证漏洞函数是否能通过该测试。值得注意的是,在本方法中,测试生成仅在修复过程开始前执行一次。

4.1.2 基准数据集

        我们利用业内公认的基准数据集评估 ContrastRepair 的有效性,包括 Defects4J 和 QuixBugs。其中,Defects4J 是一个被广泛研究的开源漏洞集合,涵盖 17 个不同的项目;QuixBugs 包含 40 个经典编程问题的漏洞版本和修复版本,支持 Python 和 Java 两种语言。

        为在修复过程中实现故障定位,我们参考现有研究方法,由人工标注提供基于真实情况的完美故障定位信息。我们根据提供的故障定位粒度,在三种不同场景下评估工具的性能:函数级、语句级和代码块级,分别对应单函数修复、单行代码修复和单代码块修复。需要说明的是,在实际应用中,函数级故障定位更具实用性,但修复难度也更高,因为细粒度的故障定位在实际场景中往往难以实现。

        对于 Defects4J 数据集,我们遵循自动化程序修复领域的标准实践,将其分为两个版本:Defects4J 1.2 和 Defects4J 2.0。Defects4J 1.2 包含 6 个 Java 项目中的 391 个漏洞,根据提供的故障定位信息,可分为 255 个单函数漏洞、154 个单代码块漏洞和 80 个单行代码漏洞。由于调用 ChatGPT API 的预算限制,对于 Defects4J 2.0,我们选取了 82 个单行代码漏洞,该设置在现有自动化程序修复工具的研究中被广泛使用,便于结果对比。

        需要注意的是,单行代码漏洞是单函数漏洞和单代码块漏洞的子集,而单代码块漏洞也是单函数漏洞的子集。此外,我们还在 QuixBugs 数据集上评估了 ContrastRepair 的性能,该数据集包含 Java 和 Python 两种语言的漏洞。同时,为评估 ContrastRepair 的性能,我们纳入了 HumanEval-Java 基准数据集,该数据集包含 163 个漏洞案例,且大部分为单行代码漏洞。

        HumanEval-Java 的发布时间晚于 GPT-3.5 的训练数据收集时间,因此数据泄露的风险较低。在该数据集中,开发人员将 HumanEval 中的 Python 程序及其对应的测试用例,转换为 Java 程序和 JUnit 测试用例,并在正确的 Java 程序中人为引入了部分漏洞。同样,我们为所有漏洞的修复提供了函数级的故障定位信息。

表1. 各基准测试中的bug比例

        针对上述基准数据集,我们统计了每个数据集中断言型漏洞和异常型漏洞的占比,明确变异策略的适用范围。如表 1 所示,结果表明,在大部分数据集中,断言型漏洞的占比高于异常型漏洞,在 Defects4J 数据集中这一特征尤为明显:断言型漏洞占比 67.16%,这意味着在 335 个漏洞中,仅 32.84% 的漏洞可采用变异策略。

4.1.3 实现

        在我们选取的数据集中,提供的单元测试并非始终针对漏洞函数,也可能针对漏洞函数的调用函数。为获取专门针对漏洞函数的测试用例,我们采用插桩技术:通过 Java 工具库 Javassist 在漏洞函数的起始位置插入插桩代码,在单元测试执行过程中捕获参数值,这些捕获的参数值即可作为漏洞函数的专属测试用例。收集到测试用例后,对失败测试用例采用上述类型感知的变异策略,生成变异测试用例集。

        在执行变异测试用例时,我们使用 Defects4J 提供的原始测试驱动,在测试驱动的执行过程中,漏洞函数中植入的插桩代码会将参数值替换为变异后的参数值。

4.1.4 基线方法

        在对比评估中,我们将 ContrastRepair 与 7 种当前最优的基线方法对比,包括 5 种基于学习的自动化程序修复方法(SelfAPR、AlphaRepair、RewardRepair、Recoder、CURE)、1 种传统自动化程序修复方法(TBar)和 1 种近期的基于大语言模型的技术(CHATREPAIR)。此外,我们构建了一个基于大语言模型的基线方法 BaseChatGPT,该方法使用无详细反馈的基础提示词(如无测试对和回溯信息),仅将漏洞函数作为输入,通过多轮对话迭代输出修复后的函数。具体而言,BaseChatGPT 的提示词中仅保留漏洞函数,且在多轮对话迭代中,会将上一轮模型返回的新生成函数更新至提示词中。

        本文仅选择一种传统自动化程序修复方法作为基线,原因有二:1. TBar 是当前最优的传统自动化程序修复方法;2. 现有研究已证实,传统自动化程序修复方法的性能通常低于基于学习的方法。

        选择 CHATREPAIR 作为基线的原因主要有两点:首先,ContrastRepair 和 CHATREPAIR 均采用对话式范式进行程序修复,对比结果更具参考性;其次,现有研究表明,CHATREPAIR 的性能已超越当前所有最优方法,取得了全新的最优结果。

4.1.5 评估指标

我们选取两个业内广泛使用的指标,将 ContrastRepair 与基线方法对比:

  1. 正确修复数量(#Correct):评估修复工具生成准确补丁的能力,该指标统计经人工审核后,工具成功修复的程序数量。
  2. ChatGPT 调用次数(#Query):量化基于大语言模型的修复方法的资源利用率和效率,评估所有漏洞案例中,平均调用 ChatGPT API 的次数,反映调用 ChatGPT 的频率。

4.2 RQ1:ContrastRepair 的修复效果

Defects4J 数据集上的修复性能

        表 2 展示了不同工具在 Defects4J 1.2 和 Defects4J 2.0 数据集上的正确修复数量(工具以缩写表示)。值得注意的是,ContrastRepair、BaseChatGPT 和 CHATREPAIR 在 Defects4J 1.2 上的结果,包含单函数修复、单代码块修复和单行代码修复的并集。符号 * 表示该结果并非由本研究团队得出,而是从相关论文中收集而来,原因是部分工具的代码未开源,或在本地配置中无法运行。但这些工具的配置与其他基线方法一致,且 ContrastRepair 的性能相较于这些基线方法均有显著提升,因此我们认为这并不影响对比结论的有效性。

表2. Defects4J的比较结果,加粗数值表示所有工具中表现最佳的数值

        结果表明,ContrastRepair 的性能在所有基线方法中表现最优:该方法在 Defects4J 1.2 的 255 个漏洞中成功修复 103 个,在 Defects4J 2.0 的 82 个漏洞中成功修复 40 个;在 335 个漏洞中,共生成 201 个合理补丁,合理修复率达 60.00%。在 Math 和 Jsoup 项目中,ContrastRepair 的性能显著优于其他基线方法,相较于次优基线方法,修复的漏洞数量分别提升 15.38% 和 40.00%。上述结果表明,ContrastRepair 实现了自动化程序修复性能的新突破,验证了该方法的有效性。

        将基于大语言模型的工具与其他方法对比可以发现,整合 ChatGPT 能显著提升修复性能。例如,最优的非大语言模型方法 AlphaRepair,在 Defects4J 1.2 和 2.0 上分别成功修复 70 个和 36 个漏洞;而 BaseChatGPT 的性能已具备竞争力,在两个数据集上分别成功修复 68 个和 28 个漏洞,这证明了大语言模型在漏洞修复任务中的价值。

        将 ContrastRepair 与其他基于大语言模型的方法对比可以发现,ContrastRepair 和 CHATREPAIR 的性能均优于 BaseChatGPT,这凸显了为大语言模型提供有信息量的反馈,在程序修复中的重要性。需要说明的是,本研究中 CHATREPAIR 的结果与其原始论文中的结果存在差异,原始论文中 CHATREPAIR 的结果更优,核心原因是超参数设置的不同:在原始论文中,单行代码和单代码块修复场景下的最大重启修复尝试次数上限为 200,单函数修复场景下为 100,远高于本研究的设置。

        表 5 进一步对比了 ContrastRepair 和 CHATREPAIR 的性能,其中 SL、SH、SF 列分别为单行代码修复、单代码块修复和单函数修复的结果。可以看到,在所有修复场景中,ContrastRepair 的性能均更优:在 Defects4J 1.2 的三种修复场景中,ContrastRepair 修复的漏洞数量分别比 CHATREPAIR 多 11 个、16 个和 23 个;在 Defects4J 2.0 的单行代码修复场景中,ContrastRepair 成功修复 40 个漏洞,而 CHATREPAIR 仅修复 34 个。此外,研究发现,ContrastRepair 生成的合理补丁数量比 CHATREPAIR 多约 20%(56 个 vs 47 个)。性能的提升表明,ContrastRepair 能为大语言模型提供更有用的提示词(如测试对),这也是其性能出众的核心原因。

表3. QuixBugs与基线值的比较,加粗数值表示所有工具中表现最佳者。

表4. 不同场景下的比较,加粗数值表示三种基于LLM的方法中表现最佳的方案。

表5. D4J1.2基线数据对比,加粗数值表示三种基于LLM方法中的最佳性能。

QuixBugs 数据集上的修复性能

        表 3 展示了 QuixBugs 数据集上的修复结果,值得注意的是,ContrastRepair 成功修复了 QuixBugs-Java 和 QuixBugs-Python 中的所有漏洞,表现出相较于其他方法的显著优势。表 4 展示了三种不同场景下的详细修复结果,具体而言,在 QuixBugs-Python 和 QuixBugs-Java 的三种修复场景中,ContrastRepair 的性能均优于 CHATREPAIR:在 QuixBugs-Python 中,三种场景下 ContrastRepair 修复的漏洞数量分别比 CHATREPAIR 多 3 个、1 个和 3 个;在 QuixBugs-Java 中,三种场景下的修复数量分别多 2 个、2 个和 3 个。

修复效率

        由于提示词中包含更多信息,ContrastRepair 的平均货币成本略高于 CHATREPAIR。例如,在 Defects4J 的单行代码修复场景中,使用 ContrastRepair 正确修复一个漏洞的平均成本为 0.38 美元,而 CHATREPAIR 为 0.32 美元。但另一方面,如表 5 所示,ContrastRepair 在 API 调用次数上的修复效率显著提升:与 CHATREPAIR 相比,ContrastRepair 在所有漏洞案例中的平均调用次数(#Query)大幅减少,尤其是在单行代码和单代码块修复场景中,调用次数分别减少 28.83% 和 30.20%。这一结果表明,ContrastRepair 设计的提示词质量更高,能让大语言模型通过更少的尝试,更快地掌握漏洞触发的原因。

        在单函数修复场景中,我们发现调用次数仅减少 5.97%。通过分析实验结果发现,ContrastRepair 修复了部分 CHATREPAIR 无法修复的漏洞,而这些漏洞的补丁更为复杂,ContrastRepair 只有在接近最大尝试次数时才能完成修复,这导致该场景下的调用次数减少幅度较小。

图 4. D4J1.2版本的错误修复维恩图

        图 4 为 Defects4J 1.2 数据集上,所有基线方法和 ContrastRepair 修复漏洞的维恩图。我们根据正确修复漏洞的数量,选取排名前三的基线方法,其余方法归为 “其他” 类别。可以看到,ContrastRepair 修复了 13 个其他现有工具均未修复的独特漏洞。

图5. ContrastRepair在Defects4J 1.2版本中唯一修复的错误示例。

        图 5 展示了一个具体案例(Lang-27),该漏洞仅被 ContrastRepair 成功修复。该漏洞表现为,在高亮代码行出现未预期的字符串索引越界异常。图 5 (a) 展示了存在漏洞的函数,并标注了触发异常的失败测试用例对应的代码行:若输入能成功解析,或输入无效时抛出数字格式异常,则判定输入测试通过。如图 5 (b) 所示,CHATREPAIR 错误地引导 ChatGPT 将注意力集中在 expPos 的值上,导致对其计算方式的调整出现错误。

        当输入失败测试用例 1eE 时,ContrastRepair 首先生成一个相似的通过测试用例 eE1。我们推测,该通过测试用例可暗示 expPos 的计算方式并无问题,通过添加边界检查即可实现有效修复。此外,通过测试用例 eE1 和失败测试用例 1eE 的 expPos 值分别为 2 和 4,而两个输入的长度均为 3。这一对比信息(4>3 且 2<3)能为 ChatGPT 提供更有力的引导,使其推断出漏洞由 expPos 值超过输入长度触发。因此,如图 5 (c) 所示,ContrastRepair 成功修复了该漏洞。

图6. 上下文辅助修复的示例说明

        图 6 展示了一个案例研究(Jsoup-40),验证了上下文信息对修复成功的必要性 —— 该漏洞无法被 CHATREPAIR 修复。该图从上至下分为三个部分:顶部为回溯日志,中部为日志中触发的函数源代码,底部为漏洞函数的正确修复方案。在该案例中,漏洞函数为 DocumentType,且该函数依赖 notEmpty 函数:当输入的字符串 name 为空时,依赖函数 notEmpty 会触发非法参数异常。通过在提示词中融入依赖函数的相关源代码,ChatGPT 能识别出漏洞的根本原因,并添加安全检查,确保 name 非空且非 null。最终,ContrastRepair 成功修复了该漏洞。

类型感知变异的性能

        我们还评估了提出的类型感知变异策略的有效性,发现在 Defects4J 数据集中,有 45 个漏洞能通过变异成功生成通过测试用例,进而构建新的测试对。基于不同变异规则生成的测试对,其平均相似度得分如下:1. 字符串:0.89;2. 整数、双精度浮点数、单精度浮点数:0.72;3. 字符:0.81;4. 布尔值:0.78;5. 对象:0.91;6. 数组、列表:0.79。

        结果表明,基于对象类型变异生成的测试对平均相似度得分最高,原因是对象通常包含多个属性,仅修改其中一个属性而保持其他属性不变,整体变化较小,因此相似度得分更高。相反,对于整数、双精度浮点数或单精度浮点数等类型,即使是微小的随机增减,也会导致编辑距离显著增大,进而使相似度得分降低。

        部分漏洞无法成功生成通过测试用例,原因是这些漏洞函数无基本类型的参数,或所有参数均为开发人员自定义类实例化的复杂对象。我们还分析了类型感知变异前后测试对的相似度,图 7 展示了变异前后最大相似度得分的频率分布,横轴为相似度得分,纵轴为漏洞数量。对于每个漏洞,计算其多个测试对中的最高相似度得分,若某一漏洞无测试对,则其最大相似度得分为 0。

图7. 突变前后相似评分。

        原始数据中,无测试对的漏洞数量为 43 个,而变异后减少至 25 个。结果显示,初始相似度得分的分布范围较广,仅 14.67%(11/75)的漏洞相似度得分在 0.5-1 之间;而变异后,相似度得分主要集中在 0.6-1 之间,占比达 53.33%(40/75)。这一结果表明,本文提出的变异策略能显著提升测试对的相似度得分。在实验中,我们为每个漏洞随机生成 1000 个测试用例,并通过 Defects4J 提供的命令进行验证。

RQ1 结论:ContrastRepair 的性能显著优于当前所有最优的自动化程序修复工具,包括基于 ChatGPT 的修复方法 CHATREPAIR。例如,在 Defects4J 1.2 的三种修复场景中,ContrastRepair 的修复成功率分别为 70.00%、44.81% 和 29.41%,而 CHATREPAIR 的修复成功率分别为 56.25%、34.42% 和 20.39%。此外,ContrastRepair 的 ChatGPT API 调用次数少于 CHATREPAIR。

4.3 RQ2:在未见过的数据集上的评估

        考虑到 Defects4J 和 QuixBugs 等广泛使用的基准数据集,可能被纳入 ChatGPT 的训练数据,存在数据泄露的风险,我们在未见过的全新数据集上开展实验,评估 ContrastRepair 的有效性。需要说明的是,尽管 Defects4J 和 QuixBugs 可能对 ChatGPT 的训练数据产生影响,但在 RQ1 中,ContrastRepair 与 CHATREPAIR、BaseChatGPT 使用相同版本的 ChatGPT,因此二者的对比分析仍能验证 ContrastRepair 的有效性。

        为从新的角度验证方法性能,我们选择了近期发布的基准数据集 HumanEval-Java(2023 年 1 月公开)。在评估中,我们将 ContrastRepair 配置为单函数修复模式,n 和 m 分别设为 3 和 40,并将其结果与 CHATREPAIR、BaseChatGPT 对比。

        如表 6 所示,ContrastRepair 在三种工具中表现最优,成功修复了 164 个漏洞中的 137 个;相比之下,CHATREPAIR 修复 130 个,BaseChatGPT 修复 126 个。值得注意的是,与 CHATREPAIR 和 BaseChatGPT 相比,ContrastRepair 的修复成功率分别提升 5.38% 和 8.73%。

表6. HumanEval-Java的比较,加粗数值表示三种基于LLM的方法中表现最佳的数值

        此外,从平均每个漏洞的 ChatGPT 调用次数(#Query)可以看出,ContrastRepair 的效率显著优于另外两种方法:与 CHATREPAIR 相比较,调用次数减少 29.34%(从 24.68 次降至 17.44 次);与 BaseChatGPT 相比,调用次数减少 39.66%(从 28.90 次降至 17.44 次)。该结果与 Defects4J 基准数据集上的发现一致,进一步验证了 ContrastRepair 的有效性。

RQ2 结论:在未见过的数据集 HumanEval-Java 上的评估结果表明,ContrastRepair 在正确修复漏洞数量和 ChatGPT API 调用效率两方面,均表现出更优的性能。

4.4 RQ3:超参数评估

        ContrastRepair 的核心超参数包括:测试用例对的数量 k,以及由连续修复和重启修复配置(m,n)决定的总尝试次数。我们选择更具实际应用价值的单函数修复场景开展实验,实验数据集为 Defects4J 中至少能构建一个测试对的 106 个漏洞。

表7. 对比组数评估,加粗数值表示在所有设置中表现最佳。

        表 7 展示了不同测试用例配置下的实验结果(#Fail 和 #Pair 列),配置包括四种类型:单个失败用例(SingleFail)、两个失败用例(DoubleFail)、一个测试对(SinglePair)、两个测试对。同时,我们展示了基于大语言模型的基线方法在这 106 个漏洞上的修复结果。由于数据集中半数漏洞的相似度阈值 0.5 下的测试对数量不足两个,我们将最大测试对数量限制为 2;若某一漏洞的测试对数量超过两个,仅选取相似度排名前二的测试对。实验从正确修复数量(#Correct)、合理修复数量(#Plausible)和 ChatGPT API 调用次数(#Query)三个维度进行对比。

        研究发现,当提供两个测试用例对时,ContrastRepair 的正确修复率和合理修复率均达到最高(37/60);仅提供一个测试用例对时,修复效果次之(35/58)。关键结论是,使用测试用例对的修复效果,始终优于提供相同数量的失败测试用例;此外,提供两个失败测试用例(33/55)的修复效果优于单个失败测试用例(32/53)。值得注意的是,当输入两个测试用例对时,ContrastRepair 的 API 调用次数最少(63.09 次)。

        上述发现凸显了测试用例对的价值:与仅提供失败测试用例相比,测试用例对能为大语言模型提供更多用于漏洞修复的有用信息。因此,增加测试用例或测试用例对的数量,有助于优化修复结果。

        为评估连续修复的有效性(即 n 轮迭代的修复方向是否趋于正确),我们设置了四种配置,包括 40×1、40×2、40×3 和 40×3(相同提示词)。需要说明的是,与前三种配置不同,最后一种配置(40×3,相同提示词)在 n 次调用中使用完全相同的提示词。通过将其与使用连续提示词的 40×3 配置对比,可判断 n 轮迭代的修复方向是否正确且有用;通过对比前三种配置的结果,可评估基于前序补丁连续修复的有效性。

表8. 增加迭代深度影响的评估,加粗数值表示所有设置中最佳性能。

        研究发现,在重启修复次数不变的情况下,增加连续修复尝试次数能提升修复效果。如表 8 所示,当 m 设为 40、n 设为 1 时,工具能生成 39 个合理补丁和 26 个正确补丁;而当 m 保持不变、n 增至 2 时,工具性能显著提升,合理补丁数量增加 38.46%,正确补丁数量增加 26.92%;进一步将 n 从 2 增至 3 时,ContrastRepair 的性能继续提升,合理补丁数量增加 11.11%,正确补丁数量增加 12.12%。这一显著的性能提升表明,“基于前序补丁继续修复” 的策略,对提升修复性能具有重要作用。

        此外,我们还发现,小幅增加迭代深度的配置(40×3),其性能优于增加广度的配置(40×3,相同提示词)。因此,若总调用次数固定,找到 m 和 n 的最优平衡至关重要。为探究该问题,我们设置了三种不同的配置:12×10、40×3 和 120×1,所有配置的总预算上限均为 120 次尝试,但策略不同:12×10 侧重深度搜索,120×1 侧重广度搜索,40×3 则平衡了深度和广度。

表9. 重启/持续修复评估,加粗数值表示三种基于LLM的方法在相应设置下的最佳性能。

        表 9 中 ContrastRepair 的结果分析表明,40×3 配置在正确 / 合理修复数量(37/60)和调用次数(63.09 次)两方面均表现最优;而 12×10 配置的效果最差(33/52)。这一差异表明,参数 n 对 ContrastRepair 的性能起关键作用:n 过大会产生负面影响,若初始修复尝试偏离最优方案,随着迭代次数增加,将难以调整至正确的修复路径,进而影响准确补丁的生成;但 n 过小也会产生不利影响,若初始修复方向正确,但生成理想补丁需要多轮迭代,则适度增大 n 能发挥积极作用 —— 此类场景下,持续的反馈信息有助于生成正确的修复补丁。

        同时,结果表明,在另外两种 m×n 配置下,ContrastRepair 的性能仍优于两种基线方法:在 12×10 和 120×1 配置下,ContrastRepair 修复的漏洞数量分别比 CHATREPAIR 多 6.45% 和 6.06%。

RQ3 结论:与仅依靠失败测试用例相比,使用测试用例对能为大语言模型提供更全面的信息,实现更有效、更高效的漏洞修复;重启修复和连续修复的平衡,是保证修复性能的关键。

4.5 RQ4:ContrastRepair 的消融实验

        在本节中,我们通过消融实验评估 ContrastRepair 的两个关键组件的作用:测试对选择依赖函数。具体而言,我们构建了该工具的三种变体:1. 无测试用例(w/o Test):ContrastRepair 既无通过测试用例也无失败测试用例;2. 无相似度筛选(w/o Similarity):将基于相似度的测试对选择替换为随机选择;3. 无变异(w/o Mutation):移除类型感知的变异过程;4. 无上下文(w/o Context):移除依赖函数的融入过程(此处的上下文指项目中与漏洞函数存在调用或被调用关系的依赖函数),依赖关系可从错误回溯信息中提取。

        为评估 w/o Test 和 w/o Similarity 的性能,我们使用与 RQ3 相同的数据集(106 个漏洞,每个漏洞均有多个测试对);为评估 w/o Context 的性能,我们选取 Defects4J 中每个样本均包含多个依赖函数的 103 个漏洞,原因是并非所有样本都存在函数调用或测试对;为评估 w/o Mutation 的性能,我们选取所有能通过变异构建新测试对的 45 个漏洞。与 RQ3 一致,我们在更贴近实际的单函数修复场景下开展修复实验,且为降低随机性的影响,每个漏洞的修复过程均重复三次。

表10. 不同组件消融效果对比,加粗数值表示不同消融参数下的最佳性能表现。

        如表 10 所示,结果凸显了测试对、依赖函数和基于相似度的选择策略,在 ContrastRepair 中的重要性。在 106 个漏洞的实验中,ContrastRepair 平均生成 60.33 个合理补丁和 37.00 个正确补丁;而 w/o Test 平均仅生成 53.33 个合理补丁和 32.33 个正确补丁。此外,当将相似度筛选替换为可能无法选中高相似度测试对的随机选择时,平均生成 56.33 个合理补丁和 33.33 个正确补丁 —— 性能优于无测试对的设置,但低于原始的 ContrastRepair,这凸显了融入基于相似度的测试对选择策略带来的性能提升。

        在 103 个融入了依赖函数上下文信息的漏洞实验中,ContrastRepair 平均生成 64.67 个合理补丁和 36.00 个正确补丁;而无上下文信息的 w/o Context,平均仅生成 61.67 个合理补丁和 32.67 个正确补丁。我们针对 45 个可通过变异生成对比测试用例的漏洞,开展实验评估变异操作的有效性,结果如表 10 最后一行所示:ContrastRepair 利用变异生成的新测试对,平均生成 29.33 个合理补丁和 19.00 个正确补丁;而无变异的 ContrastRepair(无对比测试用例时,平均仅生成 26.00 个合理补丁和 16.67 个正确补丁。此外,我们发现无变异时的平均调用次数,比原始 ContrastRepair 增加了 14.39%。这些发现表明,本文提出的变异策略能生成具有信息量的测试对,进而提升修复性能并减少调用次数。

表11. 剪除法在相似性与上下文中的性能评估,加粗数值表示不同剪除设置下的最佳性能表现。

        为进一步验证各组件的价值,我们引入了一个额外的评估指标Pass@m(m 为允许的最大修复尝试次数,参考算法 1),并为 m 设置了不同取值,结果如表 11 所示。表中 ContrastRepair (106) 和 ContrastRepair (103),分别代表 ContrastRepair 在 106 个和 103 个漏洞数据集上的实验结果。整体而言,实验结果再次印证了测试对、基于相似度的测试对选择策略,以及依赖函数上下文信息的重要性。值得注意的是,当 m 取值更小(即修复预算更紧张)时,这些组件对生成正确补丁(#C)和合理补丁(#P)的影响更为显著,这一点在 Pass@20、Pass@10 和 Pass@5 的结果中均能体现。

        此外,融入基于相似度的测试对选择策略和上下文信息,有助于减少 ChatGPT API 的调用次数。例如,在三个选定的数据集上,ContrastRepair 修复每个漏洞的平均调用次数分别为 63.09 次、58.28 次和 43.65 次;而无测试用例、无相似度筛选、无上下文、无变异四种设置下的平均调用次数,分别为 73.36 次、72.62 次、62.17 次和 49.93 次,相较于原始 ContrastRepair,分别增加了 16.28%、15.11%、6.67% 和 14.39%。

RQ4 结论:在提示词中融入基于相似度的测试对选择策略和依赖函数上下文信息,有助于提升 ContrastRepair 的漏洞修复能力。这些组件不仅能显著提高大语言模型生成正确补丁的数量,还能减少 API 的调用次数。

5 讨论

5.1 不同相似度策略的选择

        为探究本文所采用选择策略的有效性,我们选取 BM25(基于词汇)、CodeBERT、UniXcoder(基于语义)三种方法进行对比。与本文使用的达默 - 莱文斯坦距离相似度度量方法不同,BM25 采用稀疏向量表示法进行词汇匹配;CodeBERT 和 UniXcoder 均为基于学习的方法 ——CodeBERT 通过掩码语言建模,从大规模代码语料库中学习代码表示,而 UniXcoder 则进一步将抽象语法树与不同的预训练任务相结合进行学习。

        与 RQ4 的实验配置一致,我们在 106 个包含多个测试对的漏洞数据集上,探究了不同方法的有效性,且为避免随机性影响,实验均重复三次。此外,对于 CodeBERT 和 UniXcoder,我们将测试用例输入模型得到向量表示,并基于余弦函数计算相似度。

        实验结果如表 12 所示,我们发现使用达默 - 莱文斯坦距离的效果优于其他方法。由于相似度度量的目标是找出测试用例中的关键修改,因此与基于字符串的相似度检索相比,使用向量表示进行检索对于该任务而言,可能存在间接性和不适用性。表 12 的结果,验证了本文所采用相似度度量方法的有效性。

表12. 采用不同相似性策略筛选病例的有效性评估,加粗数值表示所有工具中表现最佳者。

5.2 相似度得分与修复性能的关系

        为探究相似度得分与修复性能之间的关联,我们从原始的 106 个漏洞中选取了 60 个子集,每个漏洞至少包含三个测试对,并在与 ContrastRepair 相同的配置下开展实验,结果如表 13 所示。

表13. 相似性评分与性能评估

        最高分(Highest score)中等分(Medium score)最低分(Lowest score),分别指每个漏洞函数对应的三个相似度得分最高、中等、最低的测试对,其平均得分分别为 0.83、0.66 和 0.56。我们发现,选择最高分测试对时的修复性能最优,正确补丁和合理补丁的数量分别提升了 8% 和 7.32%;而选择中等分和最低分测试对时的修复性能,无显著差异,原因是二者的得分均未达到足够高的水平。这些结果表明,选择相似度得分更高的测试对,能有效提升修复性能。

5.3 各组件的贡献

        Defects4J 数据集包含 335 个漏洞,其中 106 个漏洞有对应的测试对,103 个漏洞拥有可融入提示词的上下文信息,剩余 164 个漏洞则既无测试对也无上下文信息。对于这类漏洞,我们将测试对替换为单个失败测试用例。此外,为更全面地分析各组件在不同子集上的修复性能贡献,我们引入了另一个基准数据集 Bears,该数据集包含 63 个漏洞,其中 50 个漏洞有测试对,5 个漏洞有上下文信息,8 个漏洞仅有单个测试用例,实验结果详见表 14。

表14. 不同组件的贡献度。加粗数值表示三种基于LLM的方法在不同子集上的最佳性能。

        在 Defects4J 数据集中,ContrastRepair 成功修复的漏洞数量,比 CHATREPAIR 多 19 个。在这 19 个漏洞中,6 个来自测试对子集,8 个来自上下文子集。这表明,ContrastRepair 中融入的测试对和上下文信息,能显著提升大语言模型的修复能力。二者共同贡献了 14 个额外的漏洞修复成果,占总提升量的 73.68%(14/19)。

        我们还发现,仅提供单个失败测试用例,也能帮助 ChatGPT 多修复 5 个漏洞。原因在于 CHATREPAIR 仅提供与失败测试用例相关的运行时错误信息,未提供测试用例的完整细节;而本文的方法补充了该信息,能让 ChatGPT 更有效地理解漏洞产生的原因。类似地,在 Bears 数据集的测试对子集和上下文子集中,ContrastRepair 也实现了更高的正确修复率,证明了本文方法在不同基准数据集上的通用适用性。

5.4 无完美故障定位时的性能

        本文方法的一个局限性在于,我们主要遵循自动化程序修复领域的通用设置,考虑了三种修复场景:单行代码、单代码块和单函数修复。其中,单行代码和单代码块修复场景均提供了完美的故障定位信息,因此其正确修复率高于单函数修复场景。

        但在部分案例中,测试对能通过引导大语言模型理解漏洞的根本原因,间接助力故障定位(见图 1)。在未来的研究中,我们计划通过两种方式评估 ContrastRepair 的有效性:一是移除完美的故障定位信息,让大语言模型自主实现间接的故障定位;二是融入现有的非完美故障定位技术,例如 ITER 中使用的故障定位工具 Gzoltar。

5.5 多位置修复场景下的有效性

        多位置漏洞修复仍是一项具有挑战性的任务,这类漏洞通常包含多个分散在不同函数和文件中的漏洞代码段。事实上,本文方法也可应用于多位置漏洞修复,具体而言,需要在提示词中融入以下组件:1. 所有的漏洞函数及其对应的故障定位信息;2. 这些漏洞函数的依赖函数;3. 由不同漏洞函数生成的对比测试用例对。

        但由于该任务的复杂性,仅依靠上述信息可能无法取得理想的修复效果。为提升修复性能,我们可从以下方面对 ContrastRepair 进行改进:1. 提供更详细的上下文信息,例如漏洞文件所依赖的其他文件;2. 优化搜索算法,建立更精准的反馈机制,以确定何时采用深度搜索、何时采用广度搜索;3. 提供测试用例在运行时的执行路径信息,帮助大语言模型更准确地理解触发漏洞的根本原因。我们计划在未来的研究中,对本文方法进行适配和改进,以应用于多位置漏洞修复场景。

5.6 有效性威胁

        本文实验结果的有效性,面临的第一个潜在威胁是评估数据集的选择,ContrastRepair 所取得的性能提升,可能无法泛化到其他修复数据集。为缓解这一威胁,我们选取了多样化的数据集开展评估,包括 Defects4J 1.2、Defects4J 2.0 和 QuixBugs。

        第二个威胁是数据泄露,即真实的修复补丁可能被纳入 ChatGPT 的原始训练数据。为解决这一问题,我们额外选取了 HumanEval-Java 数据集,该数据集的发布时间晚于 ChatGPT 模型的训练时间。

        人工验证合理补丁是另一项潜在威胁,需要通过仔细检查来判断补丁是否在语义上等价于真实补丁。为缓解该威胁,我们投入了大量的人力成本以保证人工验证的准确性和客观性:邀请三名软件工程领域的研究人员分别进行验证,每人投入超过 15 小时的时间手动校验补丁;对于验证结果不一致的补丁,研究人员会展开讨论并最终达成共识。

        判定正确补丁的标准为:补丁与真实补丁完全一致,或在语义上等价于真实补丁。这种人工验证方法,与多项现有研究中所采用的方法一致。此外,我们已将所有补丁开源,供公众进行评估。

        实验中的效率评估也可能存在挑战。值得注意的是,我们未对不同方法的运行时间进行度量,原因有二:其一,在 RQ1 的实验过程中,我们发现与基于学习的修复方法相比,基于大语言模型的修复方法不仅耗时显著更少,性能也表现更优。即便 ContrastRepair 的总耗时(包含查询和变异阶段)约为 CHATREPAIR(仅包含查询阶段)的两倍,其耗时仍完全在可接受范围内。在这一阶段,我们认为在效果与效率之间进行权衡时,优先考虑修复效果更为合理。

        其二,调用 ChatGPT API 时存在诸多不可控因素,例如网络延迟、ChatGPT 模型的配置,以及模型的并行处理和调度策略。为缓解这一问题,我们通过统计 API 的调用次数(即 #Query)来衡量提示词的信息含量,以此判断 ChatGPT 是否能更好地理解漏洞产生的根本原因。

        我们的观察发现,对话式修复的性能通常会随着 API 调用次数的增加而提升。通过对比调用次数,我们能评估不同的反馈信息在引导修复过程中的有效性。

        此外,实验设置中存在的不确定性,也可能对结果的可复现性构成潜在威胁。导致这种不确定性的关键因素包括:随机种子和温度的设置选择、大语言模型的选取,以及大语言模型推理过程的非确定性。例如,浮点运算中的微小精度误差,可能会随着时间的推移不断累积,最终导致实验结果出现显著差异。

        本文实验结果面临的另一项潜在威胁是,RewardRepair 和 TBar 的部分结果引自现有论文。但我们认为这些对比不会影响本文的研究结论,因为现有研究已证明,这些方法与基于大语言模型的方法之间存在显著的性能差距。

6 相关工作

6.1 自动化程序修复

        自动化程序修复工具的作用,是从原始代码和对应的漏洞位置生成修复后的补丁。自动化程序修复工具生成的每一个补丁,都需要通过测试套件的验证:合理补丁指能成功通过整个测试套件的补丁,而正确补丁则是能有效修复底层漏洞的合理补丁。

        总体而言,自动化程序修复领域主要分为两类方法:传统方法和基于学习的方法。传统工具大致可分为三类:基于启发式的方法、基于约束的方法和基于模板的方法。但这些传统方法存在诸多局限性,其中基于模板的工具因修复性能最优,被视为传统方法中的最优技术,但其高度依赖人工设计的模板或特定的修复模式,仅能修复有限类型的漏洞。

        随着深度学习技术的飞速发展,基于学习的方法逐渐成为研究热点,例如 CURE、RewardRepair、Recoder、CoCoNut、SelfAPR 和 ITER 等。这类方法将自动化程序修复问题转化为神经机器翻译问题,在提升漏洞修复性能方面展现出巨大潜力。与传统方法相比,这些基于学习的方法能实现更优的性能,其中 ITER 甚至能在多位置修复这一自动化程序修复领域的高难度场景中,取得最优的性能。

        与传统方法不同,基于学习的方法能自动捕捉并行漏洞修复对之间的语义关系,这一能力使其能生成更有效、且具备上下文感知能力的补丁解决方案。尽管如此,模型的有效性在很大程度上仍由训练数据集的质量和数量决定。在从 GitHub 爬取数据时,若包含大量无关的提交和修改记录,训练语料中可能会引入大量噪声。

        近年来,研究人员开始探索利用性能强大的大语言模型实现自动化程序修复的可行性。大语言模型能够直接从上下文信息中生成准确的补丁,无需进行微调。AlphaRepair 是首个完形填空式的自动化程序修复方法,无需在历史漏洞修复数据上进行任何微调或再训练,直接利用大规模预训练代码模型实现自动化程序修复。

        尽管基于大语言模型的方法已取得显著成果,但这类方法主要聚焦于漏洞代码本身,将漏洞修复视为一个单步过程,忽略了漏洞修复中固有的交互协作特性。此外,通过挖掘和分析与大语言模型的交互记录历史数据,理解大语言模型在漏洞修复中失败的根本原因,研究人员能在后续的交互中,更好地引导大语言模型朝着正确的方向完成程序修复。

        现有研究也已证明,将测试用例应用于代码修复任务的有效性,能帮助自动化程序修复工具生成高质量的补丁。基于这一思路,研究人员提出了 CHATREPAIR—— 首个全自动化的对话式自动化程序修复方法,将补丁生成与测试套件的即时反馈相结合,以对话的形式实现自动化程序修复。与传统方法和基于学习的方法相比,CHATREPAIR 通过为对话提供错误反馈信息,利用大语言模型取得了良好的修复效果。但遗憾的是,这类反馈并非总能为高效的修复工作,提供精准且具有信息量的提示词。

        本文提出了 ContrastRepair 方法,旨在设计更具体、更具指导性的提示词,提升大语言模型准确理解漏洞相关语义、并生成高质量补丁的能力。

6.2 大语言模型

        生成式人工智能的最新发展,带来了性能的大幅提升和大语言模型的广泛应用。在自然语言处理领域,大语言模型在机器翻译、文本摘要和分类等众多任务中,均取得了令人瞩目的性能。

        大语言模型最初在自然语言处理任务中表现出色,例如文档分类、文本摘要和机器翻译。研究人员将程序设计语言视为另一种自然语言,并在程序代码和自然语言文本的混合语料库上训练大语言模型,这一做法带来了巨大的进展。如今,大语言模型已被广泛应用于各类软件工程任务,包括代码生成、代码摘要、代码评审、代码补全、程序规约生成和提交信息生成。

        由于大语言模型的设计具备通用性,且能在不同领域中学习知识,研究人员可通过为模型提供定制化的提示词,或根据需求提供少量任务解决示例作为输入,让大语言模型应用于相关的下游任务。

        大语言模型是拥有海量参数和卓越学习能力的先进语言模型,GPT-3、InstructGPT 和 GPT-4 等众多大语言模型的核心模块,均是 Transformer 中的自注意力机制,该机制也是语言建模任务的基础构建模块。

        大语言模型的一个重要特征是具备上下文学习能力,即模型经过训练后,能根据提供的上下文或提示词生成文本。这一能力让大语言模型能生成更连贯、且符合上下文的回复,使其非常适用于交互式和对话式场景。

        基于人类反馈的强化学习是大语言模型的另一项核心要素,该方法利用人类生成的回复作为奖励对模型进行微调,让模型能从错误中学习并逐步提升性能。值得注意的是,ChatGPT 注重对话交互,且能记忆并参考之前的对话内容,在各类软件工程任务中均取得了最优的性能。

        本文的研究目标是,在与 ChatGPT 的交互协作过程中,通过利用和挖掘测试用例中的信息,设计更有效的提示词,引导和启发 ChatGPT 理解漏洞的语义特征,实现更高效的漏洞修复。

6.3 自动化测试生成

        自动化测试生成是一种被广泛应用的技术,通过自动生成测试用例检测软件缺陷。黑盒测试生成中的模糊测试技术,无需分析被测系统的内部代码结构,仅向其提供随机的测试输入(如随机字节流)。传统的黑盒测试技术主要可分为两类:基于生成的技术和基于变异的技术。

        基于生成的技术专注于从零开始创建测试输入,而基于变异的技术则通过对现有种子输入进行系统性修改,生成更多样化的测试用例。黑盒测试策略的一个核心缺陷是常被称为 **“盲目性”** 的问题,即难以生成设计精巧的测试用例,对复杂的代码路径进行全面的探索。

        白盒测试方法通过分析被测系统的源代码,生成更高质量的测试用例。例如,符号执行通过利用符号路径约束生成测试用例,针对更深层的代码路径开展测试,从而突破了代码覆盖率的限制。但由于约束求解的成本较高,研究人员提出了一些提升可扩展性的解决方案,包括符号执行与具体执行结合的测试等算法方法,以及优化编译等实现策略。

        作为介于黑盒和白盒测试之间的方法,覆盖率引导的模糊测试(也被称为灰盒测试),利用被测系统的覆盖率信息作为反馈,调整测试输入的生成和变异过程。

        由于本文的研究目的并非提升代码覆盖率或增加测试用例的多样性,我们仍采用黑盒测试生成方法。受类型感知算子变异的启发,我们对参数进行变异,将其称为类型感知参数变异。该策略有两个目的:其一,能在短时间内快速生成大量的测试用例;其二,通过对原始用例进行细粒度、最小化的修改,确保新生成的用例与原始用例高度相似。

6.4 测试用例相似度

        为选择相似的测试用例对,我们需要特定的度量指标来衡量每一对测试用例的相似度,目前有两种被广泛使用的指标:基于词汇的相似度和基于语义的相似度。

        BM25 是一种基于词汇的相似度指标,采用稀疏向量表示法进行词汇匹配,将每个代码片段转换为词袋表示,并计算测试对之间的词汇相似度,其计算的相似度得分表示为。作为一种基于稀疏术语的度量指标,BM25 对源代码中的标识符命名选择较为敏感,而标识符命名并不会影响代码的语义。

        密集段落检索属于基于语义的相似度指标,为训练密集段落检索模型使其能有效学习代码嵌入表示,需要同时提供正样本对和负样本对,这一方法基于一个假设:两个相似的代码片段通常具有相似的语义特征。

        但在本文中,测试用例并非以代码片段的形式呈现,而是由漏洞函数中参数的具体取值组成。因此,上述两种用于评估代码相似度的指标,并不适用于本文的研究场景。本文的方法为:首先将这些参数值转换为字符串,再利用达默 - 莱文斯坦距离计算其相似度。

7 结论

        本文提出了 ContrastRepair,一种新型的基于对话的自动化程序修复方法,利用 ChatGPT 实现漏洞修复。通过生成对比测试用例对,ContrastRepair 能为 ChatGPT 提供具有信息量的反馈,使其更好地理解漏洞产生的原因。

        我们在 Defects4J、QuixBugs 和 HumanEval-Java 等多样化的基准数据集上,开展了全面的评估,并将 ContrastRepair 与当前最优的自动化程序修复基线方法进行对比。实验结果表明,ContrastRepair 在自动化程序修复领域实现了新的最优性能。

启发

        通过失败测试用例 + 高度相似的通过测试用例组成对比对,用「负反馈 + 正反馈」的组合让模型更好地定位导致差异的漏洞。

        将漏洞分为「断言型」「异常型」,并针对二者设计不同的测试对构建策略(异常型可变异生成,断言型依赖现有断言用例),应该用不同的方式应对两种不同的漏洞。

        可以参考“类型感知变异” 生成最小差异的测试用例,提升测试用例的有效性和覆盖率。

BibTex


@article{ WOS:001612246800001,
Author = {Kong, Jiaolong and Xie, Xiaofei and Cheng, Mingfei and Liu, Shangqing
   and Du, Xiaoning and Guo, Qi},
Title = {ContrastRepair: Enhancing Conversation-Based Automated Program
   Repair via Contrastive Test Case Pairs},
Journal = {ACM TRANSACTIONS ON SOFTWARE ENGINEERING AND METHODOLOGY},
Year = {2025},
Volume = {34},
Number = {8},
Month = {OCT},
DOI = {10.1145/3719345},
Article-Number = {216},
ISSN = {1049-331X},
EISSN = {1557-7392},
ResearcherID-Numbers = {Liu, Shangqing/LCD-8169-2024
   Cheng, Mingfei/HCH-3725-2022
   Xie, Xiaofei/ABE-4095-2020},
ORCID-Numbers = {, Xiaofei/0000-0002-1288-6502
   },
Unique-ID = {WOS:001612246800001},
}

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐