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

策划编辑 | Natalie
做者 | JOHANNES BADER 等
译者 | 核子可乐
编辑 | Vincent算法

AI 前线导读:Facebook 开发了一款名为 Getafix 的工具,能够自动查找出 bug 的修复方案,并提供给工程师审批,这极大提升了工程师的工做效率和总体代码质量。Getafix 不只可以利用强大的聚类算法,分析问题代码的上下文找到更合适的修复方案,并且给出的方案对于人类工程师来讲很容易理解。Getafix 是第一款被大规模部署到 Facebook 生产环境中的自动修复工具,它进一步提高了 Facebook 拥有数十亿用户的应用程序的稳定性和性能。小程序

更多干货内容请关注微信公众号“AI 前线”,(ID:ai-front)api

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

这个工具叫做 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 给出了下列修复结论:测试

须要注意的是,此修复方法不只取决于变量 ctx,同时也与方法的返回类型相关。与简单的 lint 修复方法不一样,此类修复程序没法被归入 Infer 自己。

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

深刻了解 Getafix 关键技术细节

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

Tree Differencer 标识树级别的更改

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

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

新的修复模式挖掘方法

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

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

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

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

基于反合一方法的模式挖掘并不是什么新鲜事物,但要想以尽量少的修复操做解决新 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();

下面,咱们将简要介绍如何为彻底陌生的代码生成如下补丁。

Getafix 经过如下步骤建立补丁:

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

对漏洞 h0 与 h1 进行实例化

利用实例化以后的部分替换 sub-AST

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

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

示例 2:请考虑如下模式: h0.h1() → h0!=null && h0.h1()

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

以上模式将与其它模式竞争,例如更为具体的 if (h0.h1()) { ... } → if (h0!=null && 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 当中得到的宝贵看法,也将帮助咱们在这一领域构建并部署更多其它与之相似的重要工具。

相关文章
相关标签/搜索