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

原论文: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教授提供翻译授权。鉴于原文很长,该翻译将分为六次上传。本次包含原文第6.1.2 和 6.1.3节的内容。原文中的参考文献可至附在开头与结尾的原文链接查看。

6.1.2 预定义模板

基于预定义模板的修复技术会根据一组更改操作来修改程序,这些更改操作会影响程序的一个或多个语句(参见表2中”预定义”行)。使用模板,开发人员可以定义复杂的更改模式,这些更改模式会在多个位置连贯地影响正在修复的程序,而通过原子更改的随机组合很难获得这种模式。这些模板的示例包括:扩展同步块、对程序条件执行具有一定复杂度的操作以及添加实现预定义访问控制策略的代码。

到目前为止,该类别中基于搜索的技术利用模板来解决特定类别的缺陷,而蛮力技术利用模板来解决的还包括通用类型的故障。

与预定义模板一起工作的大多数技术都使用蛮力而非基于搜索的策略。之所以会发生这种情况,是因为应用模板可能比原子更改代价更大,并且这种成本因素可能会阻碍基于搜索的算法的发展速度。因此,最好使用蛮力策略来系统地检查可以应用模板的任何地方是否存在缺陷。

 

6.1.2.1 针对并发缺陷的基于搜索的技术

在某些情况下,一个模板的应用虽然有用,但可能不足以解决问题。在这些情况下,基于搜索的策略可能仍然可行。并发缺陷是一类可能需要在程序的多个点上进行多个具有一定复杂度的更改才能解决的问题。例如,ARC [38]是一种针对并发缺陷的修复技术,它在基于搜索的算法的上下文中使用手工生成的模板。

ARC[37],[38]通过使用遗传编程和一组预定义的模板对正在修复的程序进行演化,生成并发缺陷的候选解决方案,这些模板实现了一系列在并发程序中可能有用的具有一定复杂度的更改,包括同步未受保护的共享资源,将同步区域扩展到包括未受保护的源代码和交换嵌套锁对象。

通过多次运行可用的测试套件来评估候选解决方案,每次使用ConTest工具[112]注入不同的噪声。必须多次执行测试用例,因为观察并发缺陷的能力可能取决于执行的特定时序关系,因此同一测试的多次执行可能会产生不同的结果。候选解的适合度定义为已观察到的正确解的个数与不同时序关系数之间的比率。

 

6.1.2.2 通用的蛮力技术

蛮力技术定义了一旦应用到程序后就可能修复程序缺陷的模板。修复过程是将每个模板应用到每个可能的位置,直到程序被修复或超时。通用技术定义了可能有潜力解决程序中任何类型缺陷的模板。在这一小结中,我们报告了AutoFix-E [92],AutoFix-E2 [93],SPR [28]和Prophet [113]技术,它们利用了不针对特定缺陷模型的通用预定义模板。

AutoFix-E [92]与PACHIKA相似,但可在以Eiffel编写的软件上运行,Eiffel是一种支持按规则(contracts)编程的编程语言。规则的存在(例如,方法前置条件和后置条件)使得AutoFix-E生成的修复比PACHIKA生成的修复更复杂。事实上,规则提供了关于程序的语义知识,并可用于缩小可能的修复集,区分错误和正确的程序状态,并有助于识别达到正确状态的策略。

AutoFix-E使用算法7、8、9、10所示的模板来修复程序。其中snippet是新代码,旨在使应用程序处于不违反可用规则的新状态。oldStmt是应用程序源代码中已经存在的语句或语句块。fail是一个谓词,它捕获在可用测试套件中运行测试用例时使程序失败的值集。

AutoFix-E使用基于模型的缺陷定位来确定应用模板的点,并使用度量修复如何影响程序源代码和程序动态状态的指标,来优先考虑导致最小更改的修复。这种策略基于这样一个假设:只要对程序稍加修改就能修复。

AutoFix-E2[93](也称为AutoFix[94])利用从测试执行期间评估的条件中动态提取的信息以及从规则中提取的信息,改进了模板在AutoFix-E中的应用方式。此外,它还扩展了AutoFix-E的功能,可以根据基于频谱的故障定位技术计算出的正在修复的程序中语句的可疑度,来对候选解决方案进行排序。

SPR[28]通过一个阶段性的过程来解决程序修复问题,该过程可以快速跳过许多有误的修复,并专注于最有希望的情况。修复策略系统地将一组通用转换模板应用于正在修复的程序。这些模板是参数化的,因此每个模板代表一类程序转换。在第二阶段,对于每个被转换了的程序,SPR确定能够使修复成功的参数值(如果其存在)。由于模板通常需要一个条件作为参数,因此在最后一步中,SPR尝试合成一个条件,该条件可以在评估时产生第二阶段确定的值。如果该过程成功,则将条件嵌入到程序中,并最终完成修复。

SPR中定义的参数化模板可能会产生影响if条件(通过向现有条件添加子句和生成新条件)和变量值(替换变量名和常量值)的更改。新条件的合成仅限于v op const形式的条件,其中v是变量名,op是 = 或 != ,const是常数。SPR还可以将源代码中存在的语句复制到程序中的其他位置。

请注意,与原子更改操作相比,SPR中定义的模板可以解决更复杂的场景。例如,模板可以改变整个条件,而原子更改操作通常只会影响单个运算符。当然,使用原子更改操作通过演化的技术可能有潜力达成某些模板的效果。

SPR系统地将这些模板从最可疑的语句应用到最不可疑的语句。

例如,我们使用SPR修复示例程序中的算法2,其修复过程将首先选择一个语句和一个模板。假设选中了第1行的if语句和添加新子句的模板。新条件的形式为a == 0 && b == 0 || absCond,我们必须确定absCond。SPR运行可用的测试套件,寻找表达式absCond返回的什么布尔值能将失败的测试用例转换为通过的测试用例,而不会影响通过的测试用例的结果。SPR还会在有缺陷的if语句的作用域内记录所有变量的值,以整合使用这些变量的条件并返回所需的布尔值序列,在这个实例下,成功找到了要添加的正确子句a == 0。

Prophet[113],[114]使用了与SPR相同的修复生成过程,但它利用软件更改的大型数据库(包含许多已修复的程序)上的可用信息改进了修复过程。相同类型的修复可能会在不同项目的生命周期中重复出现,因此可以从过去吸取教训,使修复过程更加高效。此外,从人工编写的修复程序中学习可以增加生成正确的,而不仅仅是合理的修复程序的可能性。

Prophet分析了软件更改的数据库,以生成概率模型P(m, l | p),该模型表示对程序p中的修改点l处应用AST修改m,导出修复后的程序的概率编码。这些概率用于对候选补丁进行排序,并从较高概率的补丁开始尝试修复程序。与SPR所使用的一组启发式方法相比,此策略可以显著地减少探索搜索空间所需的时间。

 

示例

AutoFix-E和AutoFix-E2针对Eiffel程序,因此它们无法处理示例中的程序。

SPR和Prophet可以修复第4节中描述的许多样本缺陷。特别是,他们可以修复算法2中的缺陷,如前面描述SPR时提到,但他们也可以使用变量值模板修复算法3和6。它们还可以利用模板复制现有语句修复算法4中的缺陷(在本例中为exit(0))。它们无法修复算法5中的缺陷,因为if语句的模板不足以修复错误条件。

 

6.1.2.3 缓冲区溢出故障的蛮力技术

PASAN [115]是一种旨在修复缓冲区溢出漏洞的技术。它的工作方式是首先检测劫持控制攻击(control-hijacking attacks)中使用的输入,然后使用这些输入生成修复程序,以消除攻击中利用的漏洞。PASAN可以处理三种类型的缓冲区溢出漏洞:由于使用不安全的libc函数(如strcpy)而导致的溢出,由于数组复制循环而导致的导致返回地址损坏的溢出,以及不会损坏任何返回地址的缓冲区溢出。

PASAN检测正在修复的应用程序,以提取有关静态数组和动态分配的缓冲区大小的信息,并使用RAD[116]检测损坏函数返回地址的缓冲区溢出攻击。在每次迭代中,基于可能引入损坏值的语句的性质,PASAN使用不同的策略生成候选修复。如果修改返回地址的语句是libc函数,PASAN会尝试用安全函数替换不安全函数来修复。如果损坏内存地址的语句处于循环中,PASAN将首先标识溢出的数组及其大小,然后使用If语句对该数组强制进行边界检查,从而尝试修复该问题。最后,如果该漏洞不在返回地址上,PASAN将尝试查找引发问题的库函数或循环,并通过在代码中引入适当的检查来更改它。通过重放对修复程序的攻击来测试生成的修复。

AutoPAG [117]使用类似于PASAN的方法来修复越界违规。它首先检测应用程序中溢出的变量,从而识别受污染的语句和变量集。修复生成器使用不同的修复模板处理这些受污染集:重定向缓冲区边界内的越界读取、用安全函数(如strncpy)调用替换允许越界写入的函数(如strcpy)的调用,以及跳过导致越界错误的语句。然后针对触发修复过程的同一越界攻击对修复了的应用程序进行测试。

 

6.1.3 基于示例的模板

使用基于示例的模板的修复技术根据从已用于修复程序的修复示例集中提取的一组更改操作(参见表2中的 “基于示例”行),来改进程序(基于搜索的技术),或者系统地执行更改(蛮力技术)。与预定义模板类似,基于示例的模板可能实现相当复杂的模式,在多个位置一致地影响正在修复的程序。

模板的提取可以是手动的,也可以是自动的。手动提取时,模板将被一次定义完,然后用于修复程序。历史记录驱动的修复[118],PAR [22],Relifix [119]和R2Fix [120]就是这种情况。请注意,历史驱动的修复虽然会自动从历史数据中挖掘信息以指导候选解决方案的演化,但实际上使用了一组简单的预定义原子操作和基于示例的模板作为更改操作。

在其他情况下,可以使用挖掘技术或其他算法自动提取模板。在这种情况下,每次都可以从不同的程序集中重复提取过程,从而增加了该技术的通用性。CodePhage [24]就是这种情况,它可以从一组正确的程序中自动提取缓冲区溢出问题的修复。基于示例的技术的有效性显然取决于用于提取模板的案例集[121]。

 

6.1.3.1 通用的基于搜索技术

属于此类的技术不考虑特定类别的缺陷(它们的缺陷模型是通用的),而是使用从对多个实际修复程序进行分析得出的特定更改模型。假设某些缺陷可能需要同时应用多个模板,则通过基于搜索的算法自动重新组合提取的模板,以最大限度地提高其修复故障的效率。

历史驱动修复[118],[122]利用从几个软件项目的历史中提取的信息来指导候选修复的生成。该技术使用与GenProg的相同进化方法,但没有交叉操作,并且具有从GenProg [20],PAR [22]和突变测试[123]衍生的12个突变操作。从项目中挖掘出的信息用于合成bug修复模式,这些模式表示AST级别的更改集,这些更改在过去有助于修复错误。bug修复模式用于指导选择过程,也就是说,最有可能在每次迭代中进化的候选解决方案是那些包含一组与bug修复模式中所表示的更改相似的更改的解决方案。

PAR [22]使用从对60,000多个实际修复的手动分析中定义的模板。从实际修复定义的模板可能比其他模板更有效,还可能提高修复的可接受性。

模板被编码为AST重写规则的序列,并被遗传编程算法用来演化一组候选解。将模板应用于基于频谱的故障定位确定的代码位置,直到找到修复程序为止。

Relifix [119]在特定的回归问题中应用了与PAR类似的方法。Relifix使用了一组代码转换模板,这些模板是根据对73个实际回归问题的手动分析定义的。模板对特定于回归缺陷的规则进行编码,例如用一条语句的先前版本替换该条语句,或对刚刚修改的一条语句进行突变。

这些操作与优化的随机搜索算法一起使用,该算法利用基于频谱的缺陷定位来识别模板应针对的代码位置。

 

示例

对于PAR和Relifix,它们修复缺陷的能力与必须修复的缺陷和这些技术中定义的模板之间的匹配直接相关。例如,PAR不包括可以添加方法调用的模板,这将有助于在算法4中修复缺陷,而PAR包括可以从条件谓词中添加和删除术语的模板,该模板可以通过从算法2的第1行if语句移除条件b == 0来修复其中的缺陷。PAR也可以使用模板修复算法6,该模板在相同作用域内用兼容的参数替换方法调用的参数,而针对算法3和5没有模板。

Relifix处理第4节中出现的示例缺陷的能力在很大程度上取决于这些缺陷是否已作为回归问题引入。例如,如果通过简单地更改算法的先前正确版本引入了任何错误语句(例如,算法2第1行的if条件),则Relifix可以选择性地还原仅针对错误语句的更改。

历史驱动修复可以修复算法2、4和6中的缺陷。修复算法2时历史驱动的修复可以使用变异操作从复合if条件中删除布尔条件b == 0。要修复算法4,历史驱动修复可以使用变异操作,来插入在程序中其他位置找到的必须修复的语句。在这种情况下,可以通过将语句exit(0); 复制到第1行if条件的then分支中来修复缺陷。最后,算法6可以使用替换方法调用参数操作(假设该技术处理C函数的方式与Java方法类似)进行修复,该操作可以使用在方法调用的相同作用域内找到的变量替换方法调用中的参数。在这种情况下,可以通过将参数a替换为变量b来修复。

 

6.1.3.2 通用的蛮力技术

属于这一类的技术被设计成能够潜在地修复任何类型的缺陷,只要提供一组可供学习的合适的实际修复。与其他使用从一组实际案例中手动提取模板的技术不同的是,通用技术每次都可以从一组不同的样本中自动提取模板。

R2Fix[120]是一种修复技术,它从用户提交的bug报告开始,利用与许多实际bug报告相关联的一系列预定义修复模板生成修复。R2Fix使用机器学习技术来识别实际的bug报告,这些报告看起来与必须修复的故障的bug报告类似。已识别的bug报告将被分析(例如,查看指针和函数名),自动与关联的预定义模板配对,这些模板与新的bug报告上下文相关(例如,尽管相似,两个bug报告可能引用不同的变量和函数名),并应用于代码。

识别出的bug报告和相应的模板将被系统地应用于正在修复的程序的代码,并且候选方案将通过附加到bug报告的测试套件进行验证。

 

示例

对于此类技术,其解决缺陷的能力取决于要学习的样本集。原则上,如果提供了合适的样本修复程序,其可以修复任何缺陷。

 

6.1.3.3 缓冲区溢出缺陷的蛮力技术

属于该类别的技术旨在使用模板来修复缓冲区溢出漏洞,这些模板的定义至少部分地受到实际修复和实际程序的影响。这些模板通过蛮力算法被系统地应用。

CodePhage[24]针对缓冲区溢出问题,但不是定义一组预定义的模板,而是使用一组捐赠(donor)程序来提取应添加到正在修复的程序中的条件,以防止缓冲区溢出问题。这组捐赠程序必须与要修复的程序实现相同的功能。这种修复技术的假设是,可能存在一个捐赠程序,其中包含有缺陷的程序中丢失的检查,如果将该检查从捐赠程序复制到正在修复的程序中,则可以修复该故障。

CodePhage使用(定向整数溢出发现引擎,Directed Integer Overflow Discovery Engine)DIODE修复处定位技术[124]专门发现在要修复的程序中触发溢出故障的输入。DIODE有两个主要逻辑步骤。第一步,它使用基于Valgrind框架[125]构建的动态污点分析来识别程序中的内存分配位置和分配的块的大小。第二步,它使用符号执行[126]来分析分配位置并识别可能导致程序因整数溢出而失败的输入值。DIODE通过使用识别出的输入来运行程序并检查故障的产生来确认溢出的存在。生成修复的目标语句是在第一步中发现并在第二步中被确认的导致整数溢出的内存分配点。

注意,这些语句尽管在执行时会产生溢出,但不一定就是错误的。确实,造成溢出的值可以由其他语句生成。然而,整数溢出问题不一定需要在缺陷位置查看,但可以在能观察到溢出的位置直接预防。

然后,使用相同的输入执行捐赠程序集,以发现能够正确处理触发错误的输入和不触发错误的输入的捐赠程序。分析由输入值驱动执行的捐赠程序的条件分支,以发现对失败和通过的输入进行的评估不同的条件是什么。这些条件可能会严重影响失败的执行,并可能被用来修复有故障的程序。

如果发现了这样的条件,CodePhage将运行一个进程,使在捐赠程序中发现的条件适应要修复的程序。特别地,首先将该条件相对于程序的输入值以符号形式表示,以便可以将其传输到正在修复的程序中。然后,CodePhage确定由失败输入执行的每个可能的位置,可以在其中插入条件以修复程序,从而生成许多程序变体,用可用的测试套件对这些变体进行测试,并用DIODE进行检查。

 

(未完待续)

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

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

译者:ClarkC.

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

猜你喜欢

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