可重用库和子系统的高级静态分析

描述

高级静态分析工具不再是新鲜事物,而是成为标准专业开发人员工具包中真正的战略元素。

一些早期的静态分析工具严格按照逐个函数或逐个模块的方式运行,很少或根本没有跨模块分析。接下来是“整个程序”分析,工具可以在其中找到可能的运行时故障,但前提是给出感兴趣程序的所有源代码。今天,我们看到的工具可以对程序的某些部分有效,例如软件库或软件子系统,而无需人工“驱动程序”代码来激活库或子系统中的代码。

静态分析正在成为专业开发人员工具集的标准部分。美国国防部已经认识到静态分析在帮助识别软件密集型系统中的安全漏洞和弱点方面的重要性。  许多非国防组织也开始将静态分析作为其日常软件卫生的一部分,因为静态分析在早期识别使用更传统的测试不易检测到的问题方面提供了独特的优势。但是现在已经确定了它们的好处,我们需要更仔细地研究各种工具的功能,并了解如何在现代开发环境中最有效地部署它们。

几乎所有软件开发组织都提倡代码库或代码子系统的重用,至少在组织内是这样。不幸的是,许多静态分析工具不能很好地适应分析完整可执行程序以外的任何内容。这是因为许多静态分析工具首先找到给定例程的所有调用者,然后有效地替换在任何这些调用中传递的参数值集,以确定感兴趣的例程是否可能在给定输入上失败。因此,要对库执行静态分析,程序员必须首先创建范围广泛的单元测试集,然后将静态分析应用于这些测试。这在一定程度上降低了静态分析工具的潜在优势,

通过一种基于从代码本身推断构建的前置条件和后置条件(通常称为“合同”)的软件库静态分析方法,同时还适应程序员提供的断言和前置条件和后置条件合同,分析工具非常有效地分析了库中的所有代码,并且作为一个附带好处,生成了人类可读的合同,总结了每个库例程的要求和效果。

分析可重用库

一种新型的静态分析工具正在出现,它摆脱了为正在分析的代码提供驱动程序或线束的需求(图 1)。这些工具可以自下而上地工作,从程序的叶例程或库开始,并朝着更高级别的例程工作。此类工具从代码本身推断例程的契约(前置条件和后置条件),确定例程的算法可以处理哪些值范围或值组合,以及哪些值集或组合将导致运行时问题,例如在数组外部建立索引、溢出数值计算或取消引用可能为空的指针。这种自下而上、基于契约的方法允许这些高级静态分析工具提供对程序片段的宝贵见解,从单个模块到库和子系统。推断的合同被设计成人类可读的,它提供了有用的竣工文档,以帮助促进手动代码审查,以及识别原始需求和当前现实之间的不匹配。此类工具的示例包括 AdaCore 的 CodePeer 分析器和 Microsoft Research 的 CC-Check 工具 。

图 1:可重用库的静态分析。

代码

推断前置条件和后置条件

在手动代码审查期间,由高级静态分析工具推断的合同可以帮助识别代码的直接问题。这是一个例程的示例,其中例程的名称显然与其功能不匹配,如分析工具生成的推断后置条件所示(图 2)。

图 2:推断后置条件的示例。

代码

推断的后置条件(由 --#postcondition 注释标识)表明例程返回给定月份自年初以来的天数,而例程的名称暗示它应该返回在月。显然,无论是程序员还是命名例程的人都感到困惑。这只是分析器推断竣工合同的好处的一个例子。许多其他情况发生,其中推断的前置条件或推断的后置条件表明程序员选择的算法显然是错误的,给定例程的要求。

静态分析器可以通过使用一种巧妙的技术来推断例程的前置条件和后置条件,该技术首先假设例程的输入可以采用任何可能的值,然后通过消除可能导致的输入值或输入组合继续进行执行期间的运行时故障。一旦分析器到达例程的末尾,剩余的不会导致运行时失败的值代表了唯一可以始终安全地传递给例程的值,因此代表了例程的有效前提条件。后置条件是通过获取满足前置条件的输入值集并计算它们为例程的输出生成的值集来确定的。

有条件的先决条件

这种推断合同的技术对于简单的直线例程非常有效,但不能捕获具有在某些但不是所有调用上执行的代码的例程的完整故事。对于这些,我们需要考虑条件前置条件,即仅适用于通过例程的某些路径的前置条件。这是一个示例,说明了条件前置条件的必要性(图 3)。

图 3:条件前置条件示例。

代码

此处分析器已推断出在计算 Y +/- 1 时防止数值溢出的先决条件,但它需要为通过例程的两条路径提供不同的先决条件。它通过发出条件前置条件来处理这个问题,其形式为“不或者。“这相当于蕴涵” ⇒ 。” 在分析典型的可重用库时,条件前置条件非常重要,因此能够捕获适用前置条件的条件对于对库或其他可重用子系统进行精确的自下而上静态分析至关重要。

记录关于未分析代码的假设

分析库或子系统时出现的另一个问题是它们通常依赖于其他较低级别的库或子系统,并且希望相对独立于它可能依赖的库来分析一个库。这为高级静态分析器带来了不同的挑战,即处理从正在分析的代码到当前分析中未包含的代码的调用。当调用发生在当前分析中包含的较低级别的例程上时,自下而上的分析方法为被调用的例程提供推断的前置条件和后置条件,从而能够对较高级别的例程进行进一步分析。当被调用的例程不在当前分析中时,一种不同的方法是合适的——即,静态分析工具可以跟踪这个未经分析的例程返回的值是如何使用的,并指出正在分析的例程中对这个未经分析的代码进行了哪些假设。例如,如果对返回指针的例程进行调用,并且调用例程立即取消引用该指针而没有首先检查它是否为空,则显然假定此未经分析的例程返回的指针是非空的(在例如,调用发生在第 12 行,由 @12 表示,图 4)。

图 4:调用未分析代码的假设示例。

代码

类似地,调用代码可能会对数字返回值的值范围或更复杂的返回对象的初始化状态做出假设。通过显式记录调用代码的所有此类假设,分析器可以让库实现者更深入地了解对较低级别库的期望。然后可以将这些假设与低级库的实际行为进行比较,以验证低级库是否被正确使用。

静态分析工具的重要性

随着静态分析工具的使用成为软件开发过程中不可或缺的一部分,这些工具的功能可以决定获得的整体价值。成熟的软件组织一直致力于以库或子系统的形式创建可重用的软件,因为众所周知,整体生产力的关键在于为每个应用程序编写更少的新代码。高级静态分析工具可以通过直接分析库或子系统来帮助测试可重用组件的可重用性和稳健性这一具有挑战性的任务,而无需创建完整的可执行程序可能需要的驱动程序、线束或存根。这些工具可以通过自下而上的方式实现这一目标,首先从可重用组件的叶例程开始,然后以前提条件、后置条件和假设的形式推断人类可读的信息。通过这样做,它们使库或子系统开发人员能够准确了解组件的构建行为,而无需开发扩展的动态测试套件。

审核编辑:郭婷

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分