t分布表精确完整图_分析动态语言的测试完整性

76197739b06d7d6866654c286e72e691.png

摘要

在动态类型编程语言中,类型错误可能会在运行过程中发生。虽然通常伴随程序执行的测试套件可以让你相信那些错误会消失,但一般来说它是没有任何保证的。 我们提供了一个程序分析,可以检查测试套件是否具有足够的覆盖范围来证明给定的类型及其相关的属性,证明类型和属性对于具有重载和值依赖类型的程序代码尤其困难。 这个分析实现了可扩展静态分析和动态分析之间的协同效果,也超出了静态分析所能实现的范围。 此外,该分析提供了一个关于类型相关属性的指标,这个指标是全新的,更与测试套件的覆盖充分性相关。

在基于Dart的实践中,特别是在程序代码之中,传统的静态分析技术难以处理。我们通过展示减少类型错误和推断健全的调用图的信息,演示了如何使用这种混合静态/动态程序分析来测量测试套件的质量。

关键词

程序测试; 静态分析; 类型检查; Dart

介绍

Dijkstra曾说:“程序测试可用于发现漏洞的存在,但永远不会证明它们不存在”。正如几十年前所观察到的那样,有些情况下测试套件是“完整”的,在某种意义上它们允许在任意一次执行中保持对正确属性的验证。举一个简单的例子,以字符串作为输入的直线Java程序,单个测试执行就足以检测到可能存在的任何空指针错误。更通俗一点说,对于特定程序点处的本地属性,测试套件可能可以完成完整的测试。本文中,我们将探索如何利用这种测试完整性的概念,来对动态类型语言中的编程模式进行合理的类型检查。

使用动态或可选类型编程语言(如Dart ,TypeScript,Typed Racket 和网状Python)的优势之一是基于它们的灵活性:它们允许动态编程模式,例如基于值的重载和其他形式的值依赖类型,而这些并不适合于传统的静态类型系统。这种灵活性的代价是,直到运行时才会检测到与类型相关的错误,并且该类型信息不能静态地用于指导自动完成功能和IDE中的导航功能或编译器中的优化器。我们的目标是提供可以自动推断此类代码的是否健全和精确类型信息的程序分析技术。更具体地说,我们专注于Dart,旨在确保程序不存在运行时的类型错误且能推断出精确和健全的调用图信息。在Dart程序中,两种运行时类型错误特别有趣:在隐式继承中的子类违规错误和在域和方法的查找操作中的消息不理解错误。程序员可以了解测试能在多大程度上保证没有这样的运行错误。 同样,关系图对启用死代码消除和其他优化操作都很有用,也可以用在IDE的动态类型支持方面。

85cdc2f9bd99e6de44e58b55e259a5b3.png

示例图1显示了使用box2d库附带的示例应用程序中的一些Dart代码,该库使用向量数学库,且演示了动态类型语言中常见的编程风格,这对于静态类型分析具有挑战性。cross函数是重载的,因为在其行为和返回类型中依赖于其参数的运行时的类型:第4行中的分支返回类型vec3或参数out的运行时的类型,第7行返回类型double,第16行返回输入vec2或out的类型。此外,如果调用时参数的类型与任何情况都不匹配,如果在启用断言的情况下则是第20行发生了故障,如果没有启用则返回null,这可能在程序中触发运行时错误。在第32行和第33行中调用了cross函数。在Dart的检查模式执行过程中,如果返回的值不是vec3类型,则dp2perp和dp1perp的赋值将失败。而在生产环境执行中,赋值将成功,但如果dp2perp或dp1perp的类型为double而另一个参数的类型为vec2或vec3,则第34行中*的应用程序将失败,如果左侧参数和右侧参数的类型不同,则+将会失败。 (第26-27行显示左侧参数类型为vec2的情况下+的实现。)程序员如何确定应用程序中不会发生这些潜在的类型相关错误?运行一些测试可能会给人一些信心,但是很难知道是不是已经做了足够多的测试。

传统的静态分析不适合用来分析这种代码,因为这种代码的分析过程需要非常高的精度(例如上下文灵敏度和路径灵敏度),而精度与足够的可扩展性难以同时满足。如上例所示,上下文不敏感分析可能能够显示cross函数可以返回vec3,vec2或double类型的值(假设out通常也是其中一种类型),但该信息并不够精确,因此不能用来排除各种类型相关的错误。目前已经提出了两段输入来解决类似的问题,但这对于程序来说还不够实用。相反,传统的动态分析也不合适,因为它不能提供任何可靠性的保证。有一个研究与我们工作相关,非常值得关注,这项研究是An等人所研究的基于约束的一种动态类型推理技术。它可以使用动态分析来推断Ruby程序的健全类型信息。但是有一点,它需要对分析的程序里每个函数都进行完整的路径覆盖,而这是不多见的。 我们提出了混合轻量级静态分析和动态执行的测试套件。 我们认为,静态和动态技术的组合可以确定测试套件是否具有足够的覆盖范围,以期保证示例中的类型相关的正确性。 静态分析有两部分:依赖性分析和类型分析。 它对上下文和路径并不敏感,因此可以扩展到大型程序来使用,并且相对容易实现; 值得一提的是,相较于完全的静态分析方法,它需要对本地函数进行更简单的建模。

此篇文章的成果是:

•我们定义测试完整性的概念:它是使测试套件拥有足够的覆盖率来确保给定类型相关的正确性属性的充分条件。使用轻量级静态分析来近似测试完整性,继而我们演示了怎样混合静态/动态分析可以产生健全的类型信息。这个信息可以来抵抗传统静态分析的,来自具有挑战性的动态编程模式。

•基于Dart语言的Goodenough的实现,与仅使用静态分析的更传统的技术相比较,我们评估了其确保运行时不发生类型错误和生成精确调用关系图的能力。在各种各样的Dart项目中,我们发现了许多改进精度的例子。具体而言,该分析能够保证其中81%的表达式,在运行时可能出现的所有类型实际上都是通过执行测试套件来观察的。实验还表明,在某些情况下,精度的限制因素是难以测试覆盖率。在其他的某些情况下的限制因素是依赖性分析的精确度,这表明该技术在未来仍需不断改进。

概述

定义1(测试完整性) 如果测试套件T的执行包含e在运行时可能具有所有的可能类型,则测试套件T关于表达式e的类型完整,写为completeT(e)。

我们的方法涉及四个组成部分:1)动态执行测试套件,2)静态依赖性分析,3)静态类型分析,和4)测试完整性分析。

组合过度近似和缺乏近似

我们的出发点是一个有关动态执行和静态分析的事实:执行测试套件构成了程序行为的缺乏近似,而静态分析(在抽象解释风格中)可以提供过度近似。

36 x = new A(); 37 x.m();

对于第36-37中所示的程序代码,一个简单的静态分析就能够显示出,第37行中x的唯一可能值即是第36行中创建的A对象。该信息不仅告诉我们第37行不能以message-not-understood的运行时错误执行失败,而且告诉我们唯一可能的被调用者只有A类中的m方法。

由于可扩展性,我们希望仅使用上下文不敏感和路径不敏感的静态分析。 这样的精度分析可能就足以满足程序运行的大部分需求,但用动态类型语言编写的程序通常包含需要更多重量级的程序分析技术的代码,例如细化类型或者上下文敏感性。以下是值依赖类型的典例。

51b59a3e12f6a88474265b3ffc97a5eb.png

这里,A有m方法而B没有。 函数g被重载,因为它由参数确定它的返回对象为类型A或者B。 第45行中的调用显然总是返回一个A对象,但是这个事实不能仅通过上下文不敏感的静态分析就断定(它会推断该类型是A或B)。 通过执行覆盖f的测试套件也不能得出A是唯一的可能性这一结论。 如果对g的调用改为返回B对象,则程序将在运行时失败,在已检查模式中第45行中会出现子类型违规错误,在生产模式中第46行会出现message-not-understood错误。

类型分析

静态类型分析部件有两个用意:它解析依赖分析的间接调用,并计算每个表达式可能类型的过度近似。我们在测试完整性的分析中就使用过它。 类型分析只是一种对上下文不敏感且对路径不敏感的基于子集的分析,它同时跟踪功能、方法和类型。 这是一种为大众广泛知晓的分析技术,因此考虑到空间有限,我们省略了该部件工作过程的详述。本次实验我们选择轻量级分析。

实验评估

我们的实验研究以下四个问题:

Q1该技术在多大程度上能够显示真实的Dart程序和测试套件的测试完整性? 或者更具体地说,这些程序和测试套件被计算过的类型覆盖是什么?

Q2混合静态/动态方法是否能够更好地精确检查运行时的类型缺失错误,并且生成精确的调用图信息?

Q3依赖性分析对计算类型覆盖的精确度有多重要?它是我们技术的核心组成部分吗?

Q4在分析无法显示测试完整性的情况下,这个会是静态分析组件中测试不足或精确性缺乏的原因吗?

我们的实现Goodenough由四个部件组成,其中包括使用完整性事实来改进类型分析的类型过滤技术。为了记录运行时表达式的类型,我们使用了一个受Jalangi启发的小型的运行时的检测框架。我们只观察应用程序代码中的变量读取和调用的类型来保持较低的运行时开销,并且我们不会检测库。

评估基于27个基准测试,从小型命令行和Web应用程序到演示库的使用的示例应用程序。列出的基准测试位于表1的第一列。第二列则显示了每个基准测试的代码行数(不包括无法访问的库代码)。我们使用可用于命令行应用程序的现有测试套件;对于Web应用程序,我们通过浏览器手动操作应用程序一分钟的方式,获得测试套件。为了使静态分析的组成及健全有一定的可信度,我们检查了所有运行的结果,并比较是否与静态分析结果显示一致。

3598f9982296aa68d47975bacffb847a.png

Q1类型覆盖

为了回答第一个问题,我们在每个基准测试中运行Goodenough及其测试套件T并测量类型覆盖率CT(X),其中X被选为程序中所有表达式的集合。 结果显示在表1的第三列中。

类型覆盖率通常较高,平均为81%。换句话说,这个分析能够保证在运行时可能出现的所有类型的81%的表达式,这些类型都是通过执行测试套件而被发现。

Q2类型错误和函数关系调用图

为了研究我们的混合静态/动态的途径是否能够测试完整性,这个完整性可用于发现缺少类型错误和推断调用图信息,我们将Goodenough与不利用测试套件中的运行时观察功能的变体进行比较。我们选择出五个衡量精度的指标:

MNU(消息未理解):可能无法访问字段或方法的警告数;

UIC(不安全的隐式转换):隐式向下转发时潜在子类型违规的警告数(仅与检查模式执行相关);

CGS(调用图大小):调用图中的边数;

RF(达到功能):可能达到的功能数量;

PI(多态调用):具有多个潜在调用方的呼叫站点数。

表1中的列MNU,UIC,RF,CGS和PI展示了与全静态分析相比混合分析的结果数的百分比变化。

我们发现混合分析能够极大地提高某些基准测试的精度,而在其他基准测试中则略有改进甚至没有改进。考虑到基准测试的不同性质及其编程风格,这种现象的出现并不奇怪。在一些基准测试里,天真的全静态分析已经获得了MNU或UIC的最佳精度,混合技术已经没有改进的余地。但有趣的是,混合技术在27个基准测试中有19个针对一个或者几个指标进行了改进,这意味着混合方法在各种各样的程序中都具有些许优势。这也证明了,基于推断完整性事实的类型过滤器可以对类型分析的精度产生实质性影响。

静态分析的一个重要的设计决策是:静态分析部件通过对上下文和路径的不敏感,以保持系统的轻量级。通过人工研究我们的混合方法所获得高精度的一些示例,我们不难发现,全静态的替代方案需要高度的上下文敏感性才能得出相同的结论。但众所周知,这种分析并不能很好地扩展。

例如,上下文不敏感的静态分析不足以精确地推断类似于向量数学库的cross函数的返回类型。相比之下,Goodenough发现了测试套件已完成的对cross的19次调用,这使得静态类型分析能够过滤调用类型的相关推断,从而能够避免多次虚假警告。

Q3依赖性分析

依赖性分析发现,在所有函数返回中占73%的表达式对函数输入状态没有类型依赖。这意味着单次执行这些表达式足以涵盖所有可能的类型。在其他16%返回的表达式中,返回类型取决于一个或多个参数的类型或值,但并不取决于堆。对于剩余的11%,依赖性分析发现其取决于堆。

为了研究依赖性分析的重要性,我们使用较弱的依赖性分析重复了Q1和Q2的实验,该分析没有利用类型分析产生调用图相反,这些所有间接调用的调用站点返回值的类型和值,其依赖性被保守地设置为最高。这一单一限制导致被证实的完整性事实数量减少,其精度指标显著下降:类型覆盖率从81%下降到77%,类型错误检测和调用图构造的精度几乎与全静态分析相同。这些结果表明依赖性分析确实是一个关键组成部分。

Q4无法展示测试完整性的原因

为了回答问题4,我们在Goodenough所无法证明测试完整的方面研究了我们的分析行为。有些模式难以量化其原因,有些模式却很清楚。我们的初步调查确定下来两个主要原因:测试套件缺少覆盖率和依赖分析中的堆建模过于粗糙。反过来说,不精确在类型分析中看起来并不重要。

覆盖率对于证明测试完整性确实很重要。在这种情况下,需要在特定浏览器版本上执行两次。在其他的许多情况下,我们发现仅仅是改进测试套件的语句覆盖就有可能显著改善类型覆盖。

对于此技术的第一个设计,我们选择了一种简单的依赖性分析方法,在所有域的加载操作中使用顶部抽象。请考虑以下示例,该示例使用Dart HTML5解析器6中的parse函数。

93 f() {  94 DivElement div = parse("

解析函数总是返回一个HTML元素树,其结构仅由parse的输入决定。依赖性关系分析在原则上可以确定firstChild的查找总是可以找到相同的类型,并且这个分析是能够跟踪堆上的依赖关系的。我们在基准测试中使用的许多库里都看到了类似的情况。这一观察结果表明:扩展依赖性分析直到达到可以包括堆的依赖性,未来大有可为。

结论

我们已经介绍了测试完整性的概念以及混合程序分析,它们用来论证测试套件的充分性和如何证明类型相关的属性。 此外,我们用我们的实现Goodenough,证明了这种分析技术是如何在Dart代码中显示类型错误,并且推断健全的调用图的信息的。对于这些 ,在Dart代码之中全静态的分析十分具有挑战性。

我们还发现在未来可能的有趣工作方向。 一方面,我们计划探索在依赖性分析和关系依赖性中如何更好地进行堆建模,还有如何可以进一步提高类型分析的精度。 而在另一个方面,对于类型覆盖度量如何与其他度量和编程错误相关联,我们打算进行实证研究,并使用类型覆盖来指导自动化测试的生成,并将类型分析结果用于程序优化,将我们的方法应用于其他动态语言,想必会很有趣。

致谢

本文由南京大学软件学院2019级硕士研究生程鹏翻译转述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值