当前位置: 首页 > news >正文

Facebook智能bug修复神器:让程序员少掉几根头发

Facebook开发了一款名为Getafix的工具,可以自动查找出bug的修复方案,并提供给工程师审批,这极大提高了工程师的工作效率和整体代码质量。Getafix不仅能够为工程师提供直观的修复方案,还能利用更强大的聚类算法,分析问题代码的上下文找到更合适的修复方案。Getafix是第一款被大规模部署到Facebook生产环境中的自动修复工具,它进一步提升了Facebook拥有数十亿用户的应用程序的稳定性和性能。

现代的生产环境代码库非常复杂,并且一直持续不断地更新。为了创建一个可以自动查找bug修复方案的系统——在没有工程师帮助的情况下——我们构建了一个工具,可以从工程师之前对代码库的更改中学习如何修复bug。它找到了一些隐藏的模式,并用这些模式来识别最有可能修复新bug的补救措施。

\"image\"

这个工具叫作Getafix,已经被部署到Facebook的生产环境中,进一步提升数十亿人使用的应用程序的稳定性。Getafix一般与Facebook其他两个工具结合使用,不过这项技术也可以用于其他地方。它目前能够为Infer发现的bug提供修复建议,Infer是我们的静态分析工具,可识别Android和Java代码中的null指针异常等问题。它还通过SapFix提供修复建议——针对我们的智能自动化测试系统Sapienz检测到的bug。现在,我们将深入了解Getafix是如何学习修复bug(指任意代码问题,而不仅仅是导致应用程序崩溃的问题)的。

Getafix的目标是让计算机处理日常工作,不过是在人类的监督之下,因为一个bug是否需要复杂的修复仍然需要由人类做出决定。这个工具将一种新的层次聚类方法应用于之前的数千个代码变更上,同时检查代码变更本身及其上下文。它可以检测bug的基础模式,并提供之前的自动修复工具无法检测到的修复方案。

Getafix还能够在bug修复过程当中,显著缩小程序当中可能需要更改的具体空间,从而更快地选择适当的修复手段; 此外,其不再像以往暴力破解及基于逻辑型技术那样对计算时间提出极高的要求。这种更为高效的方法使得Getafix被成功部署至生产环境当中。与此同时,由于Getafix能够以以往代码变化为基础进行学习,因此足以产生让人类工程师更容易理解的修复结论。

Getafix目前已经在Facebook生产环境中部署完成,负责自动对Infer报告提供null解引用bug进行修复,同时亦可为Sapienz标记的与null解引用相关的崩溃错误提供修复建议。此外,Getafix还被用于解决在较新版本Infer重新访问现有代码时所发现的代码质量问题。

Getafix与传统简单自动修复工具有何不同

在目前的行业实践当中,自动修复功能主要用于各类基础性问题,而代码修复则更为简单。举例来说,分析器可能会提出“致命异常”警告,强调开发人员可能忘记在新的Exception(…)之前添加一个throw。自动修复工具能够直接完成调整,而具体调整方式则可通过lint规则进行定义——换言之,其并不需要了解操作应用的特定情景。

Getafix则完全不同,它提供更多通用性功能,并可结合上下文相关因素来解决问题。在以下代码示例当中,对应第22行中的Infer错误,Getafix给出了下列修复结论:

\"image\"

需要注意的是,此修复方法不仅取决于变量ctx,同时也与方法的返回类型相关。与简单的lint修复方法不同,此类修复程序无法被纳入Infer本身。

下图所示为Getafix为Infer bug提供的修复方法; 尽管来自Infer的bug总是相同的(null方法调用,有可能引发NullPointerException风险),但每一项具体修复操作仍然独一无二。另外需要强调一点,Getafix的修复方法与人类开发者的常见操作完全一致。

\"image\"

深入了解Getafix关键技术细节

Getafix的组织形式如下图内工具链所示。在本节中,我们将描述Getafix的三大主要组件及其各自的功能与挑战。

\"image\"

Tree Differencer标识树级别的更改

基于抽象语法树的Differencer首先负责在两个源文件之间识别实际的编辑痕迹,例如针对同一文件的连续修订。举例来说,它会检测以下粒度的编辑:使用if打包语句、添加的@Nullableannotation或者import,以及将条件提前返回至某一现有方法之内等等。在以下示例中,插入条件判断语句if dog is null并提前返回、将public重新命名为private、方法的移动都会被检测为实际编辑。而基于行的diffing工具只会将方法标记为完全移除与插入,Tree Differencer则能够检测到这一移动并将移动方法之内的插入操作视为实际编辑。

Tree Differencer的主要挑战在于如何有效且精确地对树级别中的“之前”与“之后”部分进行对齐,从而识别出正确的实际编辑及其映射关系。

\"image\"

新的修复模式挖掘方法

Getafix通过利用新的层次聚类技术以及反合一方法(即一种能够在不同符号表达式之间实现泛化的现有方法)进行模式挖掘。在此之后,它会建立可能相关的树差异集合,进而选择该集合中最为常见的程序并转换为修复模式。这些模式可能是抽象的,且包含程序转换所面向的不同“漏洞”。

以下示例图像展示了一组层次结构,即树状图,其通过一组编辑生成。(在本示例中,我们直接采用上个示例中的编辑结果。)每一行皆展示出一种编辑模式——其中紫色代表“之前”,蓝色代表“之后”——以及一些元数据。每个垂直黑条对应于层次结构中的具体层级,其中黑条顶部的编辑模式代表着通过对该结构中所有同一层级的其它编辑进行反合一所获得的模式。其它编辑由较细的黑色线条连接。反合一将来自上一示例中的“如果dog为null则提前返回”条件与另一条编辑相结合——后者的唯一区别在于“dog正在饮水”。结果是,其将生成一个代表共性的抽象修复模式。由反合一引入的符号h0代表着可以基于上下文实现实例化的“漏洞”。

\"image\"

接下来,该编辑模式可以与其它变量名称更为多样但仍然具有相同整体结构的编辑模式相结合。在根据梳理树状脉络时,整个流程将产生越来越抽象的编辑模式。举例来说,其能够将此编辑与同猫相关的编辑组合在一起,从而获得位于图表上方位置的抽象编辑。

更值得强调的是,这种分层匹配流程为Getafix提供一套强大的框架,足以在代码变更中发现各类可复用模式。以下图片所示,将总计2288项用于修复我们代码库内Infer报告null指针错误的编辑汇总为一套树状图(横向布局,小型化)。我们希望挖掘的修复模式,无疑正隐藏在这份树状图内。

\"image\"

基于反合一方法的模式挖掘并非什么新鲜事物,但要想以尽可能少的修复操作解决新bug,我们还需要对挖掘得出的模式结果做进一步强化。

其中的变化之一就是引入一部分周边代码,即编辑结果当中没有变更的部分。如此一来,我们不仅能够发现人们在变更中采取的模式,同时也能发现应用变更时上下文中存在的某些模式。举例来说,在上面的第一份树状图中,我们注意到有两项不同的编辑会在dog.drink(…);之前添加if(dog==null)return。尽管dog.drink(…);没有变更,但其应被作为模式“之前”与“之后”部分的上下文信息进行考量,从而帮助我们理解这项修复的应用情景。从更高的编辑层级上考虑,dog.drink()这一上下文与其它上下文合并成为了抽象的上下文h0.h1(),用以限制模式的适用位置。在下一节中,我们将介绍另一个更具现实意义的示例。

根据以往的自动修复工具文献所述,贪婪聚类算法往往不太可能学习到上述情况。这是因为贪婪聚类算法倾向于维持各个聚类的单一表示,因此如果上下文不存在于训练数据的全部编辑当中,则该算法将不会引入该上下文。例如,如果某项编辑会在do(list.get());与以上示例中提到的dog.drink()合并时插入if (list != null) return,那么贪婪聚类算法会丢弃全部关于提前返回具体插入位置的上下文。与此相反,Getafix的分层聚类方法则尽可能在各层级上保留上下文,从而确保整体结构的通用性水平。在某种程度上讲,虽然我们希望学习的某些常规上下文可能丢失,但其仍将存在于结构当中的某些底层位置。

除了周边代码之外,我们还将编辑与提示这些编辑的Infer bug报告关联起来,从而了解编辑模式与对应的bug报告之间的映射关系。在前文第一份树状图中,可以看到Infer在bug报告中将“errorVar”视为bug来源变量,并在进行反合一之后给出漏洞h0。以此为基础,我们接下来即可在发布新的Infer bug报告时将需要关注的变量修改为h0,从而使得整个修复模式更为具体。

Getafix如何创建补丁

最后一步,我们需要考虑如何获取存在bug的源代码并从挖掘到的结论中生成修复模式,从而针对源代码生成修复补丁。在这方面,我们往往拥有多种修复模式可以选择(如前文树状图所示)。因此,接下来的挑战就是如何选择正确的模式以修复特定bug。如果该模式适用于多个位置,Getafix还需要选择出正确的匹配项目。以下示例说明了我们采用的常规方法以及如何在Getafix当中切实解决这项挑战。

示例1:考虑我们之前挖掘到的模式: h0.h1(); → if (h0 == null) return; h0.h1();

下面,我们将简要介绍如何为完全陌生的代码生成以下补丁。

\"image\"

Getafix通过以下步骤创建补丁:

1.找到与“之前”部分匹配的sub-AST: mListView.clearListeners();

2.对漏洞h0与h1进行实例化

3.利用实例化之后的部分替换sub-AST

请注意,之后部分中的h0是绑定的,因为其中包含了未修改的上下文h0.h1();,这将有助于限制模式适用的位置数量。如果不修改上下文,则该模式将为 → if (h0 == null) return;。很明显,这种模式将适用于众多与预期无关的位置,例如mListView.clearListeners();之后、甚至是mListView = null;之后。

实际上,仅插入模式也有可能出现在树状图中的某些较高位置,其中具有 h0.h1();这一上下文的模式已经通过负责向另一不同语句之前插入return的模式完成了反合一。以下示例说明了Getafix如何处理这类模式适用范围过广的情况。

示例2:请考虑以下模式: h0.h1() → h0!=null \u0026amp;\u0026amp; h0.h1()

通常情况下,此补丁应该来自对if条件或者return表达式的修复模式,因此我们当然希望其适用于这类上下文。但其同时也适用于其它一些情况,例如以上示例当中提到的调用语句:mListView.clearListeners();。Getafix的排名策略会尝试对模式的修复效能做出估算,并为其分配最可能实现修复效果的上下文。这项策略使得该系统能够在之后的运行当中不再依赖于验证步骤,从而显著降低计算时间。

以上模式将与其它模式竞争,例如更为具体的if (h0.h1()) { … } → if (h0!=null \u0026amp;\u0026amp; h0.h1()) { … }或者示例1中仅适用于调用语句而非表达式的模式。由于具体程度更高的模式往往拥有更少的匹配位置数量,因此Getafix会将其视为更适合当前情况的解决方案并为其分配更高的排名。

Getafix实际应用与表现

Getafix现已部署在Facebook的生产环境中,负责为Infer报告的null解引用bug提供自动修复建议。顺带一提,Infer是我们的一款统计分析工具,负责为Sapienz发现的、与null解引用相关的崩溃bug提供修复建议。此外,Getafix还负责解决Infer以往提出的某些重要bug。

在一次实验当中,我们将Getafix计算出的修复建议与以往人工编写的修复方法进行了比较,我们发现,在对大约包含200项小型编辑的数据集内各种Infer null方法调用bug进行修复时,需要修改的内容不足5行。此外,在大约四分之一的案例当中,Getafix提出的排名最高修复补丁与人工创建的补丁完全匹配。

在另一项实验中,我们着眼于Instagram代码库中的一套子集,并尝试批量修复其中存在的约2000个null方法调用问题。Getafix能够在大约六成bug中尝试使用某个补丁,且其中90%的尝试都通过了自动验证——这意味着其可编译且Infer将不再发出警告。总体来讲,Getafix成功以自动方式修复了1077条(占比约53%)的null方法调用错误。

除了针对新Infer bug提供修复建议之外,我们还利用相同的方式清理在原先代码审查中积压的旧有Infer bug。我们已经清理了数百个返回不可为空的Infer bug以及字段不可为空的Infer bug。有趣的是,在这项工作完成之后,Getafix在自动修复建议中开始越来越擅长处理返回不可为空以及字段不可为空类问题,二者的成功修复占比分别由56%与51%增长至62%与59%。总体而言,在过去三个月中,Getafix提供的一系列建议帮助我们成功修复了数百项额外bug。

Getafix还为SapFix生成了修复建议,用以处理Sapienz检测到的崩溃问题。过去几个月以来,SapFix所采用的修复方法中有约半数来自Getafix且实际有效(通过全部测试)。而在Getafix提供给SapFix的全部修复建议中,约80%通过了全部测试。

提升Getafix影响力

Getafix帮助我们实现了让计算机处理常规bug修复工作这一重大目标。随着我们对自身测试及验证工具的不断完善,预计Getafix将能够在未来更好地防止各类部署后故障问题。

我们还注意到,Getafix所挖掘出的修复模式不仅仅是在响应Infer报告的bug; 实际上,其同时也能够针对手动代码检查结果给出修复建议。这种额外的修复模式源将给自动重复代码审查带来令人兴奋的可能性。换句话说,未来我们有可能会将代码库中曾被多次标记及修复的bug直接交给自动化工具处理,而不再需要任何人工筛查

Getafix是我们构建大型代码语料库以及相关元数据统计分析智能化工具这一整体性举措中的组成部分。此类工具的出现,有望改善软件开发生命周期中的各个层面,包括代码发现、代码质量与执行效率等等。我们从Getafix当中获得的宝贵见解,也将帮助我们在这一领域构建并部署更多其它与之类似的重要工具。

原文链接:

https://code.fb.com/developer-tools/getafix-how-facebook-tools-learn-to-fix-bugs-automatically/

相关文章:

  • Android-来填写一个验证码吧!(二)
  • webpack的使用
  • 最详细的Log4j使用教程
  • Javascript 对象 - 数学对象
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 都是用 DllImport?有没有考虑过自己写一个 extern 方法?
  • python实现将json数据以json格式写入txt文件
  • 【笔记】Nginx热更新相关知识
  • 简单读!spring-mvc源码之穿越http请求
  • C++与Rust操作裸指针的比较
  • 团队项目的NABCD的分析
  • .net core 依赖注入的基本用发
  • win10驱动下获取cpu信息
  • 闭包--闭包之tab栏切换(四)
  • 单据类报表的制作
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • Apache Pulsar 2.1 重磅发布
  • Bytom交易说明(账户管理模式)
  • css布局,左右固定中间自适应实现
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • Elasticsearch 参考指南(升级前重新索引)
  • Java小白进阶笔记(3)-初级面向对象
  • js继承的实现方法
  • KMP算法及优化
  • Next.js之基础概念(二)
  • Python语法速览与机器学习开发环境搭建
  • Transformer-XL: Unleashing the Potential of Attention Models
  • WebSocket使用
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 更好理解的面向对象的Javascript 1 —— 动态类型和多态
  • 构造函数(constructor)与原型链(prototype)关系
  • 基于遗传算法的优化问题求解
  • 两列自适应布局方案整理
  • 微服务框架lagom
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 协程
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 优秀架构师必须掌握的架构思维
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #QT(串口助手-界面)
  • #预处理和函数的对比以及条件编译
  • (1)bark-ml
  • (2)(2.10) LTM telemetry
  • (html5)在移动端input输入搜索项后 输入法下面为什么不想百度那样出现前往? 而我的出现的是换行...
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (利用IDEA+Maven)定制属于自己的jar包
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (算法二)滑动窗口
  • (转)setTimeout 和 setInterval 的区别
  • (转)shell调试方法
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • .gitignore文件---让git自动忽略指定文件
  • .md即markdown文件的基本常用编写语法