acm-header
登录

ACM通信

实践

静态分析


一个红色的立方体在一组白色的立方体中,说明

信贷:Tostphoto

回到顶部

源代码、计算机和人之间的关系是复杂的。尽管大多数代码的存在是为了在计算机上运行,但它的目的并不限于此。它也旨在阅读和理解。现代软件开发的复杂性与理解代码的努力是对立的:无法理解的软件不容易维护或改进,许多实证研究表明,给定代码库的复杂性与其缺陷率之间存在相关性。3.理解任何规模的代码库都是很困难的,因此程序员需要并且应该得到所有有用的工具和技术来帮助他们理解复杂的代码库。

其中一种工具,静态程序分析它由程序或算法组成,旨在从另一个程序的源代码中提取事实,而不执行相关程序,通常作为日常软件开发过程中的一个独立阶段。使用工具进行静态程序分析的软件开发人员(通常简称为静态然后就有机会使用分析产生的事实来进一步理解、评估和修改相关的代码库。

可以从源代码中提取的事实分为许多不同的类别。例如,旨在发现安全漏洞的静态分析将提取有关程序使用的函数和库的信息,而旨在美化或标准化代码布局的分析将关注语法结构的位置和位置。从代码中提取的其他常见事实包括死代码检测,它表示永远无法到达的代码路径;使用不安全的api,例如C的gets()函数;污染分析,识别和监控可能被恶意数据污染的值的使用;竞态条件检测,用于识别因程序执行线程之间的交互而产生不正确或不可预测结果的代码模式;以及边界检查,它确保对数组或内存位置的访问在程序员预期的范围内。

静态分析不同于动态像valgrind这样的分析,它在程序运行时从程序中提取事实模型检查,它验证程序的单独外部规范的正确性。动态和静态分析通常在许多应用程序中同时使用。静态分析比动态分析有一些优点。一个这样的优点是,静态分析通常操作程序中所有可能的执行分支,而动态分析只能访问当前执行的代码路径。然而,反过来也是正确的:动态分析有关于数据在运行程序内存中的布局和位置的具体信息,而静态分析必须猜测给定的语言、编译器、操作系统和计算机架构如何表示给定的数据。也有一定程度的重叠;动态和静态分析都可以检测到c族语言中未初始化变量的使用,这是一个常见的编程错误。

静态分析在现代软件工程中无处不在。流行的例子包括安全漏洞扫描器,如Coverity和CodeQL;程序员错误检测工具,例如scan-build, LLVM项目提供的分析工具,针对C、Objective-C、c++、Swift;代码格式化器,例如Python的黑色的或者去的go的;以及面向用户的编辑器工具,如Rust的rust-analyzer

静态分析的概念也是其他几个领域的核心。例如,编译器设计本身就是一个活跃的研究领域,但是静态分析是编译器作者工具包中的一个基本构建块,而且大多数编译器在代码生成前后运行许多独立的静态分析。实际上,您可以将编译器看作是一个静态分析工具,在这个工具下生成的事实由一个可执行程序以及任何适用的调试信息组成。然而,术语静态分析通常指的是可以与编译器或构建系统一起使用的外部工具。

静态分析的实践有一些基本的限制。一种有用的静态分析,终止分析与预测有关,给定一些源代码,该代码最终是完成运行还是无限循环。艾伦·图灵,在1936年的开创性证明中,7说明没有算法能够为所有可能的程序输入确定此属性。因此,终止分析,以及许多其他可以被证明等价于中止问题的程序属性,在它们的能力方面都有一个上限。这样的分析可以接近,但永远不会达到,对所有可能的输入的完美。这一限制意味着静态分析常常局限于产生程序行为的近似。然而,近似值在实践中往往是非常有用的。

回到顶部

历史的角度来看

为了讨论静态分析的历史,我们必须把静态分析和静态分析区分开来通过和一个工具。静态分析过程包括对给定代码段的遍历,该代码段从该代码中提取一个事实或一组事实,而静态分析工具是一个程序,通常与程序执行机制(例如解释器或编译器)分离,它使用一个或多个分析过程向用户生成一组事实。在独立工具出现之前,静态分析通道作为现有程序的一部分出现。

海军上将格雷斯·霍珀(Grace Hopper)在1952年的开创性论文《计算机的教育》(The Education of a Computer)中明确了编译器需要访问的一组事实,以便正确生成代码。2第一次发展类型推断算法诞生于1958年;这些过程既能够检查程序中声明的值的类型,也能够在没有显式声明的情况下推断值的类型。接下来的十年里,对优化编译器内部分析通道的研究大量涌现;美国皇家ca实验室1965年的一篇论文概述了一种编写程序的方法,该方法“可以检查任何其他程序,并对其进行简化,只要从参数程序的形式就可以检测到,而不需要知道它将做什么。”6

今天的静态分析工具的概念是在20世纪70年代现代软件开发技术出现时发展起来的,它被称为独立于编译器或解释器的一个阶段。最早流行的分析工具是斯蒂芬·c·约翰逊的线头它写于1978年,并在1979年的Unix版本7中向公众发布,该版本检查C程序的错误。那个时代的C编译器执行的正确性检查比现代编译器少得多,而且线头介绍了一些长期流行的分析,例如关于可疑类型转换、不可移植构造、未使用或未初始化变量的警告——尽管现在这些警告通常是C编译器本身的一部分。

线头它填补了一个重要的利基并得到了广泛的应用,但它容易产生假阳性结果,这要求程序员用旨在抑制警告的辅助信息来注释他们的程序。线头它的影响力如此之大,以至于把它的名字传给了一整类工具,剥绒机,跨越多种编程语言。

同时,静态分析也受到了计算机科学理论分支的关注。Patrick和Radhia Cousot介绍了背后的理论抽象的解释在1977年ACM SIGPLAN会议上发表的一篇论文中,为静态分析提供了一种正式的数学方法。1抽象解释形式化了程序行为近似的概念:中止问题,以及许多其他可以简化为中止问题的问题,是不可计算的,这意味着行为近似是唯一可行的前进路径。

抽象解释方法允许分析考虑问题中控制流的所有可能分支,并详细说明如何将程序的每个部分的近似行为组成整个程序的近似行为。抽象解释在实践中被证明是一种非常有用的方法;许多后来的分析工具都建立在抽象解释的基础上,它仍然是一个活跃的研究领域。

随着时间的推移,静态分析成为许多软件开发方法的基本部分。静态分析工具最著名的用户是NASA(国家航空航天局)。它的软件工程与保证手册它是一种建立和有时是授权软件开发过程的文件,在其最早的版本中规定,NASA的软件必须通过可用的分析工具进行静态分析。4该手册将静态分析确立为项目经理和相关工程组的基本职责。开发安全关键软件的公司,如航空电子设备软件或医疗设备固件,已经热情地采用静态分析;MISRA C是由汽车工业软件可靠性协会(Motor Industry Software Reliability Association)开发的一套用C语言开发安全关键嵌入式系统的流行指南,要求使用这些工具。

回到顶部

实现和发展

就像线头虽然针对的是C语言,但由于语言之间的语法和语义存在广泛的差异,大多数实际的静态分析工具都针对单一语言或语言族。语言的功能对任何相关分析工具的行为和需求都有巨大的影响;例如,针对支持运行时代码计算的语言的工具(eval ())字符串,如JavaScript和Python,必须考虑这些功能。Python的全面漏洞扫描器会在遇到调用的代码路径时向程序员发出警告eval ()不可信的用户输入。在一种没有eval ()程序员不需要考虑这一点。相反,针对高级语言的分析不需要考虑低级内存访问的风险。

将静态分析引入代码库的方法有很多种。其中最受欢迎的是那些与VCS(版本控制软件)集成的软件,如Git;许多VCS服务(免费的或商业的)提供了集成静态分析工具的平台,以便在将新代码推入存储库时按需执行分析。静态分析工具还与CI(持续集成)服务集成,后者在添加或删除代码时管理构建和打包软件的过程。大多数CI服务允许用户指定,如果分析工具报告了意外的结果,则构建过程应该失败。用户需要编写他们的

自己的静态分析经常为现有的分析框架编写扩展;例如,scan-build提供了一个API,允许终端用户连接到LLVM的内部进程,并利用LLVM的丰富功能来遍历和分析程序的语法树。

值得注意的是,静态分析及其相关语言之间的交换是双向的;随着分析的发展,它们推动了编译器和相关工具的开发。一个现代的例子是Swift编程语言的发展。Swift的前身Objective-C最初使用了一种内存管理风格——手动引用计数,这要求程序员指定给定对象的内存何时应该保留(“保留”),何时应该释放(“释放”)。这个过程很容易出错;因此,与Clang编译器相关联的静态分析项目引入了检查程序员插入的程序是否正确地保留和释放的功能。

可以检查这种管理的正确性这一事实促使我们认识到,Objective-C内存管理的整体可以由编译器自动化。这一见解导致了ARC(自动引用计数)的引入,它为具有正式内存管理策略的语言(即Swift本身)铺平了道路,在Swift中,内存管理通常是完全自动化的。类似地,描述类型推理分析的文献的存在启发了ML语言家族的发展,并帮助将类型推理引入到缺乏它的语言中,如c++。

回到顶部

现代实践

静态分析在编程实践中以几种方式表现出来。最直接的分析方法涉及到在本地机器上运行分析的最终用户。许多流行的文本编辑器和ide(集成开发环境)自动集成静态分析工具,在开发软件时直接向程序员提供分析反馈。

如前所述,CI和VCS服务经常提供钩子来将静态分析集成到开发和构建过程中。然而,许多静态分析是看不见的。毕竟,编译器本身就是一种静态分析,由几十个单独的分析过程构建而成,生成计算机可执行的工件。甚至文本编辑器也会执行自己的分析。语法高亮显示在几乎所有编辑器中都是常见的,它是一种静态分析,生成有关程序中使用的标识符和关键字的语义作用的信息。此外,静态分析了开发过程中使用的软件的很大一部分,包括操作系统甚至可能是CPU的微码。

并非所有的分析在实践中都是可行的。代码库越大,解析和遍历所需的时间就越长;此外,许多静态分析的计算成本很高——通常是二次元的,有时甚至是三次元的——在执行它们所需的空间或时间方面。因此,静态分析和被分析的代码库之间存在着某种军备竞赛。随着代码库的增长,程序员需要更复杂和有效的分析。


程序员采用静态工具的一个障碍——也许是最重要的障碍——是要求人们改变他们的行为,以解释发现的问题和出现的警告。


程序员采用静态分析工具的一个障碍(可能是最重要的障碍)是要求人们改变其行为,以解释发现的问题和出现的警告。自从线头在美国,程序员一直在努力消除与给定分析的假阳性结果相关的警告;补救措施通常需要在代码中插入“魔法注释”,依靠给定的分析工具扫描并适当禁用相应的警告。然而,手工插入这些指令可能非常繁琐,程序员通常会选择不使用产生太多误报的静态分析工具。

类似地,假阴性(例如未被检测到的安全错误)会让程序员对其代码的正确性产生毫无根据的信心。程序员可以通过对给定工具的精心配置来解决误报问题;假阴性更难以发现,尽管可以通过连续使用多个静态分析工具来降低风险。此外,分析可能会发现理论上有效但实际上无害的问题,例如违反了C语言中命名标识符的大量和繁琐的限制。

回到顶部

向未来

现代静态分析工具对代码库提供了强大而具体的见解。例如,Linux内核团队开发了一个搜索、分析和重写C源代码的强大工具Coccinelle;因为Linux内核包含超过2700万行代码,所以静态分析工具对于发现错误和在其众多库和模块之间进行自动更改都是必不可少的。另一个针对C语言家族的工具是Clangscan-build,它提供了许多有用的分析,并为程序员提供了一个API来编写他们自己的分析。

基于云的工具,如LGTM。Com与现有的构建和发布过程集成,并跨各种编程语言工作。与静态分析相关的高级协议也出现了。语言服务器协议是一组通用的定义,标准化了分析工具与文本编辑器(如Emacs和VS Code)的接口方式,确保了分析工具可以与程序员的工作流程集成,而不管他们选择的工具是什么;类似地,SARIF(静态分析结果交换格式)为静态分析工具生成的输出提供了一个标准。

旨在检测安全漏洞的静态分析子领域继续吸引工业和研究的注意。随着安全漏洞的后果变得更加可怕,静态分析的效用增加了。像Spectre (CVE-2017-5753)这样的漏洞,暴露了与计算机CPU投机性执行相关的安全缺陷,促进了分析工具的开发,专门用于检测这类漏洞。许多分析工具利用特定漏洞和反模式的CVE(常见漏洞和暴露)和CWE(常见弱点枚举)数据库。

软件工程的基本挑战是复杂性。大型软件产品是人类尝试过的最复杂的工作之一。更让这个负担加重的是它的短暂历史——人类开发软件的历史只有不到50年,相比之下,其他领域(如建筑或医学)则有几千年的历史。


现代静态分析工具对代码库提供了强大而具体的见解。


最重要的是,随着新的平台、操作系统和编程语言的出现,软件生产的机制每年都变得越来越复杂。工业软件的复杂性的增长通常快于其作者管理这种复杂性的能力,这是软件工程和计算机科学作为学科所面临的核心挑战之一。在这场战斗中,我们只有为数不多的宝贵武器,但是静态分析是最有效的,正如开发工作和不良率的大量元分析所证明的那样。5

像计算机科学中的许多东西一样,静态分析的效用是自我参照的:要编写可靠的程序,我们还必须为我们的程序编写程序。但这并非悖论。静态分析工具,尽管它们的理论和实践可能很复杂,但它将使我们和未来的工程师能够克服这一挑战,并产生我们从业者应得的知识和见解。

回到顶部

参考文献

1.抽象解释:通过构造不动点或近似不动点对程序进行静态分析的统一格模型。在4 .会议记录th年度ACM SIGPLAN-SIGACT编程语言原理研讨会(洛杉矶,加利福尼亚州,美国,1977)238-252。ACM出版社;https://dl.acm.org/doi/10.1145/512950.512973

2.计算机的教育。计算机历史年鉴9, 3-4 (1988), 271-281;https://dl.acm.org/doi/10.1109/MAHC.1987.10032

3.软件复杂性与软件维护:实证研究的调查。软件工程年鉴1, 1(1995), 1 - 22。

4.NASA总工程师办公室。软件工程与保证手册。美国国家航空航天局。理念- 135,2011;https://swehb.nasa.gov/display/SWEHBVC

5.开发过程中静态分析的成本和效益,2020,arXiv:2003.03001;https://ui.adsabs.harvard.edu/abs/2020arXiv200303001N

6.论计算机程序的自动简化。Commun。ACM 8, 6(1965年6月),366-370;https://doi.org/10.1145/364955.364963

7.图灵,点在可计算数上的应用。在伦敦数学学会学报s2-421 (1937), 230-265;https://doi.org/10.1112/plms/s2-42.1.230

回到顶部

作者

帕特里克•汤姆森是GitHub公司的高级工程师,致力于对世界上最大的代码库进行静态分析。


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

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


没有发现记录

登录为完全访问
»忘记密码? »创建ACM Web帐号
文章内容:
Baidu
map