Automatic Patch Generation Learned from Human-Written Patches文章记录

本文介绍了一种新的自动补丁生成技术PAR,该技术从人工编写的补丁中学习修复模式。通过对大量开源补丁的分析,作者们识别出常见的修复模式并创建了修复模板。在对119个真实bug的评估中,PAR成功生成了27个补丁,比GenProg的16个更成功。用户研究也表明,PAR生成的补丁比GenProg的更容易被接受。
摘要由CSDN通过智能技术生成

Automatic Patch Generation Learned from
Human-Written Patches
从手写的补丁中学习自动生成补丁

作者:Dongsun Kim, Jaechang Nam, Jaewoo Song, and Sunghun Kim
The Hong Kong University of Science and Technology, China

摘要

补丁生成是一项必不可少的软件维护任务,因为大多数软件系统都不可避免地存在需要修复的bug。不幸的是,人力资源通常不足以修复所有报告的和已知的错误。为了解决这个问题,**提出了几种自动生成补丁的技术。**特别是,Weimer等人提出的基于遗传规划的patch生成技术GenProg已经显示出了很好的结果。然而,由于突变操作的随机性,这些技术可能会产生无意义的补丁。
为了解决这一限制,我们提出了一种新的补丁生成方法,基于模式的自动程序修复(PAR),使用从现有人工编写的补丁中学习的修复模式。
我们手动检查了超过60,000个人工编写的补丁,发现有几个常见的修复模式。我们的方法利用这些修复模式自动生成程序补丁。我们对119个真实的bug进行了实验评估。此外,一项涉及89名学生和164名开发人员的用户研究证实,由我们的方法生成的补丁比由GenProg生成的补丁更容易接受。PAR在119个错误中成功生成了27个,而GenProg只成功生成了16个错误。

1介绍

为了减少人工的工作量,人们提出了几种自动生成补丁的技术。Arcuri和Yao提出了将进化算法应用于自动生成补丁的思想。Dallmeier等人提出了一种利用对象行为模型的方法,并将该方法应用于来自开源项目的实际bug。Weimer等人提出了一种基于群体的技术,利用遗传编程。Wei等人提供了一种基于契约的技术来自动生成补丁,并通过将其应用于Eiffel类中的bug显示了它的有效性。

其中,**获奖的补丁生成技术GenProg[7]及其扩展[8]显示了最有前景的结果。为了修复给定程序中的错误,这种技术通过使用交叉操作符和变异操作符(如语句添加、替换和删除[9])来生成程序的变体。然后,它运行测试用例来评估每个变量。GenProg迭代这些步骤,直到其中一个变体通过所有测试用例。任何程序变体通过所有测试用例被认为是一个成功的补丁。**他们实验表明,这种技术可以成功地为105个真正的bug中的55个创建补丁。

然而,GenProg有一个固有的局限性:由于这种技术基本上依赖于随机的程序突变,如语句的添加、替换和删除,因此可能生成无意义的补丁。图1(b)显示了一个由GenProg为图1(a)所示的bug生成的无意义补丁的示例。与图1©中人工编写的补丁相比,GenProg的补丁完全从程序中删除了“strings[]”变量。注意,只要给定的索引是有效的,程序就会给lhs分配一个字符串元素,而GenProg生成的补丁不会这样做。尽管GenProg的补丁实际上可以通过所有给定的测试用例,但开发人员不会接受这个补丁,如Section IV-C所示。

为了解决这个问题,我们提出了一种新的补丁生成技术:基于模式的自动程序修复(PAR)这种方法利用了人类编写补丁的知识。我们首先仔细检查了62,656个人工编写的开源项目补丁。有趣的是,我们发现有几个常见的固定模式。根据我们的观察,**我们创建了10个修复模板,它们是基于确定的修复模式的自动程序编辑脚本。****解析这些修复模板以生成程序补丁。**尽管创建修复模板需要手工工作,但这只是一次性的成本,而且这些模板创建后在不同的上下文中具有高度可重用性。图1(d)显示了由我们的方法生成的补丁,它类似于人工编写的补丁(图1©)。

为了评估PAR,我们将其应用于从Apache log4j、Rhino和AspectJ等开源项目收集的119个实际bug。

我们询问了253名人类受试者(89名学生和164名开发人员),如果他们是PAR和GenProg生成的匿名补丁的代码审查者,他们会接受哪些补丁。本研究的结果清楚地表明,**PAR生成的补丁比GenProg生成的补丁更容易接受。**此外,我们的方法生成了比GenProg更成功的补丁:PAR在119个错误中成功生成了27个补丁,而GenProg成功生成了16个错误。

本文的主要贡献如下:
人工观察人类写的补丁:我们对人类写的补丁的调查显示,补丁中有共同的修复模式。
PAR,一种利用修复模式的自动修补程序生成技术:我们提出了一种新的自动修补程序生成技术,它使用来自公共修复模式的修复模板。
**实证评价:**我们将PAR应用于119个真实bug,给出了实证评价结果。

本文的其余部分组织如下。在第二节介绍了从人类编写的补丁中识别出的常见修复模式之后,我们在第三节中提出了我们的方法,PAR。第四节实证地评价了我们的方法,第五节讨论了它的局限性。在第六节对相关工作进行了调研之后,第七节对未来的研究方向进行了总结。

2. 常见的固定模式

本节介绍从我们对人工编写补丁的手工调查中确定的常见修复模式。我们首先描述了我们是如何收集和检查大量人类书写的补丁的。然后,我们报告一列常见的修复模式。

补丁收集

为了我们的调查,我们从Eclipse JDT中收集了62,656个人类编写的补丁。我们使用Eclipse JDT,因为它有很长的修订历史(超过10年),并且在文献中得到了广泛的应用。我们使用Kenyon框架检索漏洞补丁。

常见补丁

由于我们的目标是探索人类在补丁生成中的知识,所以我们关注的是语义而不是句法变化。首先,我们检查了补丁中是否添加或删除了任何语义。其次,我们确定了每个错误的根本原因和相应补丁的解决方案。最后,将相似的斑块分组成共同的模式。

为了减少人工检查时间,我们首先使用分组收集类似的补丁。组是表示对象使用的基于图的模型。虽然不是为补丁分析而设计的,但分组可以帮助检测语义而不是语法差异。对于每个补丁,我们从两个连续的程序版本中构建了两个组:应用补丁之前和之后。然后,自动计算两组节点和边的差值。我们可以将具有相同差异的补丁收集到一个组中。尽管补丁组不一定是固定模式,但这样做可以大大减少手工检查时间。

为了识别修复模式,我们首先将补丁分为加补丁、减补丁和改变补丁。附加补丁插入新的语义特性,如新的控制流,而减法补丁删除语义特性。更改补丁只是通过替换语义特性来更改控制流。

然后我们检查了bug的根本原因,以及相应的补丁是如何专门解决这些bug的。例如,图2所示的补丁会插入一个新的if语句,以避免在fTextHoverManager为空时发生崩溃。在本例中,根本原因是“空值”,补丁通过添加新的控制流来解决它。

一些补丁处理多个原因,这些被称为复合补丁。我们将一个复合斑块划分为多个独立的斑块,并对它们进行单独分析。

修复模式

在检查这些补丁之后,我们发现了许多重复出现的类似补丁,即修复模式。表一显示了我们的调查确定的常见模式。以上8种模式几乎覆盖了我们观察到的所有斑块的30%。下面的段落描述了每个模式的细节:
在这里插入图片描述
模式:更改方法参数。
Example:obj.method(v1,v2)→obj.method(v1,v3)
描述:该模式可以修复一个bug,因为它使调用者给方法提供适当的参数。

模式:使用相同的参数调用另一个方法。
Example:obj.method1(param)→obj.method2(param)
描述:此模式更改方法调用语句中的被调用方,以修复不适当的方法调用。

模式:使用另一个参数调用另一个重载方法。
Example:obj.method(v1)→obj.method(v1,v2)
描述:此模式向现有方法调用增加了一个参数,但它实际上用另一个重载方法替换了被调用者。

模式:更改分支条件
if(a == b)→if(a == b &&c ! = 0)
描述:该模式在条件语句或三元操作符中修改分支条件。这种模式中的补丁通常只是在谓词中添加一个术语或从谓词中删除一个术语。

模式:初始化对象。
Example:Type obj;→Type obj= new Type()
描述:此模式为对象插入额外的初始化。这可以防止对象为空。

模式:添加一个“null”、“array-out- bound”和“classcast”检查器。
Example:obj.m1()→if(obj!=null){obj.m1()}
描述:这三种模式在程序中插入一个新的控制流。它们通常会添加一个新的“if(…)”语句,以避免由于程序的意外状态而引发异常。

总的来说,我们发现在人类编写的补丁中有一些常见的修复模式。由于这些主要模式在许多真实的补丁(几乎30%)中被用于修复bug,我们可以通过在自动补丁生成中利用它们来生成更成功的补丁。

3.PAR:基于模式的自动程序修复

PAR通过使用第II-C节中描述的修复模式自动生成错误修复补丁。图3展示了我们方法的概述。当报告错误时,(a) PAR首先使用现有的错误定位技术(上篇文章介绍的)识别错误位置,即可疑语句。(b) PAR使用修复模板,通过围绕错误定位编辑源代码来生成程序变体(候选补丁)。( c) 程序变体通过适应度函数评估,该函数计算通过补丁候选测试用例的数量。
如果一个候选补丁通过了所有给定的测试用例,我们的方法假设它是一个成功的补丁[7]。否则,我们的方法将重复生成补丁候选和评估步骤。

我们的方法利用进化计算技术[J. R. Koza,Genetic Programming: On the Programming of Computers by Means of Natural Selection, 1st ed. The MIT Press, Dec. 1992.]来生成程序补丁。进化计算是繁殖、评估和选择种群的迭代过程。这三个步骤的一个循环称为一代。
在这里插入图片描述
算法1显示了我们遵循这个进化计算过程的方法。我们的方法首先以适应度函数、固定模板和种群大小作为输入。在创建与给定种群大小相等的初始程序变量种群(第1行)之后,它迭代两个任务:通过使用固定模板生成新的程序变量(第3行,复制)和根据给定的适应度函数选择最顶端的变量(第4行,评估和选择)。当任何程序变体通过所有给定的测试用例时(第5行),或者当它满足预定义的终止条件时(参见第IV-A节),迭代将停止。算法1返回一个通过所有测试用例的程序变体,作为给定错误的成功补丁(第6行)。

我们采用这种进化计算过程,因为它是有效地自动生成补丁[C. Le Goues, M. Dewey-V ogt, S. Forrest, and W. Weimer, “A systematic study of automated program repair: Fixing 55 out of 105 bugs for $8 each,” inICSE ’12.],有效地探索大量的程序变体。将进化计算过程应用于程序修复是由Weimer等人[W. Weimer, T. Nguyen, C. Le Goues, and S. Forrest, “Automatically finding patches using genetic programming,” inICSE ’09.]和Arcuri等人[A. Arcuri and X. Yao, “A novel co-evolutionary approach to automatic software bug fixing,” inCEC ’08.]首创的。

本节的其余部分描述PAR中使用的故障定位、修复模板和适应度函数的详细信息。

故障定位

为了确定故障位置,PAR使用基于测试用例[8]的统计故障定位。该技术假设,失败的测试用例访问的语句比其他语句更有可能是缺陷。具体来说,这种技术为程序中的每个语句赋值。这个值表示怀疑的程度。我们的方法使用该值来决定是否应该修改语句。

这种本地化技术首先执行两组测试用例:通过和失败。然后,该技术记录两个测试用例组的基于语句的覆盖率。比较每个语句的覆盖范围会得到以下四种结果之一:1)两组都覆盖,2)仅通过组覆盖,3)仅失败组覆盖,4)不属于任何一组。我们将0.1赋给具有第一个结果的语句,将1.0赋给具有第三个结果的语句。否则,0.0被赋值。

我们的方法使用分配的值来确定每个语句被编辑的概率。例如,1.0意味着语句总是被编辑,而0.1意味着它在10代中被编辑一次。我们采用了[8]中使用的简单故障定位技术,但可以使用其他故障定位技术来确定哪些语句需要突变。

修复模板

修复模板是重写程序抽象语法树(AST)的程序编辑脚本。每个fix模板定义了编辑程序的三个步骤:1)AST分析,2)上下文检查,3)程序编辑。AST分析步骤扫描给定程序的AST,并分析给定的故障位置及其相邻位置。上下文检查步骤通过检查分析的AST来检查给定的程序是否可以被模板编辑。如果AST是可编辑的,那么我们的方法将基于模板中预定义的编辑脚本重写给定程序的AST(程序编辑步骤)。

在本节中,我们首先描述如何创建和应用修复模板。然后,我们提供PAR中使用的修复模板列表。

1)创建修复模板:我们首先仔细检查每个模式中的补丁(表I)。由于程序补丁改变了程序的AST,我们可以计算应用补丁前后的AST差异。我们将这些差异转换为修复模板中的编辑脚本。请注意,相同模式中的补丁可能具有不同的ast,即使它们的语义更改是相同的。我们试图通过识别最常见的变更集来在修复模板中概括这些变更。

对于上下文检查步骤,我们从补丁中提取上下文信息,比如是否存在数组访问,这对于检查我们的方法是否可以使用修复模板编辑程序是必要的。然后,我们将AST分析步骤添加到模板中,该步骤在错误位置扫描程序的AST并提取AST元素,比如变量名和类型。我们通过查找修复模式来标识这些元素。这些元素在上下文检查和程序编辑步骤中使用。
在这里插入图片描述

例如,一个修复模板Null Pointer Checker如图4所示。这个模板来自于第II-C节中所示的“添加一个空检查器”模式。要创建这个模板,我们首先概括模式中的补丁如何更改程序的ast。通常,它们插入包含故障位置的if()语句。详细的程序编辑脚本显示在第13 - 26行(程序编辑步骤)中。**然后,我们确定程序编辑所必需的通用上下文信息,这些信息将被添加到上下文检查步骤中。**对于这个模板,上下文检查步骤验证故障位置必须至少有一个对象引用(第9-10行)。最后,我们添加AST分析步骤(第6行),该步骤收集给定故障位置中的所有对象引用。
在这里插入图片描述

以同样的方式,我们创建了10个修复模板,如表II所示。尽管创建修复模板需要手工工作,但这只是一次性的成本,可以重用模板来修复其他类似的错误。我们在第四节中的评估确认,从Eclipse JDT中的修复模式创建的模板可以成功应用于修复其他程序(如Rhino)中的错误。

应用修复模板:PAR将模板应用于每一个单独生成中的故障位置。如第III-A节所述,每个故障位置都有一个选择概率,PAR使用该概率来确定将由固定模板修改哪些位置。由于我们的方法遵循进化计算过程,每个位置都可以在几代中由多个模板编辑。

当应用修复模板时,PAR首先接受一个程序和一个错误位置作为输入值。然后PAR在模板中执行AST分析步骤,以收集必要的信息,如变量类型和方法参数。通过使用收集到的信息,PAR运行上下文检查步骤,以确定程序是否具有应用给定模板的适当上下文。如果上下文检查通过,我们的方法将执行模板的程序编辑步骤,重写给定错误位置的程序的ASTAST重写包括添加节点、替换参数和删除谓词。修改后的程序是PAR视为补丁候选的新程序变体

有可能有几个模板通过了给定故障位置的上下文检查。 在本例中,PAR随机选择一个传递的模板

图5显示了应用空指针检查器模板(参见图4)从NativeRegExp生成一个新的程序变体的示例。修复Rhino Bug #76683。在本例中,图5(a)中的第4行是一个故障定位。我们的方法检查位置是否包含任何对象引用,以确保空指针检查器模板是适用的。因为位置有两个引用,所以模板是适用的。然后,我们的方法通过在模板中应用编辑脚本生成一个新的程序变体:插入一个包含错误位置的if语句,如图4所示。因此,一个新的程序变体(即候选补丁)有一个谓词来检查两个对象是否不为空。
在这里插入图片描述

3)修复模板列表:表II列出了我们方法中使用的所有10个修复模板。
Parameter Replacer模板派生于表1中的“更改方法参数”模式。它的编辑脚本可以更改方法调用的参数。**替换参数的候选参数是从故障位置的目标方法调用的相同范围中收集的,它们必须具有兼容的类型。**这些候选参数根据给定程序抽象语法树中给定故障位置的距离(节点的数量)进行排序。我们的方法根据距离选择其中一个,并在给定的错误位置替换语句的方法参数。距离较短的参数更有可能被选择。

Method Replacer模板在方法调用语句中替换被调用方的名称。该模板包含一个上下文检查步骤,该步骤扫描具有相同参数和返回类型的其他方法调用在相同的程序范围内。如果有几个候选方法,我们的方法随机选择其中一个来替换给定故障位置中的方法名。该模板派生自表I中的“调用另一个具有相同参数的方法”。

我们创建了Parameter Adder 和 Remover 模板,以使方法调用具有更多或更少的参数。只有当目标方法有重载的方法时,这个模板才适用。我们的方法随机选择一个可用的重载方法。当添加参数时,模板的脚本指定如何扫描给定错误位置的相同范围,以找到与新位置兼容的变量。当删除参数时,我们的方法只是过滤掉未包含在所选重载方法中的参数。这个模板派生自表I中的“使用另一个参数调用另一个重载方法”模式。

Expression Replacer 、 Expression Adder 和 Remover派生自修改条件语句或循环语句中的谓词的补丁,如if()或while()。我们收集的许多补丁都会更改或引入新的谓词来修复表i中“更改分支条件”模式所描述的错误。要替换或添加谓词,我们的方法首先扫描给定故障位置相同范围内的谓词。这些谓词根据它们与给定程序的AST中的错误位置的距离进行排序。我们的方法根据距离选择其中一个谓词,并将谓词替换或添加到错误位置的目标语句中。当从给定谓词中删除一个项时,我们的方法随机选择一个要删除的项。

我们创建了Object Initializer,它将一个初始化语句插入到错误位置。该语句的初始化式是不带参数的基本构造函数。该语句防止变量为空。这个模板派生自表I中的“初始化对象”模式。

Null Pointer Checker,Range Checker,Collection Size Checker, 和Class Cast Checker 派生自表I中相应的模式。通过使用这些模式,我们的方法可以插入一个新的if()语句来检查是否有任何异常状态,如空指针、索引越界或错误的类转换

适应度评价

我们方法的适应度函数取一个程序变量和测试用例,然后计算一个值,表示通过该变量的测试用例的数量。这个适应度函数适用于[7],[8]。所有测试用例都是从给定程序的相应代码存储库中收集的。结果适应度值用于评估和比较一个群体中的程序变体:“哪个变体比其他的更好?”PAR根据程序变量的适应度值,使用竞赛选择方案[18]选择程序变量(算法1第4行)。

4.评价

在本节中,我们将对我们的方法进行实验评估。具体来说,我们的实验旨在解决以下研究问题:有多少错误被成功修复?哪种方法可以生成更可接受的错误补丁?

实验设计

为了评估PAR,我们从开源项目中收集了119个真实的bug,如表III所示。对于每个错误,我们都应用了PAR和GenProg[8]来生成补丁。然后,我们检查了每种方法成功修复了多少错误(RQ1)。我们还进行了用户研究,比较了两种方法的patch质量(RQ2)。

表三中的六个项目是用Java编写的。我们选择这些项目有两个主要原因。首先,Java是最流行的编程语言之一,所以有很多Java项目。其次,由于JUnit和其他基于Java的测试框架,Java项目经常包含许多测试用例。

许多开源项目维护自己的问题跟踪系统,如Bugzilla和JIRA。我们的实验选择了六个开源项目,包括Mozilla Rhino、Eclipse AspectJ、Apache Log4j和Apache Commons (Math、Lang、Collections),因为它们在文献[6]、[19]、[20]中经常使用,并且有维护良好的bug报告。我们尝试搜索他们相应的问题跟踪器来寻找可重复的bug。其中,我们每个项目随机选择15 - 29个bug,因为有些项目bug太多。虽然我们在bug收集上投入了最大的精力,但是收集到的bug并不能代表整个bug。然而,据我们所知,119是迄今为止自动补丁生成评估中最大的数字。

对于每个错误,我们从它们相应的代码存储库中收集了所有可用的测试用例,包括复制错误的失败测试用例。项目经常包括许多测试用例,因为开发人员不断地编写和维护测试用例[21]。在我们的实验中,我们使用了所有的测试用例,如表III所示,来验证一个候选补丁[6],[7],[10]。

当对每个错误应用PAR和GenProg时,我们运行了100次。Arcuri和Briand推荐至少1000次运行来评估随机算法[22],但由于时间限制,我们进行了100次运行。我们的实验总共有23,800次(100×119 bugs×2方法)

每次运行超过10代或8个小时(时钟时间)时就会停止,此时我们假设运行无法生成成功的补丁。为了公平比较,我们使用了与[8]中完全相同的终止条件。

此外,我们使用与GenProg相同的种群大小(=40)。我们还使用了相同的参数,如[8]中建议的GenProg运行的突变概率。、

这个实验是在几台机器上进行的,两台六核3GHz cpu和16GB RAM。当运行测试用例时,我们并行执行它们以加速实验。此外,如[7]所述,我们记住了程序变体的适应度值,以防止再次对同一变体进行重新评估。

RQ1:修复能力

表IV显示了PAR和GenProg的patch生成结果。PAR成功修复了119个bug中的27个,而GenProg只成功生成了16个bug的补丁。
在这里插入图片描述
在修复的bug中,有5个(Rhino的4个+ Math的1个)被PAR和GenProg修复了。请注意,这两种方法为这五个错误生成了不同的补丁。然而,这些补丁成功地通过了所有给定的测试用例。这些补丁将用于我们的补丁可接受性比较研究(第IV-C节)。

使用修复模板可以有效地修复bug。Range Checker模板通过插入if()语句为Rhino bug #114493生成了一个补丁。这个补丁如图1(d)所示。AspectJ错误#131933可以由类强制转换检查器模板修复,因为它的错误语句使用了无效的强制转换。“Expression Replacer”和“Expression Adder and Remover”模板可以为两个Rhino错误(#192226和#222635)生成成功的补丁,这两个错误在有bug的if()语句中有无效的条件表达式。

注意,**我们从Eclipse JDT中确定了常见的修复模式,并使用它们创建修复模板。**PAR利用这些模板为其他项目(如Mozilla Rhino和Apache Commons Math)的bug生成补丁。这表明从一个项目中学到的修复模板可用于其他项目。

虽然我们在本文中只引入了少量的修复模板,但这些模板显然扩展了补丁生成的可修复性。从现有补丁中确定更多的修复模板可能会进一步提高可修复性。这仍然是未来的工作。

在我们的评估中,GenProg修复了大约10%的bug,而最近的一项系统研究[8]报告称,它可以修复用C语言编写的受试者中近50%的bug。也许,这是因为GenProg的突变操作符在Java程序中可能不太有效。大多数Java程序倾向于将其功能分解为小型类和方法。这限制了GenProg的变异操作符中可以使用的语句的数量,因为它收集和使用给定故障位置相同范围内的语句。另一方面,C程序通常有许多全局变量和更大的方法。这可能为在mutation中使用bug修复语句提供了更多的机会(GenProg的作者展示了一个针对全局变量访问器崩溃的补丁,作为[8]中成功案例的典型例子)。然而,这并不意味着我们的主题选择对GenProg有偏见。在我们的实验中,PAR具有与GenProg相同的约束条件。此外,我们的方法并不局限于Java程序。

RQ2:可接受性

在本节中,我们度量PAR和GenProg生成的补丁的可接受性。由于所有成功的补丁都通过了所提供的测试用例,所以系统地选择或多或少可以接受的补丁是很有挑战性的。相反,我们呈现了匿名的补丁,并要求人类受试者选择或多或少可以接受的补丁。

为了回答RQ2,我们给出了以下两个null假设。
H10: PAR和GenProg生成的补丁没有可接受性差异。
H20: PAR生成的补丁与人类编写的补丁没有可接受性差异。

对应的备选假设为:
H1a: PAR生成的可接受补丁比GenProg多。
H2a: PAR生成的补丁比人类编写的补丁更容易接受。

实验对象

在这项研究中,我们招募了两组不同的参与者:计算机科学(CS)学生和开发人员。学生小组由17名拥有2 - 5年Java编程经验的软件工程研究生组成。对于开发团队来说,有68个开发人员参与了我们的研究。我们从像“stackoverflow.com”和“coderanch.com”这样的在线开发者社区,以及像韩国领先的互联网软件公司Daum这样的软件公司招募了这些开发者。他们被要求参加这项研究,前提是他们有Java编程经验。

研究设计

我们的用户研究有五个阶段。在每个会话中,我们展示了PAR和GenProg修复的五个错误中的一个,如表4所示。每个会话都详细解释了为什么一个错误是有问题的,并给出了相应的错误报告的链接。然后,会话列出了三个匿名补丁:一个人为编写的补丁,以及PAR和GenProg生成的补丁。每个参与者被要求作为补丁审稿人对它们进行比较,并根据可接受性报告它们的排名。

在这项研究中,我们建立了一个基于网络的在线调查引擎,它以随机的顺序显示五个会议。我们把引擎的超链接给了学生和开发人员组。在我们的调查开始时,我们强调所呈现的补丁可以通过从相应的项目中收集的所有给定的测试用例。没有时间限制,所以参与者可以花足够的时间检查和排名补丁。

结果–学生

17个学生参与者分配的平均补丁可接受性排名如表V所示。在所有五个错误中,PAR生成的补丁的排名始终高于GenProg生成的补丁。PAR生成的patch平均排名为1.57,标准差为0.68。GenProg生成的patch的平均排名和标准差分别为2.67和0.64。PAR与GenProg的排名差异有统计学意义(p-value =0.000<0.05)。统计检验采用Wilcoxon符号秩检验[23],因为我们比较了两个相关样本,这些样本是非参数的。根据结果,我们可以拒绝学生群体的原假设H10。
在这里插入图片描述
一些学生认为PAR生成的补丁与人类编写的补丁一样好,甚至更高。然而,这并不一定意味着PAR生成的补丁优于人类编写的补丁(平均为1.72,标准差为0.67)。排名差异无统计学意义(p-value =0.257>0.05)。因此,对于学生群体,我们不能拒绝原假设H20。

结果–开发者

68个开发人员的调查结果如表VI所示。与表V类似,PAR生成的补丁的排名比GenProg生成的补丁的排名要高,除了Rhino Bug #114493。对于这个错误,开发人员可能认为GenProg生成的补丁比PAR生成的补丁更容易接受,因为当lhs变量未定义时,它会指定一个默认值(见图1(b))。然而,这并不能代表总体结果。
在这里插入图片描述
PAR和GenProg的平均排名分别为1.82和2.36。它们的标准差值分别为0.80和0.90。由于PAR和GenProg之间的排名差异具有统计学意义(p-value =0.016<0.05),我们可以拒绝开发人员组的null假设H10。

人工书写斑块的平均排序和标准差分别为1.81和0.70。PAR生成的patch与人工编写的patch之间的排序差异无统计学意义(p-value =0.411 >0.05)。因此,原假设H20不能被拒绝。

我们的两个比较研究(学生和开发人员)一致表明,PAR生成的补丁平均排名高于GenProg生成的补丁。此外,该结果具有统计学意义。在PAR和人类书写的补丁之间,两项研究都显示了不同的结果,但排名差异在统计上没有显著性。这意味着我们的方法可以生成比GenProg更可接受的补丁。此外,PAR生成的补丁可以与人工编写的补丁相媲美。

间接块比较

第IV-C3和IV-C4部分的用户研究显示了直接的补丁比较结果,但结果仅限于两种方法修复的5个bug。

为了解决这个问题,我们进行了一项用户研究,**通过将PAR(27个补丁)和GenProg(16个补丁)生成的所有43个补丁与相应的人类编写的补丁进行比较,间接比较它们的可接受性。**为此,我们构建了一个基于网络的在线调查引擎。**每个调查会话都显示了一对匿名补丁(一个来自人类,另一个来自PAR或GenProg,针对相同的bug),以及相应的bug信息。**参与者被要求选择更容易接受的补丁,如果他们是补丁评审者。此外,参与者可以在可接受补丁和不确定是否确定可接受补丁之间进行选择。我们随机向参与者展示了所有43个环节,他们可以回答任何他们想回答的环节。

在这项研究中,我们通过在软件开发者社区和个人twitter上发布在线调查邀请来招募参与者。我们还向2012年春季在香港科技大学学习软件工程课程的CS本科生发送了邀请邮件,因为他们有Java编程知识。在所有的调查邀请中,我们都明确表示只邀请有Java经验的开发人员/学生。

调查结果见表七。总共168名(72名学生和96名开发人员)参与者回答了965个问题。会话响应速率(PAR:GenProg = 621:344)与每种方法生成的成功补丁的速率(PAR:GenProg =27:16)相似。这意味着我们的调查是随机进行的。

在这里插入图片描述
如表VII(a)所示,在621个会话中,有130个(21%)参与者选择了由PARas生成的补丁,在175个(28%)会话中选择了“两者都是可接受的”。总的来说,参与者在305个(49%)会话中选择了PAR生成的补丁作为可接受的补丁(PAR: 21%+ both: 28%)。另一方面,参与者在344个session中选择了108个(32%)由GenProg生成的patch作为可接受的patch (GenProg: 20%+both: 12%),如表VII(b)所示。

间接比较结果还表明,PAR生成的补丁更容易接受。

5. 讨论

本节讨论PAR生成的不成功补丁,并识别对我们的实验有效性的威胁。

不成功的补丁

在表IV中,119个bug中有92个没有被我们的方法修复。我们检查了补丁生成失败的主要原因,并确定了两个主要挑战:分支条件和没有匹配模式,如表VIII所示。我们详细讨论每个挑战。
在这里插入图片描述
**分支条件表示PAR不能通过使用修复模板生成谓词来满足故障位置的分支条件。**由于这个原因,PAR无法修复26个(28%)错误。例如,当范围变量被分配给NativeCall或NativeWith类型对象时,就会发生Rhino错误#181834。要修复此错误,必须在错误位置之前插入适当的控制语句。这个控制语句有一个检查范围类型的谓词。从头生成这个谓词很有挑战性。

**没有匹配的模式表明PAR不能为错误生成一个成功的补丁,因为没有修复模板具有适当的编辑脚本。**由于这个原因,PAR无法修复66(72%)错误。例如,为了修复AspectJ Bug #109614,其人工编写的补丁在故障位置之前添加了一个控制语句:" if(sources[i] instance of ExceptionRange)…"。但从故障定位中无法推断出ExceptionRange。为了让PAR修复此错误,必须创建匹配的修复模板。

我们检查了GenProg修复的11个bug,但是PAR没有修复。我们的方法无法修复11个bug中的7个,因为这些bug没有匹配的修复模式。这意味着如果我们添加更多的修复模式,这些bug就可以通过我们的方法修复。添加更多的修复模式仍然是未来的工作。其他4种bug属于分支条件范畴。

威胁的有效性

我们确定了以下威胁我们实验有效性的因素。

系统都是开源项目:我们只从开源项目中收集bug来检查我们的方法。因此,这些项目可能不能代表闭源项目。闭源项目的补丁可能有不同的模式。

我们用户研究的一些参与者可能不完全合格:在我们对开发者小组的调查邀请中,我们明确了只有开发者才能参与调查。但是,我们不能完全核实调查参与者的资格。

Weimer等人[7]提出了基于遗传规划的自动补丁生成技术GenProg。这种方法随机改变有bug的程序,以生成几个可能的候选补丁的程序变体。通过运行通过和失败的测试用例来验证变量。如果一个变体通过了所有的测试用例,它就被认为是给定错误的一个成功补丁。2012年,作者扩展了他们之前的工作,增加了一个新的突变操作,替换和删除开关操作[8]。并以105只真实bug[8]进行了系统评价。虽然评估显示,GenProg修复了105个bug中的55个,但GenProg可以生成不被开发人员接受的无意义补丁,如第四节- c所示。

Fry等人进行了一项人类研究,通过测量patch的可维护性[24]来间接测量GenProg生成的patch的质量。他们向参与者展示补丁,并询问由Sillito、Murphy和Volder[25]开发的与可维护性相关的问题。此外,他们向参与者展示了机器生成的更改文档[26]和补丁。他们发现,机器生成的补丁[8]与机器生成的文档[26]在可维护性方面可与人类编写的补丁相媲美。我们还比较了机器生成的补丁和人类编写的补丁。然而,我们没有问可维护性相关的问题,而是问参与者哪个补丁更容易/更不容易被直接比较。此外,我们还比较了GenProg和PAR两种不同的patch生成方法生成的patch。

Demsky等人关注的是避免数据结构不一致[27],[28]。他们的方法是使用正式规范检查数据结构的一致性,并插入运行时监视代码以避免不一致的状态。然而,这种技术提供了变通方法而不是实际的补丁,因为它不直接修改源代码。

Arcuri等人介绍了一种自动生成补丁的技术[5]。虽然他们也使用遗传编程,但他们的评估仅限于气泡排序和三角形分类等小程序,而我们的评估包括开源软件中的真实bug。他们的方法依赖于正式的规范,而我们的方法不需要。

Wei等人提出了一种基于契约的补丁生成技术[10]。这种技术也依赖于规范(即契约)。此外,这种技术只能生成四种程序变体。这些变体只检查违反合同的情况,而我们的方法将人类编写的补丁一般化,并生成各种程序变体。

PACHIKA[6]利用对象行为模型。PACHIKA是在Mozilla和Eclipse的26个bug上进行评估的。然而,在他们的评估中,PACHIKA仅为26个错误中的3个创建了成功的补丁,因为它只生成有限数量的程序变体(即记录的对象行为)。我们的评估也包括这26个错误(见表III), PAR成功修复了15个错误,其中包括PACHIKA修复的3个错误。

Martinez和Monperrus从大约90000个修复[15]中确定了常见的程序修复操作(模式)。他们声称,最常见的修复操作是语义改变模式,比如“附加功能”。但是,它们识别的模式太抽象和粗粒度,不能用于自动生成补丁。第III-B3节中描述的修复模板非常具体,可以用于自动生成补丁。

SYDIT[29]自动从程序更改中提取编辑脚本。它的限制是,用户必须指定一个程序更改来提取编辑脚本和一个目标程序来应用它。此外,SYDIT不能通过多次程序更改来提取(通用的)编辑脚本。SYDIT的作者提出了一种名为LASE[30]的改进技术。这种技术可以进行多次更改,以提取更通用的编辑脚本并自动查找目标程序。PAR可以利用这些技术自动创建修复模板。

6.总结

在本文中,我们提出了一种新的补丁生成方法,PAR,从人类编写的补丁中学习来解决现有技术(如GenProg[7],[8])的局限性:生成无意义的补丁。我们首先手动检查人工编写的补丁,并确定常见的修复模式,然后将其用于自动生成补丁。我们在119个真实bug上的实验结果表明,我们的方法成功生成了27个bug的补丁,而GenProg成功生成了16个bug的补丁。为了评估生成的补丁是否可以用于修复bug,我们对253名参与者(89名学生和164名开发人员)进行了用户研究。这项研究表明,我们的方法比GenProg生成了更多可接受的补丁,而且我们的补丁与人类编写的补丁相当。

我们基于模式的方法可能有助于改进其他自动生成补丁的技术。例如,仅基于契约的技术[10]就可以为每个错误生成四种变体,但是可以使用修复模板为这类技术生成更多的程序变体。PAR还可以用于在基于模型的技术[6]中生成更多的程序变体。

我们未来的工作包括自动修复模板挖掘和平衡测试用例生成。首先,尽管所建议的修复模板成功地用于生成补丁,但是需要更多的修复模板来有效地修复更多的bug。我们计划研究更多人工编写的补丁,并开发自动算法来提取修复模板。第二,我们的方法需要测试用例来评估程序的变体,但是通常很少有失败的测试用例可以用于错误。由于测试用例中的不平衡可能导致不准确的补丁评估,我们将通过利用现有的自动测试技术[31]、[32]、[20]来开发失败测试生成技术。

所有在本文中使用的材料和详细的补丁生成结果都可以在https://sites.google.com/site/autofixhkust/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值