[关闭]
@qinyun 2017-11-02T03:51:25.000000Z 字数 3113 阅读 1955

NullAway:Uber用于检测Android上的NullPointerExceptions的开源工具

未分类

--
保持Uber的移动应用程序的可靠性对于促进愉快的用户体验至关重要。除了强大的插件架构、特征标志和外部数据的动态验证外,静态分析工具还可以通过在更新发布给用户之前检测潜在的错误来确保代码的可靠性。

以前,Uber利用第三方静态分析工具来检测潜在的NullPointerExceptions (NPE)和在Android代码库中保持可靠性,这是导致应用崩溃的主要原因。然而,随着我们的代码库的增长,我们发现这些工具不能满足我们为工程师提供强大的检测和快速反馈的需求。

为了解决这个问题,Uber开发了NullAway,这是一个快速实用的工具来帮助消除NPE。NullAway显着提高了开发人员的生产力,同时也满足了我们高要求的安全检查。而现在,我们已经为开源社区贡献了这个工具,所以其他人也可以建立更可靠的应用程序!

NullAway:https://github.com/uber/NullAway

在本文中,我们将讨论我们开发NullAway的动机,概述我们如何构建该工具,并提供有关如何将其用于自己的Android应用程序和Java项目的说明。

NullAway背后的故事

手机崩溃可能会给我们的用户带来重大问题,例如它可能会影响司机们不能及时接单拉客。在Java中取消引用空指针时发生的NPE是Android应用程序崩溃的常见原因。我们在Uber的策略是使用静态代码分析工具来尽可能地防止NPE崩溃。在2016年,Uber部署了用于静态检测潜在NPE 的Facebook Infer和Eradicate工具。除了帮助我们运行时对注释验证引擎(RAVE)进行验证,这些工具将把我们的应用程序中观察到的NPE数量减少一个数量级。

为了最大限度地提高可靠性,我们希望保证在所有可能的NPE得到修复之前,代码不能合并到我们的应用程序中; 这样,我们的主线将始终处于“绿色”状态(意味着构建已通过所有相关测试),并处于零警告状态。要获取此保证需要在我们的提交队列(在我们的持续集成管道的最后阶段)运行这些工具,以便工具中出现的任何错误都能阻止新的代码合并。

在提高应用程序可靠性的同时,在提交队列上运行诸如NPE检查器之类的错误查找工具有两个副作用,鼓励我们创造性地思考我们的代码:

1.延迟反馈

对于具有NPE问题的diff,开发人员在进行代码更改的最后阶段只收到警告。这种迟来的反馈可能会导致糟糕的体验:开发人员的更改可能会通过代码审查和所有其他检查,由于易于修复的原因,只能在提交队列中被拒绝。这个时候,开发人员可能已经转向了另一个任务中,并且必须进行上下文切换以返回到代码中并解决警告中的问题。

2.高延迟降低开发人员的效率

提交队列体验的总体延迟增加,从而降低了开发人员的生产力。由于在具有NPE警告的提交队列上的许多差异失败,因此需要多次提交,从而增加了整体队列的长度。

图1:Uber Engineering在Android上的持续集成管道有四个阶段。

在NullAway之前,Uber开发者只会注意到在提交队列阶段报告的NPE错误。使用NullAway,当它们在本地构建代码时,他们可以更早地识别错误。

我们的发展速度(同时有很多diff)和现有NPE检查器冗长的运行时间限制了我们在此过程中能更早地运行它们,因此我们决定构建一个在我们的发展管道的每一个阶段能够快速运行的低延迟解决方案。开发我们自己的修复程序的另一个好处是,在我们不断增长我们的代码库的过程中可以节省机器资源。

我们的答案?NullAway。

介绍NullAway

NullAway的核心是基于开源类型的NPE检查器,用于Java代码。要使用NullAway,你必须先在代码(字段,方法参数或返回值为null)中添加@Nullable 注释。(由于我们以前使用Eradicate,我们已经在我们的代码库中有这些注释。)给定这些注释,NullAway执行本地的一系列一致性检查,以确保在代码中取消引用的任何指针不能为空。

NullAway是一个用于查找框架Error Prone bug的检查插件。Error Prone的运行代码检查作为Java编译过程标准的一部分。此编译器的集成允许检查编译器已经完成的工作,如代码解析和类型检查。此外,NullAway和Error Prone可直接集成到我们用于Android代码的构建工具Buck所支持的快速内存并行构建中。因此,NullAway可以比在正常构建过程之外运行的工具快得多。

我们发现,NullAway在正常构建时间中只用了很少的开销(约占10%)。因此,我们配置了NullAway,不仅仅是在提交队列中运行,而是运行在我们的Android代码的每一个版本上。

将NullAway整合到我们所有的Java构建中的价值表现在以下三个方面:

使用NullAway

图2:在三个简单的步骤中,NullAway确定方法m中的表达式e是否为空。

要了解NullAway的工作原理,让我们考虑一下如何确定程序中的一些表达式是否为空,这是空值检查所需要的。请考虑以下示例:

  1. class A {
  2. @Nullable Object f;
  3. }
  4. static void m(A x) {
  5. if (x.f != null) {
  6. System.out.println(x.f.toString());
  7. }
  8. }

在上面的示例中,NullAway尝试显示x不为空,以确保x.f不会导致NPE,并且x.f在调用xftoString()时也不为null。对于不应该为null的表达式,NullAway首先尝试快速证明表达式 是非空的,例如,通过检查其类型是否不是@Nullable,或者是否是不能为nullnew Object()的表达式。例如,由于x的声明没有注释为@Nullable,所以NullAway可以假设它不为空,从而显示x.f是安全的。NullAway通过确定@Nullable表达式从不作为参数传递给方法m的方式执行这一假设。

如果快速检查失败,NullAway试图使用数据流分析和充分利用检查框架现有的库显示非null的含量。数据流分析的一个主要目的是发现代码中的现有null。对于上面的例子中的xftoString()调用,快速检查不能显示x.f不能为null,因为A.f字段是@Nullable。然而,我们的数据流分析使用封闭的条件检查,xf!= null来表示x.f.toString()的调用是安全的。由于数据流分析可能很昂贵(它需要计算控制流图并运行定点计算),所以NullAway每个方法只运行一次分析,并缓存结果。

在NullAway GitHub页面上,有关于如何在Android应用程序或其他Uber Engineering和开源的NullAway运行该工具的详细说明,NullAway是消除NPE的快速实用工具,帮助其他人部署更可靠的Android应用程序。有关NullAway的详细检查,错误信息和限制的更多详细信息,请参阅我们的实施指南。

链接:

https://github.com/uber/NullAway/wiki

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注