acm-header
登录

ACM通信

贡献的文章

在谷歌上构建静态分析工具的经验


从构建静态分析工具谷歌的教训,说明

信贷:伊戈尔Kisselev

软件bug耗费了开发人员和软件公司大量的时间和金钱。例如,2014年,一个广泛使用的SSL实现中的bug(“goto fail”)导致它接受无效的SSL证书,36以及一个与日期格式相关的bug,导致Twitter大规模宕机。23这样的bug通常是静态检测到的,事实上,在阅读代码或文档时很明显,但仍然会在产品软件中出现。

回到顶部

关键的见解

ins01.gif

在之前的工作中,曾报告过将bug检测工具应用于生产软件的经验。63.729虽然对于使用静态分析工具的开发人员来说有很多这样的成功案例,但工程师并不总是使用静态分析工具或忽略它们的警告也是有原因的,672630.包括:

不整合。该工具没有集成到开发人员的工作流程中,或者运行时间太长;

不是可行的。警告是不可执行的;

不值得信赖的。用户不相信结果,比如误报;

没有体现在实践中。报告的错误在理论上是可能的,但实际上问题并没有体现在实践中;

修起来太贵了。修复检测到的bug代价太大或风险太大;而且

警告不理解。用户不理解这些警告。

在这里,我们描述了我们如何应用谷歌以前使用FindBugs Java分析的经验教训,以及从学术文献中获得的经验教训,来构建谷歌大多数软件工程师日常使用的成功的静态分析基础设施。谷歌的工具每天检测数千个问题,在问题代码被检入谷歌的全公司代码库之前,工程师根据自己的选择修复这些问题。

范围。我们主要关注静态分析工具,它们已经成为谷歌核心开发人员工作流程的一部分,并被谷歌的大部分开发人员使用。许多静态分析工具部署在谷歌的20亿在线代码库中32相对简单;大规模运行更复杂的分析还不是优先考虑的事项。

请注意,谷歌以外的开发人员在专门领域工作(例如航空航天13和医疗器械21)可以使用额外的静态分析工具和工作流程。同样,从事特定类型项目的开发人员(如内核代码和设备驱动程序)4)可以运行专门的分析。在静态分析方面已经有了很多很棒的工作,我们并不认为我们在这里报告的经验是独一无二的,但是我们相信,整理和分享在谷歌提高代码质量和开发人员体验的经验是有价值的。

术语。我们使用以下术语:分析工具在源代码上运行一个或多个“检查”,并确定可能代表或可能不代表实际软件故障的“问题”。如果开发人员在看到问题后没有采取积极行动,我们认为这个问题是“有效的假阳性”。35如果一个分析错误地报告了一个问题,但开发人员还是进行了修复以提高代码的可读性或可维护性,这不是一个有效的误报。如果一个分析报告了一个实际的错误,但是开发人员不了解错误,因此没有采取任何行动,这是一个有效的假阳性。我们这样做是为了强调开发者感知的重要性。开发人员,而不是工具作者,将决定工具感知的假阳性率并采取行动。

谷歌如何构建软件。在这里,我们概述了谷歌的软件开发过程的关键方面。在谷歌,几乎所有开发人员工具(开发环境除外)都是集中和标准化的。基础设施的许多部分都是从零开始构建的,由内部团队拥有,这给了试验的灵活性。

源代码控制和代码所有权。谷歌开发并使用了一个单一源代码控制系统和一个单独的源代码存储库,该存储库包含(几乎)所有谷歌专有源代码。一个开发人员使用“基于主干”的开发,有限地使用分支,通常用于发布版本,而不是特性。任何工程师都可以修改任何一段代码,但必须得到代码所有者的批准。代码所有权是基于路径的;目录的所有者还隐式拥有所有子目录。

构建系统。谷歌存储库中的所有代码都使用定制版本的Bazel构建系统构建,5要求构建是密封的;也就是说,所有的输入必须显式地声明并存储在源代码控制中,这样构建就可以很容易地分发和并行化。在谷歌的构建系统中,Java规则依赖于签入到源代码控制中的Java开发工具包和Java编译器,并且可以通过签入新版本为所有用户更新这样的二进制文件。构建通常来自源代码(从头),很少有二进制工件检入到存储库中。因为所有的开发人员都使用相同的构建系统,所以任何给定的代码段编译时是否没有错误就取决于它的真实性。

分析工具。谷歌使用的静态分析工具通常并不复杂。谷歌没有基础设施支持在谷歌级别运行过程间或整个程序分析,它也没有使用高级静态分析技术(如分离逻辑)7)规模。即使是简单的检查也需要支持工作流集成的分析基础设施来使其成功。作为一般开发人员工作流程的一部分部署的分析类型包括:

风格跳棋(如Checkstyle、10Pylint,34和Golint18);

可能扩展编译器的bug查找工具(如“容易出错”、15ClangTidy,12叮当螺纹安全分析,11Govet,17和Checker框架9),包括但不限于抽象语法树模式匹配工具、基于类型的检查和未使用的变量分析;

调用生产服务的分析器(例如检查代码注释中提到的员工是否仍然受雇于谷歌);而且

检查生成输出属性的分析器(例如二进制文件的大小)。

“goto fail”bug36会被谷歌的c++检查语句是否后跟花括号的检查器捕获。导致推特中断的代码23不会在谷歌处编译,因为会出现一个Error Prone编译器错误,这是一种基于模式的检查,用于识别日期格式的误用。谷歌开发人员还使用动态分析工具(如AddressSanitizer)来查找缓冲区溢出,使用ThreadSanitizer来查找数据竞争。14这些工具在测试期间运行,有时也用于生产流量。

集成开发环境(ide)。在开发过程早期显示静态分析问题的一个明显的工作流集成点是在IDE中。但是,谷歌开发人员使用各种各样的编辑器,因此很难在调用构建工具之前一致地检测所有开发人员的bug。尽管谷歌确实使用与流行的内部IDE集成的分析,但要求启用分析的特定IDE是不可能的。

测试。几乎所有谷歌代码都包含相应的测试,从单元测试一直到大规模集成测试。测试作为一个一流的概念集成在构建系统中,并且像构建一样是封闭的和分布式的。对于大多数项目,开发人员为他们的代码编写和维护测试;项目通常没有单独的测试或质量保证组。谷歌的连续构建-测试系统在每个提交上运行测试,并在开发人员的更改破坏了构建或导致测试失败时通知开发人员。它还支持在提交变更之前进行测试,以避免破坏下游项目。


开发人员,而不是工具作者,将决定工具感知的假阳性率并采取行动。


代码评审。对谷歌代码库的每次提交都首先经过代码审查。尽管任何开发人员都可以建议对谷歌代码的任何部分进行更改,但代码所有者必须在提交之前审查和批准更改。此外,在提交更改之前,即使是所有者也必须审查他们的代码。代码审查通过一个集中的、基于web的工具进行,该工具与其他开发基础结构紧密集成。静态分析结果会出现在代码审查中。

发布代码。谷歌团队频繁发布,通过“推动绿色”方法自动化了大部分发布验证和部署过程,27这意味着费力的手动释放验证过程是不可能的。如果谷歌工程师在生产服务中发现一个bug,与必须交付给用户的应用程序相比,可以以相对较低的成本削减新版本并部署到生产服务器。

回到顶部

我们从FindBugs中学到了什么

从2008年到2010年,谷歌对静态分析的早期研究集中在使用FindBugs进行Java分析23.:一个由马里兰大学的William Pugh和宾夕法尼亚约克学院的David Hovemeyer创建的独立工具,分析编译的Java类文件并识别导致错误的代码模式。截至2018年1月,在谷歌只有少数工程师使用FindBugs作为命令行工具。一个名为“BugBot”的谷歌小团队与Pugh一起进行了三次失败的尝试,试图将FindBugs集成到谷歌开发人员的工作流程中。

因此,我们吸取了几点教训:

尝试1。错误指示板。最初,在2006年,FindBugs作为一个集中的工具集成在一起,每晚在整个谷歌代码库中运行,生成一个可以让工程师通过仪表板检查结果的数据库。尽管FindBugs在谷歌的Java代码库中发现了数百个bug,但是这个指示板用处不大,因为错误指示板超出了开发人员通常的工作流程,而且区分新的和现有的静态分析问题会让人分心。

尝试2。提交bug。然后,BugBot团队开始手动对FindBugs每晚运行时发现的新问题进行分类,对最重要的问题提交bug报告。2009年5月,数百名谷歌工程师参加了全公司范围的“修复”周,重点是解决FindBugs警告。3.他们总共审查了3954个这样的警告(9473个警告中的42%),但实际上只有16%(640个)得到了修复,尽管事实上审查的问题中有44%(1746个)导致提交了bug报告。尽管Fixit验证了FindBugs发现的许多问题都是实际的bug,但是有很大一部分问题在实践中并不重要,不足以修复。手动筛选问题和归档bug报告在大范围内是不可持续的。

尝试3。代码评审的集成。然后,BugBot团队实现了一个系统,在该系统中,当建议的更改被发送到审查时,FindBugs会自动运行,并将结果作为评论发布到代码审查线程中,代码审查团队已经在为样式/格式问题做了一些事情。谷歌开发人员可以抑制误报,并应用FindBugs对结果的信心来过滤注释。该工具进一步尝试只显示新的FindBugs警告,但有时会将问题错误地归类为新的。这种集成在2011年代码审查工具被取代时中断了,主要有两个原因:有效的误报导致开发人员对工具失去信心,以及开发人员定制导致分析结果的不一致视图。

回到顶部

让它成为一个编译器工作流程

与FindBugs实验同时进行的是,谷歌的c++工作流程通过向Clang编译器添加新的检查而得到了改进。Clang团队实现了新的编译器检查,以及建议的修复,然后使用ClangMR38在整个谷歌代码库中以分布式的方式运行更新的编译器,改进检查,并以编程的方式修复代码库中存在问题的所有实例。一旦代码库清除了一个问题,Clang团队启用了新的诊断,将其作为一个编译器错误(不是一个警告,Clang团队发现谷歌开发人员忽略了这个错误)来破坏构建,这个报告很难忽视。Clang团队通过这一策略成功地改进了代码库。

我们遵循这个设计,为Java构建了一个简单的基于模式的静态分析,称为Error Prone15在javac Java编译器之上。1第一张支票,叫做PreconditionsCheckNotNullb检测运行时先决条件检查成功的情况,因为方法调用中的参数被调换了,例如,checkknotnull ("uid为空",uid)而不是checkNotNull(uid, "uid为空")。

为了进行支票,比如PreconditionsCheckNotNull在不破坏任何连续构建的情况下,Error Prone团队使用基于java的MapReduce程序对整个代码库进行这样的检查,该程序类似于ClangMR,名为JavacFlume,使用FlumeJava构建。8JavacFlume会发出一组建议的修复,以diffs表示,然后应用这些修复来产生整个代码库的更改。易错小组使用了一个内部工具,罗西,32要将大规模的变更分解成每一个影响单个项目的小变更,测试这些变更,并将它们发送给适当的团队进行代码审查。团队只审查那些应用于代码的修复,当他们批准这些修复时,Rosie就提交更改。所有的更改最终都会得到批准,现有的问题也会得到修复,团队会允许编译器错误。

当我们对收到这些补丁的开发人员进行调查时,57%的人收到了对已签入代码提出的修复建议,他们对此感到高兴,41%的人持中立态度。只有2%的人持否定态度,他们说:“这让我很忙。”

编译器检查的值。编译器错误会在开发过程的早期显示,并集成到开发人员工作流中。我们发现,扩展编译器检查集可以有效地提高谷歌的代码质量。因为Error Prone中的检查是自包含的,并且是根据javac抽象语法树而不是字节码(与FindBugs不同)编写的,所以团队之外的开发人员相对容易贡献检查。利用这些贡献对于提高Error Prone的整体影响力至关重要。截至2018年1月,162位作者提交了733张支票。

越早报告问题越好。谷歌的集中式构建系统记录所有构建和构建结果,因此我们确定了在给定时间窗口中看到某个错误消息的所有用户。我们向最近遇到编译器错误的开发人员和收到修复了相同问题的补丁的开发人员发送了一份调查。谷歌开发人员认为,在编译时标记的问题(而不是针对签入代码的补丁)会捕获更重要的bug;例如,调查参与者认为在编译时标记的74%的问题是“真正的问题”,而在签入代码中发现的只有21%。此外,调查参与者认为6%的问题是在编译时发现的(vs. 0%签入代码)“至关重要的”。这个结果可以用“幸存者效应”来解释;3.也就是说,在提交代码的时候,错误很可能已经被更昂贵的方法(如测试和代码审查)捕捉到了。将尽可能多的检查移到编译器中是一种被证明可以避免这些成本的方法。

编译器检查的标准。为了扩展我们的工作,我们定义了在编译器中启用检查的标准,设置了很高的标准,因为破坏编译将是一个重大的破坏。谷歌的编译器检查应该很容易理解;可操作且易于修复(只要有可能,错误应包括可机械应用的修复建议);不产生有效的误报(分析永远不应该为了正确的代码而停止构建);并且报告只影响正确性而不是样式或最佳实践的问题。

满足这些标准的分析器的主要目标不是简单地检测错误,而是自动修复整个代码库中可能出现的编译器错误的所有实例。然而,这样的标准限制了易出错团队在编译代码时能够进行的检查的范围;许多不能总是正确地检测或机械地修复的问题仍然是严重的问题。

回到顶部

代码复查时发出警告

一旦错误倾向团队构建了在编译时检测问题所需的基础设施,并且证明了这种方法是可行的,我们就想要显示更多影响较大的错误,这些错误不符合我们之前概述的编译器错误标准,并为Java和c++以外的其他语言提供结果。静态分析结果的第二个集成点是谷歌的代码审查工具,Critique;静态分析结果暴露在批判使用Tricorder,35谷歌的程序分析平台。截至2018年1月,在谷歌的c++和Java构建中有一个没有编译器警告的默认值,所有分析结果要么显示为编译器错误,要么显示在代码审查中。

代码复查检查的标准。与编译时检查不同,代码审查期间显示的分析结果允许包含高达10%的有效误报。在代码审查过程中,有一种期望,即反馈并不总是完美的,并且作者在应用它们之前评估所提出的更改。谷歌的代码复查应该满足以下几个条件:

是可以理解的。易于任何工程师理解;

要有可操作性,容易解决。修复可能需要比编译器检查更多的时间、思想或精力,并且结果应该包括关于如何真正修复问题的指导;

产生不到10%的有效假阳性。开发者应该至少在90%的情况下觉得检查指出了一个真正的问题;c而且

对代码质量有潜在的重大影响。这些问题可能不会影响正确性,但开发人员应该认真对待它们,并有意选择修复它们。

有些问题严重到可以在编译器中进行标记,但生成它们或开发自动修复是不可实现的。例如,修复一个问题可能需要对代码进行重大的重组。将这些检查作为编译器错误启用将需要手动清除现有实例,这在谷歌庞大的代码库中是不可行的。分析工具在代码检查中显示这些检查,防止出现新的问题,允许开发人员决定如何进行适当的修复。代码审查对于报告相对不那么重要的问题(如风格问题或简化代码的机会)也是一个很好的环境。根据我们的经验,在编译时报告它们会让开发人员感到沮丧,并使迭代和快速调试变得更加困难;例如,无法到达的代码检测器可能会阻碍临时禁用用于调试的代码块的尝试。然而,在代码审查时,开发人员准备他们的代码以供查看;他们已经有了批判的心态,更容易接受可读性和文体细节。

分析仪。Tricorder被设计为易于扩展和支持许多不同种类的程序分析工具,包括静态和动态分析。我们展示了一套Tricorder中容易出错的检查,不能作为编译器错误启用。错误倾向还启发了一套新的c++分析,它与Tricorder集成,被称为ClangTidy。12Tricorder分析仪可以报告超过30种语言的结果,支持简单的语法分析,比如样式检查器,利用Java、JavaScript和c++的编译器信息,并且可以直接与生产数据集成(比如关于当前正在运行的作业)。Tricorder在谷歌继续取得成功,因为它是一个插件模型,支持分析作者的生态系统,在代码审查过程中突出了可操作的问题,它提供了反馈渠道来改进分析人员,确保分析人员根据反馈采取行动。

允许用户贡献。截至2018年1月,Tricorder包括146台分析仪,其中125台来自Tricorder团队外部,以及7个用于数百个额外检查的插件系统(如ErrorProne和ClangTidy,它们包含7个分析仪插件系统中的2个)。

提供修复并让审阅者参与进来。Tricorder检查可以提供可以直接从代码审查工具应用的建议修复。审阅者和作者都可以看到它们,审阅者可以要求作者修复有问题的代码,只需在分析结果上单击“请修复”按钮。评审者通常不批准变更,直到他们所有的注释(手动的和自动的)都被处理。

根据用户反馈进行迭代。除了“请修复”按钮,Tricorder还提供了一个“没用”按钮,评论者或提议者可以点击这个按钮来表示他们不喜欢这个分析发现。单击自动在问题跟踪器中记录一个bug,将它路由到拥有分析器的团队。Tricorder团队跟踪这些没用的点击,计算“请修复”和“请修复”的比例。点击“没用”。如果分析仪的比例超过10%,Tricorder团队将禁用分析仪,直到作者改进它。虽然Tricorder团队很少需要永久禁用分析仪,但在分析仪作者删除和修改特别有噪声的子检查时,Tricorder团队已经禁用了一个分析仪(在几次情况下)。

被归档的bug通常会导致分析器的改进,从而大大提高开发人员对这些分析器的满意度;例如,2014年,易错团队开发了一种易错检查,当太多参数被传递给一个printf类似于番石榴的功能。19printf-like函数实际上不接受所有的printf说明符,只接受%s。大约每周,Error Prone团队会收到一个“没用”的bug,声称分析是错误的,因为bug提交者代码中的格式说明符数量与传递的参数数量相匹配。在每一种情况下,分析都是正确的,用户试图传递除%s以外的指示符。因此,该团队更改了诊断文本,直接声明该函数只接受%s占位符,并停止提交关于该检查的bug。


即使在一个成熟的代码库中,有完整的测试覆盖和严格的代码审查过程,错误也会悄然而至。


分析仪的规模。截至2018年1月,Tricorder每天分析了大约5万个代码审查更改。在高峰时间,每秒运行3次分析。评审者每天点击“请修复”超过5000次,作者每天应用自动修复大约3000次。Tricorder分析仪每天会收到250次“没用”的点击。

代码审查分析的成功表明,它在谷歌的开发人员工作流程中占据了一个“最佳位置”。在编译时显示的分析结果必须达到更高的质量和准确性,这对于仍然可以识别严重故障的一些分析来说是不可能满足的。在检查和代码检入之后,开发人员在进行更改时遇到的摩擦增加了。因此,开发人员在对已经测试和发布的代码进行额外更改时犹豫不决,较低的严重性和不太重要的问题不太可能得到解决。主要软件开发组织的其他分析项目(如针对Android/iOS应用的Facebook Infer分析)7)也强调了代码评审是报告分析结果的关键点。

回到顶部

扩大分析仪达到

由于谷歌开发者用户已经对Tricorder分析仪的结果获得了信任,他们继续要求进一步的分析。Tricorder通过两种方式解决这个问题:允许项目级定制和在开发人员工作流程的额外点添加分析结果。在本节中,我们还将讨论谷歌没有将更复杂的分析技术作为其核心开发人员工作流程的一部分的原因。

项目级定制。在整个谷歌代码库中,并不是所有请求的分析程序都具有同等的价值;例如,一些分析仪与较高的假阳性率相关联,因此将相应地具有较高的有效假阳性率,或者需要特定的项目配置才有用。这些分析器都有价值,但只对合适的团队有用。

为了满足这些要求,我们的目标是使Tricorder可定制。我们以前对FindBugs进行定制的经验没有得到很好的结果;特定于用户的定制导致了团队内部和团队之间的差异,并导致了工具使用的减少。因为每个用户都能从不同的角度看待问题,所以没有办法确保项目中的每个人都能看到特定的问题。如果开发人员从他们的团队代码中删除所有未使用的导入,那么即使单个其他开发人员在删除未使用的导入方面不一致,修复也会迅速倒退。

为了避免这样的问题,Tricorder只允许在项目级别配置,确保任何对特定项目进行更改的人看到与该项目相关的分析结果的一致观点。维护一致的视图使几种类型的分析器能够完成以下任务:

产生两个结果。例如,Tricorder包括一个协议缓冲区定义分析器33标识不向后兼容的更改。开发人员团队使用它来确保协议缓冲区中的信息以序列化的形式持久化,但对于不以这种形式存储数据的团队来说,这很烦人。另一个例子是一个分析器,它建议使用Guava37或者Java 7的习惯用法对于不能使用这些库或语言特性的项目没有意义;

需要特定的设置或代码内注释。例如,团队只能使用Checker框架的空值分析9它们的代码是否被适当地注释。另一个分析,当配置时,将检查二进制大小的增加和特定Android二进制的方法计数,并警告开发者是否有显著的增加或是否接近硬限制;

支持自定义领域特定语言(dsl)和团队特定编码指南。一些谷歌软件开发团队已经开发了小型的dsl,其中包含他们希望运行的相关验证器。其他团队已经开发了他们自己的可读性和可维护性的最佳实践,并希望强制执行这些检查;而且

高度资源密集型的。混合分析就是一个例子,它结合了动态分析的结果。这种分析为一些团队提供了很高的价值,但对所有团队来说成本太高或速度太慢。

截至2018年1月,谷歌中大约有70个可选分析,2500个项目至少启用了其中一个。整个公司的几十个团队都在积极地开发一种新的分析器,大多数不在开发工具组。

额外的工作流集成点。随着开发人员对这些工具的信任,他们也要求进一步集成到他们的工作流中。现在,Tricorder通过命令行工具、持续集成系统和代码浏览工具提供分析结果。

命令行支持。Tricorder团队为开发人员添加了命令行支持,这些开发人员实际上是代码看门人,定期检查和清除团队代码库中的各种分析警告。这些开发人员还非常熟悉每个分析将生成的修复的类型,并对特定的分析程序具有高度的信任。因此,开发人员可以使用命令行工具自动应用来自给定分析的所有修复,并生成清理更改;

门提交。一些团队希望特定的分析器能够真正地阻止提交,而不是仅仅出现在代码审查工具中。阻止提交的能力通常是由拥有高度特定的自定义检查且没有误报的团队请求的,通常是针对自定义DSL或库;而且

结果是代码浏览。代码浏览最适合在大型项目(或整个代码库)中显示问题的规模。例如,当浏览关于已弃用API的代码时,分析结果可以显示迁移需要多少工作;或者一些安全性和隐私分析是全球性的,需要专门的团队在确定是否确实存在问题之前审查结果。由于分析结果在默认情况下是不显示的,代码浏览器允许特定的团队启用分析层,然后扫描整个代码库并审查结果,而不会干扰其他开发人员从这些分析程序中分心。如果一个分析结果有一个相关的修复,那么开发人员就可以通过在代码浏览工具中单击来应用修复。代码浏览器对于显示利用生产数据的分析结果也是理想的,因为在提交并运行代码之前,这些数据是不可用的。

复杂的分析。谷歌上广泛部署的所有静态分析都是相对简单的,尽管有些团队针对有限的领域(如Android应用程序)使用特定于项目的分析框架进行过程间分析。谷歌量表的过程间分析在技术上是可行的。然而,实现这样的分析是非常具有挑战性的。如前所述,谷歌的所有代码都驻留在一个单一的源代码存储库中,因此,从概念上讲,存储库中的任何代码都可以是任何二进制代码的一部分。因此可以想象这样一种场景,在这种场景中,特定代码检查的分析结果需要分析整个存储库。尽管Facebook的推断725着重于组合分析,以便将基于分离逻辑的分析扩展到多联机存储库,将这样的分析扩展到谷歌的多联机存储库仍然需要大量的工程工作。

截至2018年1月,谷歌不再优先考虑使用一个系统进行更复杂的分析:

大的投资。前期的基础设施投资将令人望而却步;

需要开展工作以降低假阳性率。分析团队将不得不开发技术来大幅降低许多研究分析仪的假阳性率和/或严格限制显示哪些错误,如Infer;

还有更多要实现。分析团队仍然有很多“简单”的分析程序要实现和集成;而且


从事静态分析的工程师必须通过硬数据来证明影响。


前期成本高。我们发现这种“简单”的分析器的实用性很高,这是FindBugs的核心动机。24相比之下,即使是确定更复杂的检查的成本效益比,也有很高的前期成本。

请注意,对于谷歌以外在专门领域(如航空航天)工作的开发人员来说,这种成本效益分析可能非常不同13和医疗器械21)或特定项目(如设备驱动程序)4和手机应用程序7).

回到顶部

教训

我们尝试将静态分析集成到谷歌工作流中的经验教给了我们宝贵的经验:

找到bug很容易。当代码库足够大时,它将包含几乎任何可以想象到的代码模式。即使在一个成熟的代码库中,有完整的测试覆盖和严格的代码审查过程,错误也会悄悄溜走。有时候从局部检查来看问题并不明显,有时候bug是由看似无害的重构引入的。例如,考虑下面的代码片段对字段进行哈希处理f类型的

Result = 31 * Result + (int) (f ^ (f >>> 32));

现在考虑一下,如果开发人员更改类型会发生什么fint。代码继续编译,但是右移32变为无操作,字段与自身异或,字段的散列变为常量0。结果是f不再影响hashCode方法产生的值。超过31的右移可以被任何能够计算类型的工具静态检测到f,但是我们修复了谷歌代码库中出现的31次此错误,同时在error Prone中将该检查作为编译器错误启用。

因为找到bug很容易,24谷歌使用简单的工具来检测错误模式。然后,分析编写者根据运行谷歌代码的结果调整检查。

大多数开发人员不会特意使用静态分析工具。跟随许多商业工具的脚步,谷歌对FindBugs的最初实现依赖于工程师选择访问一个中央指示板来查看在他们的项目中发现的问题,尽管很少有人真正去访问。发现检入代码中的bug(可能已经部署并运行,而用户看不到问题)为时已晚。为了确保大多数或所有工程师看到静态分析警告,分析工具必须集成到工作流中,并在默认情况下为每个人启用。像Error Prone这样的项目没有提供bug指示板,而是用额外的检查扩展了编译器,并在代码审查中显示分析结果。

开发者的快乐是关键。在我们的经验和文献中,许多将静态分析集成到软件开发组织中的尝试都失败了。在谷歌,管理层通常没有要求工程师使用静态分析工具。从事静态分析的工程师必须通过硬数据来证明影响。对于一个静态分析项目的成功,开发人员必须感到他们从使用它中受益并享受它。

为了构建一个成功的分析平台,我们已经构建了为开发人员提供高价值的工具。Tricorder团队对问题进行了仔细的修正,执行调查以了解开发者的情绪,轻松地针对分析工具提交漏洞,并使用所有这些数据来证明继续投资的理由。开发人员需要建立对分析工具的信任。如果一个工具用误报和低优先级问题浪费开发人员的时间,开发人员将失去信心并忽略结果。

不要只是发现bug,要修复它们。要销售静态分析工具,典型的方法是列举代码库中存在的大量问题。其目的是通过表明潜在的纠正潜在错误或在未来防止错误的能力来影响决策者。然而,如果开发商没有采取行动的动力,这种潜力就无法实现。这是一个基本的缺陷:分析工具通过它们识别的问题数量来衡量它们的效用,而集成尝试由于实际修复或防止的bug数量太少而失败。相反,谷歌静态分析团队负责修复、发现bug,并相应地度量成功。专注于修复bug可以确保工具提供可行的建议30.尽量减少误报。在许多情况下,修复bug就像通过自动化工具找到它们一样容易。即使对于难以修复的问题,过去五年的研究也强调了自动为静态分析问题创建修复的新技术。222831

外包分析发展。虽然典型的静态分析工具需要专家开发人员来编写分析,但专家可能很少,而且实际上并不知道什么检查会产生最大的影响。此外,分析专家通常不是领域专家(例如那些处理api、语言和安全性的专家)。有了FindBugs的集成,只有一小部分google人知道如何编写新的检查,所以小BugBot团队必须自己完成所有的工作。这限制了添加新检查的速度,并阻止其他人贡献他们的领域知识。像Tricorder这样的团队现在专注于降低开发者贡献检查的门槛,而不需要之前的静态分析经验。例如谷歌工具Refaster37允许开发人员通过在代码片段之前和之后指定示例来编写检查。由于贡献者通常在自己调试错误代码之后才会做出贡献,所以新的检查倾向于那些节省开发人员时间的检查。

回到顶部

结论

我们最重要的见解是,仔细的开发人员工作流集成是采用静态分析工具的关键。虽然工具的作者可能认为开发人员应该对他们所编写的代码中可能的缺陷列表感到高兴,但实际上我们并没有发现这样的列表能够激励开发人员修复缺陷。作为分析工具的开发人员,我们必须根据修正的缺陷来衡量我们的成功,而不是根据呈现给开发人员的数量。这意味着我们的责任远远超出了分析工具本身。

我们提倡一种专注于尽早推动工作流集成的系统。如果可能,检查将作为编译器错误启用。为了避免破坏构建,工具作者承担了首先修复代码库中所有现有问题的任务,使我们能够一次一小步地“提高”谷歌代码库的质量,而不会出现倒退。由于我们在编译器中呈现了这些错误,开发人员在编写代码后会立即遇到这些错误,而他们仍然可以进行更改。为了实现这一点,我们开发了用于在整个庞大的谷歌代码库上运行分析和生成修复的基础设施。我们还受益于代码审查和提交自动化,它允许对数百个文件进行更改,以及对遗留代码的更改通常得到批准的工程文化,因为改进代码胜过规避风险。

代码评审是在提交代码之前显示分析警告的最佳时机。为了确保开发人员能够接受分析结果,Tricorder只在开发人员更改代码时才会显示问题,在提交更改之前,Tricorder团队会应用一套标准来选择显示什么警告。Tricorder在代码审查工具中进一步收集用户数据,该工具用于检测任何产生不可接受数量的负面反应的分析。Tricorder团队通过禁用行为不当的分析来最小化有效的误报。

为了克服警告盲症,我们努力重新获得谷歌工程师的信任,发现谷歌开发人员有忽视静态分析的强烈偏见,任何假阳性或糟糕的报告都给了他们不作为的理由。分析团队非常小心地将检查作为错误或警告启用,只有在根据这里描述的标准进行审查之后,这样开发人员很少会被分析结果淹没、困惑或烦恼。调查和反馈渠道是这一过程的重要质量控制。现在,开发人员已经获得了对分析结果的信任,Tricorder团队正在满足谷歌开发人员工作流程中更多地方出现的更多分析的要求。

我们已经在谷歌构建了一个成功的静态分析基础设施,它可以防止每天数以百计的错误进入谷歌代码库,无论是在编译时还是在代码审查期间。我们希望其他人能够从我们的经验中受益,成功地将静态分析集成到他们自己的工作流程中。

回到顶部

参考文献

1.Aftandilian, E., Sauciuc, R., Priya, S., and Krishnan, S.使用可扩展的编译器构建有用的程序分析工具。在源代码分析与操纵国际工作会议论文集(Riva del Garda,意大利,2324年9月)IEEE计算机学会出版社,2012,1423。

2.Ayewah, N., Hovemeyer, D., Morgenthaler, j.d., Penix, J.和Pugh, W.使用静态分析来找到bug。IEEE软件255 (Sept.-Oct。2008), 2229。

3.Ayewah, N.和Pugh, W.谷歌FindBugs修复。在软件测试与分析国际研讨会论文集(意大利特伦托,1216年7月)。ACM出版社,纽约,2010。

4.Ball, T., Bounimova, E., Cook, B., Levin, V., Lichtenberg, J., McGarvey, C., Ondrusek, B., Rajamani, S.K.和Ustuner, A.彻底的静态分析设备驱动程序ACM SIGOPS操作系统审查40, 4(2006年10月),7385。

5.巴泽尔;http://www.bazel.io

6.Bessey, A., Block, K., Chelf, B., Chou, A., Fulton, B., Hallem, S., Henri-Gros, C., Kamsky, A., McPeak, S.,和Engler, D.之后的几十亿行代码。Commun。ACM 53, 2(2010年2月),6675。

7.Calcagno, C., Distefano, D., Dubreil, J., Gabi, D., Hooimeijer, P., Luca, M., O'Hearn, P. w ., Papakonstantinou, I., Purbrick, J.,和Rodriguez, D.快速进行软件验证。在美国宇航局正式方法研讨会论文集(加州帕萨迪纳市,2729年4月)。施普林格,2015年。

8.钱伯斯(C.)、拉尼瓦拉(A.)、佩里(F.)、亚当斯(S.)、亨利(R.)、布拉德肖(R.)和Weizenbaum (N.) FlumeJava:简单、高效的数据并行管道。在美国计算机学会编程语言设计与实现SIGPLAN会议的会议记录(加拿大多伦多,6月510日)。ACM出版社,纽约,2010。

9.检查程序框架;https://checkerframework.org

10.Checkstyle Java短绒;http://checkstyle.sourceforge.net/

11.铿锵螺纹安全分析;http://clang.llvm.org/docs/ThreadSafetyAnalysis.html

12.ClangTidy;http://clang.llvm.org/extra/clang-tidy.html

13.Cousot, P., Cousot, R., Feret, J., Mauborgne, L., Miné, A., Monniaux, D.,和Rival, X. ASTRÉE分析仪。在欧洲方案编制专题讨论会论文集(苏格兰爱丁堡,4月210日)。施普林格,柏林,海德堡,2005。

14.动态消毒杀菌剂工具;https://github.com/google/sanitizers

15.容易出错;http://errorprone.info

16.FindBugs;http://findbugs.sourceforge.net/

17.兽医;https://golang.org/cmd/vet

18.Golint;https://github.com/golang/lint

19.Grammatech;https://resources.grammatech.com/medical

20.Griesmayer, A., Bloem, R., Cook, B.修复布尔程序与应用到C. In十八届立法会会议记录th国际计算机辅助验证会议(华盛顿州西雅图,1720年8月)。施普林格,柏林,纽约,2006。

21.Guava: Java 1.6+的谷歌核心库;https://code.google.com/p/guava-libraries/

22.Gupta, P., Ivey, M.和Penix, J.测试谷歌的速度和规模。谷歌工程工具博客, 2011;http://google-engtools.blogspot.com/2011/06/testing-at-speed-and-scale-of-google.html

23.黑客新闻。2016年Twitter宕机报告;https://news.ycombinator.com/item?id=8810157

24.Hovemeyer, D.和Pugh, W.发现bug很容易。ACM SIGPLAN通知, 2004年12月,92106。

25.推断;http://fbinfer.com/

26.Johnson, B., Song, Y., Murphy-Hill, E.R.和Bowdidge, R.W.为什么软件开发人员不使用静态分析工具来发现bug ?在35年的会议记录th国际软件工程会议(旧金山,加州,1826年5月)。ACM出版社,纽约,2013。

27.Klein, d.v., Betser, d.m.和Monroe, M.G.让“推动绿色”成为现实:维护生产服务所涉及的问题和行动。39;登录:, 5(2014), 2632。

28.Kneuss, E., Koukoutos, M.和Kuncak, V.演绎程序修复。在27年的会议程序th国际计算机辅助验证会议(旧金山,加州,1824年7月)。施普林格,2015年。

29.Larus, j.r., Ball, T., Das, M., DeLine, R., Fahndrich, M., Pincus, J., Rajamani, S.K.和Venkatapathy, R. Righting软件。IEEE软件21, 3(2004年5月),92100。

30.Lewis, C., Lin, Z., Sadowski, C., Zhu, X., Ou, R.和Whitehead, Jr., E. j bug预测支持人类开发者的发现吗?:来自谷歌案例研究。在35年的会议记录th国际软件工程会议(旧金山,加州,1826年5月)。ACM出版社,纽约,2013。

31.Logozzo, F.和Ball, T.模块化和验证自动程序修复。ACM SIGPLAN通知, 10(2012年10月19日),133146。

32.Potvin, R.和Levenburg, J.为什么谷歌在一个存储库中存储了数十亿行代码。Commun。ACM 59, 7(2016年7月),7887。

33.协议缓冲区;http://code.google.com/p/protobuf/

34.Pylint Python短绒;http://www.pylint.org/

35.Sadowski, C., van Gogh, J., Jaspan, C., Söderberg, E., and Winter, C. Tricorder:构建程序分析生态系统。在37年的会议记录th国际软件工程会议(1624年5月,意大利佛罗伦萨)ACM出版社,纽约,2015。

36.Synopsys对此编辑团队。关于“Goto Fail”Bug的Coverity Report。2014年2月25日,加州山景城,Synopsys;http://security.coverity.com/blog/2014/Feb/a-quick-post-on-apple-security-55471-aka-goto-fail.html

37.使用Refaster进行可扩展的、基于实例的重构。在重构工具研讨会论文集(印第安纳波利斯,10月26日)。ACM出版社,纽约,2013。

38.Wright, H., Jasper, D., Klimek, M., Carruth, C.和Wan, Z.使用ClangMR进行大规模自动化重构。在29年的会议记录th软件维护国际会议(荷兰埃因霍温,2228年9月)。IEEE计算机学会出版社,2013。

回到顶部

作者

凯特琳Sadowskisupertri@google.com)是美国加州山景城谷歌公司的软件工程师。

爱德华Aftandilianeaftan@google.com)在美国加州山景城谷歌公司领导Java编译器和静态分析团队。

亚历克斯鹰alexeagle@google.com)是美国加州山景城谷歌公司的软件工程师。

利亚姆Miller-Cushoncushon@google.com)是美国加州山景城谷歌公司的软件工程师。

Ciera Jaspanciera@google.com)是美国加州山景城谷歌公司的软件工程师。

回到顶部

脚注

a.谷歌的大型开源项目(如Android和Chrome)使用独立的基础设施和他们自己的工作流程。

b。http://errorprone.info/bugpattern/PreconditionsCheckNotNull

c.尽管这个数字最初是由第一作者随意选择的,但它似乎是开发人员满意的最佳点,并与其他公司的类似系统相匹配。


版权归作者所有。授权给ACM的出版权。
请求发布的权限permissions@acm.org

数字图书馆是由计算机协会出版的。版权所有©2018 ACM, Inc。


没有发现记录

Baidu
map