Automatic Software Repair: A Survey 自动软件修复:综述 (1)

原论文:Automatic Software Repair: A Survey 

原作者:Luca Gazzola, IEEE会员 Daniela Micucci, IEEE高级会员 Leonardo Mariani

2017年10月发表在IEEE Transactions on Software Engineering

原文链接:https://ieeexplore.ieee.org/document/8089448

译者:ClarkC.

此翻译已经原作者授权,翻译内容的准确性与原作者无关,引用请注明出处

感谢原文作者Leonardo Mariani教授提供翻译授权。鉴于原文很长,该翻译将分为六次上传。本次包含原文第1~4节的内容。原文中的参考文献可至附在开头与结尾的原文链接查看。

 

自动软件修复:综述

Luca Gazzola, IEEE会员 Daniela Micucci, IEEE高级会员 Leonardo Mariani

2017年10月发表在IEEE Transactions on Software Engineering

摘要尽管现代软件应用程序的复杂性和规模在不断增长,但其依然必须满足严格的发行要求,这就需要较短的bug修复和维护周期,对负责及时生产高质量软件的开发人员产生了巨大压力。为了减少开发人员的工作量,最近几年,作为有效修复和维护软件的解决方案,修复和愈合技术(repairing and healing techniques)得到了广泛研究。特别是,修复方案已经能够针对软件程序中可能存在的几类错误自动生成有用的修复。

对一系列算法、技术和启发式方法进行了集成、实验和研究后,形成了一个异构的、有条理的研究框架,自动修复技术正在这个框架中迅速发展。

本文通过对108篇关于自动软件修复技术的论文的调查,阐述了其算法和方法,并和典型实例进行了对比,讨论了目前所面临的挑战和已报告的实验论据。

 

索引术语:自动程序修复(Automatic Program Repair)、生成和验证(Generate and Validate)、基于搜索(Search-Based)、语义驱动修复(Semantics-driven repair)、按构造更正(Correct by Construction)、程序合成(Program Synthesis)、自修复(Self-Repairing)。

 

1. 引言

目前调试软件故障仍然是一个困难,耗时多且高成本的过程。例如,最近的研究表明,调试过程通常约占软件产品总体开发成本的一半[1],[2]。

影响调试成本的因素很多,但其中最大的因素是识别和消除故障仍需要大量的人工作业。尤其是,调试过程需要分析和理解出错的执行,确定出错原因,实施修复,然后验证修复后的程序是否正常,也就是说,问题已经解决,并且没有引入任何副作用[3],[4],[5]。这些工作大多是人工执行的,或者部分有工具支持。

当前,调试的自动化主要涉及对可能会出错的语句的识别[6],[7],[8],隔离可能导致故障的特定输入或应用程序状态 [9],[10],[11],以及检测可能导致了故障的异常事件[12],[13],[14],[15]。

隔离可能出现故障的语句的技术会向测试人员报告按可疑程度排序的语句列表,该列表由执行每个语句的失败和通过测试用例的数量来确定[6],[7],[8]。一般来说,最可疑的语句被多个失败的测试执行到,几乎不会被通过的测试执行。

识别特定输入和可能触发故障的特定状态的技术通常都是基于有名的Delta调试技术[9]。通常,通过迭代优化,减少输入[10]和状态空间[11],应该能够识别会触发failure的,最小的输入和应用程序状态中无法消除的最小部分。此信息与了解可能触发故障的条件有关。

最后,异常检测技术可以检测应用程序在故障期间执行的操作,但不能检测正常执行期间的操作。异常检测技术通常利用规范挖掘方法[16],[17],[18],[19]来自动生成代表应用程序合法行为的模型,然后使用这些模型来分析失败的执行并确定异常事件[12],[13],[14],[15]。

所有这些技术都提供了有关故障可能位置,引起故障的输入和状态以及故障期间执行的异常操作的有用信息。但是,开发人员仍然必须对失败的执行进行相关的分析,以准确地识别必须修复的错误。此外,这些技术并不能帮助开发人员合成适当的修复程序。

最近,研究人员集中于一类新方法,即程序修复技术[20],[21],[22],[23],[24],[25],[26],[27],[28] ,[29]。这些技术的关键思想是通过生成一个实际的修复程序来尝试自动修复软件系统,该修复程序可以在最终被接受之前由测试人员验证,或者进行调整以适合系统。使用这些技术的好处在于,修复程序既可以解释故障的原因,又可以为问题提供可能的解决方案,从而减少了识别和纠正故障所需的工作量[30],[31]。

由于程序修复技术有潜力极大地减少调试工作,因此吸引了许多研究人员的兴趣,他们提出了多种方法来修复不同条件和假设下的不同类别的故障。该领域已经有了一些重要成果,但随之而来的则是必须面对的相关挑战。

Khalilian等人[32]对该领域的贡献进行了初步分析,他们比较了三种修复技术。Monperrus等人[33] [34]讨论了针对预期结果的修复和自愈(self-healing)技术。

本文通过对现有方法进行综合评述,对修复技术的能力进行严格的分析,对迄今为止所取得的成果进行调查,并确定未来研究社区会面临的主要开放挑战,为程序修复技术的研究做出了贡献。

本文的结构如下。第2节描述了我们为这篇综述选择论文所遵循的步骤,并提供了所选论文的统计数据。第3节介绍了软件修复方案,并与软件自愈方案进行了对比。第4节介绍了一个综合示例,该示例由同一程序的多个错误版本组成,这些错误版本在本文中用于说明修复技术。第5节介绍了已被用作程序修复方案一部分的故障定位技术。第6节介绍了生成修复的算法。第7节将讨论范围扩展到推荐修复的技术,这些修复经过少量的人工操作就能应用于实际。第8节介绍了迄今为止获得的主要经验性发现。第9节讨论了开放的挑战和未来的研究方向。 第10节做出总结性评论。

 

2. 论文选择

我们通过在ACM和IEEE Explorer数字图书馆和Google学术库中搜索软件修复论文中常用的以下术语来选择论文:“program repair“,”software repair”,”automatic patch generation”,”automatic fix generation”,”generate and validate”,以及”semantics driven repair”。 我们在2017年1月初进行了论文选择。对于每个词条的查询,我们提取按相关性排序的前200个作为参考,但有些查询的结果少于200个。最终我们得到了3,262份需要分析的参考。删除重复的条目后,我们共获得了2,573份参考文献。

我们通过检查摘要,引言和结论,并扫描其余部分,来手动分析所有的参考文献,以快速筛除明显与本文主题无关的论文。筛查后,我们获得了107篇可能相关的论文。我们仔细阅读了这些论文,根据第3节中讨论的概念框架来确定它们的相关性。这样一来,总共剩余了85篇相关论文。为了尽可能的全面,我们还添加了在搜索中错过的但在我们选择的论文中被引用的相关论文,共有23篇 [26],[35~56]。总共选择了108篇论文作为本篇综述的材料。

我们分析了所选论文按年份和按出版方的分布。图1显示了每年在自动程序修复领域发表的论文数量。在该领域的一些早期论文之后,我们可以注意到,从2005年开始,每年都有越来越多的论文发表。如果我们把21世纪初发表的论文数量与过去几年发表的论文数量进行比较,可以注意到发表论文数量呈现数量级的增长。这表明,研究界对这一领域的兴趣越来越大。

 

图1:1996~2016每年的论文发布数量

图2显示了每个出版方发表的论文数量。我们列出了发表了至少两篇论文的出版方,在“其他”列中包含了所有只有一篇论文的出版方。我们可以注意到软件工程会议的盛行,ICSE是目前包含相关论文数量最多的出版方。在与遗传计算(例如,GECCO)和编程语言(例如,PLDI和OOPSLA)有关的会议上也有发表。这是自动程序修复方法的固有特点的结果,这些方法通常基于搜索和合成技术,可以针对多种语言和环境。

 

图2:各出版方发表的论文数量

最后,我们检查了期刊和会议之间论文的分布情况,发现只有8.3%的论文发表在期刊上,剩余的91.7%出现在其他场所,例如会议和研讨会。

 

3. 软件修复

自动为有缺陷的程序生成修复的问题,与自动生成遵循给定规范的程序这一更普遍的问题有关。

这个问题是自动编程的一个实例,其过程是由机器操作,将关于一项任务的规范翻译成用于执行该任务的机器可执行程序[57],[58]。尽管从计算机科学的早期就已经考虑了自动合成软件程序的问题,并且至今仍然是研究的活跃领域[59],[60],[61],但是直到最近才考虑了自动修复故障程序的问题。

一些方法开创了自动程序修复领域。Stumptner和Wotawa引入了定义故障模式的想法,以捕获VHDL程序中的常见故障[46],[62]。故障模式用于自动检查程序的错误是否与任何已知故障一致,并尝试根据识别到的故障模式使用策略来自动修复程序。Staber等人研究了如何自动替换系统的组件,使其满足给定的线性时序逻辑(Linear Temporal Logic,LTL)规范[47]。有趣的是,他们将修复过程定义为必须修复的系统与环境之间的博弈,环境对系统不利,导致修复失败[43],[47]。Weimer研究了如何修复违反安全策略规范的程序,该规范在正在修复的程序的控制流程图中指定了允许的路径。他的方法找到了对程序产生的方法调用要执行的最小修改集,以使其满足规格要求[31]。其他研究人员使用人工智能技术[48]或基于模板的策略[49]扩展了模型检查以修复程序。

在A.Arcuri等人[35],[40],[63]和W.Weimer等人[20],[64],[65]发表了开创性的研究成果之后,软件修复的研究开始激增,他们是第一批成功利用基于搜索的算法生成修复的人。

在本节的其余部分中,我们将介绍与软件修复领域相关的基本概念,并提出一个概念框架,用以说明软件修复技术通常是如何工作的,以及它与软件自愈技术的关系。

 

3.1 基本概念

自动处理程序故障的两种众所周知的方法是软件愈合(healing)和软件修复(repairing)技术。尽管原理上相似,但是它们具有不同的目的并使用不同的解决方案来解决故障。

软件愈合的解决方案可在现场检测软件故障,并通过进行必要的调整以恢复系统的正常运行来做出响应[66],[67],[68]。这些调整不是在源代码级别上进行的,而是应用于已部署的运行中的应用程序,以防止或减轻故障。在同一软件实例上多次发生类似故障可能会多次触发相同的修复过程。

软件修复的解决方案可以检测软件故障,定位可以应用修复程序的位置,并进行必要的调整以修复故障,从而防止由同一缺陷引起的更多故障[20],[21],[22]。调整是在内部(例如在测试和设计时)生成的,并部署在源代码级别。

这两种技术可能会涉及人为干预。在自动软件愈合和修复中,人员仅监督其过程。同时,当完全不涉及人工时,这两种技术分别称为自愈和自修复。

这两种技术都通过执行某些操作来对故障做出反应。我们要区分愈合操作和修复操作。愈合操作是指在运行时将出错或将要出错的执行转换为成功执行所应用的操作。修复操作是指在程序源代码上执行的操作,用于消除导致故障(failure)的缺陷(fault)。

软件愈合和修复技术可能会生成规避方案(workarounds)修复(fixes)。在这种情况下,规避方案是针对软件错误的临时解决方案[69],而修复(或补丁)是针对软件错误的永久解决方案[70]。规避方案并不是用来实现最佳解决方案的。规避方案由可以快速生成和部署的解决方案组成,同时等待开发人员进行永久修复。规避方案可以应用于多个级别,也可以应用于不同的制品。例如,其可以更改程序代码,但也可以更改系统的体系结构或更改其配置,或者可以简单地更改特定的执行,比如让用户中断不需要的无限循环[71]。相反,修复一定会改变程序源代码,并且在调试和修复软件故障时,希望实现与开发人员生成的修复有着相同质量的解决方案。

 

3.2 概念框架

这一小节中,我们将介绍一个涵盖愈合和修复解决方案的概念框架,并说明这两种方法之间的共性和差异。

虽然这两种方法都用于处理故障,但它们的解决方案有着不同的目标。软件愈合解决方案关注的是在出现故障的情况下让软件可以使用[66],[67],[68]。通常执行愈合操作来屏蔽故障[69],[72],[73]或将其影响降至最小[74],[75]。该技术的主要目标并不是识别并永久消除导致故障的缺陷。与之相反的是,软件修复解决方案关心的是修复故障,而不一定防止故障出现 [20],[21],[22]。事实上,故障是可以容忍的,在出错的执行期间提取的信息甚至被修复解决方案所利用,以便更好地识别和修复故障。由于这些独特的特性,愈合解决方案被用于在现场提高软件系统的可靠性和弹性,而修复解决方案被用于在实验室帮助开发人员进行错误修复。

图3以图形方式说明了愈合和修复过程中涉及的活动,分别在图的左侧和右侧表示。这两个过程都从一个有错误的程序开始,该程序至少在某些执行过程中偏离了预期的行为。这两个流程共有的故障检测任务负责将程序的执行过程分为故障执行和无故障执行。

 

图3:Healing和Repairing的过程

故障检测的工作方式可能有所不同,具体取决于必须激活的过程。在其为愈合过程提供服务时,故障检测通常会检测程序故障的早期症状,以便及时触发愈合过程并防止严重后果,例如系统崩溃和数据丢失。在为修复过程提供服务时,故障检测通常会将一组观察结果划分为故障和无故障执行。然后对这些执行进行处理,以识别并修复导致出错的缺陷。与愈合过程相反,修复过程不会阻止故障发生,但是会利用在完整(失败)运行过程中收集到的信息来识别缺陷。

故障检测服务于两种过程的方式也会影响故障检测组件的实现。用于触发愈合操作时,故障检测组件通常作为检查应用程序行为的运行时监视器[76],[77]。当作为修复过程的一部分使用时,它则通常被用作程序的预期结果[78],例如作为嵌入在一组测试用例中的断言[79]。

图3左侧所示的愈合过程由两个步骤组成,这些步骤可以迭代执行。愈合步骤包括执行愈合操作,该操作可以防止或减轻检测到的故障。愈合操作完成后,验证步骤检查应用程序是否按需运行。并非所有的愈合技术都包括验证步骤,有时只是假设能对系统产生积极或中性的影响时使用愈合操作。如果验证步骤检测到愈合操作失败,则可能会重复愈合步骤,例如尝试不同的愈合操作并再次验证系统,直到故障被修复,或者不再执行其他操作[80],[81]。愈合过程通常需要快速进行愈合和验证操作,因为它们都是在应用程序现场运行时执行的。

 

由于愈合技术的主要目标是将失败的执行转化为成功的执行,不管问题的起因是什么,愈合过程会产生两种可能的结果:(i)执行成功但程序有误,也就是说,执行已经被治愈,但程序仍然有错误;(ii)执行失败且程序有误,即执行未被修复,程序仍然存在错误。在某些情况下,成功的规避方案可能会持久性部署到运行中的应用程序上,以自动治愈同一故障的再次发生,尽管它很少能够处理由同一缺陷触发的稍有不同的故障。

图3右侧所示的修复过程包括三个步骤。定位步骤确定可以应用修复的位置(注意,好的修复位置并不一定是在错误的语句)。修复步骤生成的修复会在定位步骤所返回的代码位置处修改软件。验证步骤检查合成的修复是否成功地修复了该软件。修复和验证步骤可能会在多个位置重复进行多次,直到故障被修复,无法生成进一步的修复或是分配给修复过程的时间耗尽为止。

将这两种过程进行比较,我们可以注意到修复过程包括了一个额外的步骤。愈合过程直接对故障进行处理,而修复过程需要先确定适当的修复位置。

与旨在防止故障的愈合过程相反,修复技术利用多次运行收集到结果(包括故障和无故障执行)来生成修复。例如,许多修复技术通过使用从大型测试套件的执行中获得的信息来生成修复,其中包括很多通过和失败的测试。

修复过程可能会产生两个可能的结果:(i)执行失败但程序已修复,因此,已修复的错误将不会导致进一步的failure ; (ii)执行失败且程序仍然错误。这两个结果分别对应成功与不成功的修复。

在以下各节中,我们将介绍自动程序修复解决方案1,重点是定位,修复和验证的策略。下一节将介绍几个示例错误程序来说明。最后,我们将讨论扩展到近乎自动的方法,这些方法可以推荐修复,但需要开发人员的干预才能最终确定修复并将修复代码整合到需要修复的程序中。
1当上下文的意思很清晰时,我们将会省略“自动”一词。

 

4. 程序示例

在本节中,我们将介绍贯穿本文使用的运行示例,以说明程序修复技术。特别是,我们使用五个错误的程序变体,使用基于减法的欧几里得算法计算最大公约数。算法1给出了程序的正确版本,Weimer等人的论文[20]也使用了该程序来介绍GenProg程序修复技术。算法2到6包括用灰色背景突出显示的故障。

在算法2中,我们通过在第1行的if条件中引入额外的子句。 每当a为0并且b大于0时,该fault将导致程序在语句8上无限期循环。

在算法4中,我们从第1行的if语句块中删除了exit(0)语句。这个fault产生的故障与算法2中考虑的故障相同,即,当a为0且b大于0时,程序在语句7上无限循环。[20]中使用了相同的fault。

在算法5中,我们将第1行的if条件中的子句从a == 0更改为a < b。当a不为0且小于b时,该fault可能导致程序输出错误的结果。在这种情况下,执行到第一个if语句就会输出变量a的值,该值可能不是a和b的最大公约数。

在算法3中,我们将第4行的while条件从b != 0更改为a != 0。当a大于0且b等于0时,该fault将导致程序在语句6上无限循环。

在算法6中,我们修改了第2行的print语句,使该语句打印错误的变量值。 当a为0且b不为0时,程序会输出错误的值。

这组有故障的程序将用于说明下一节中程序修复算法的能力和互补性。

 

(未完待续)

若发现翻译问题,可直接评论或与我联系:[email protected]

原文链接:https://ieeexplore.ieee.org/document/8089448

译者:ClarkC.

此翻译已经原作者授权,翻译内容的准确性与原作者无关,引用请注明出处

猜你喜欢

转载自blog.csdn.net/ClarkCC/article/details/107145906