7918年Electronicdesign Adacorepromo

READIEM用于错误 - 验证软件,第2部分:通过SPARK 2014正式验证

2015年1月7日
通过像SPARK这样的工具,可以以与静态分析类似的方式使用形式验证,以发现代码中的所有运行时错误,甚至是最不可能的、可能多年都未被发现的错误,比如Split计算中的数值溢出。

本文是其中的一部分嵌入式软件系列:使用ADA合同强制编码

下载本文以.pdf格式

在我们之前的文章中(bug验证软件的安魂曲:测试与静态分析),我们介绍了一个样本ADA程序,用于执行排序阵列的二进制搜索,我们使用传统测试和代码仪静态分析工具来定位错误。这是我们提出的代码:

类型Integer_Array是数组(正范围<>)的整数;——正的是整数的子集在范围1 ..Integer'Last——一个Integer_Array类型的对象有一个正的下界和任何上界函数取值:Integer) return Natural is——Natural是整数值的子集,取值范围为0 ..整数'最后的左,右,分裂:正;如果A'Length = 0,则返回0;如果;左:= 'First;右:= 'Last;如果A(左)>值然后返回0; end if; while Left <= Right loop Split := (Left + Right) / 2; if A (Split) = Value then return Split; elsif A (Split) < Value then Left := Split + 1; else Right := Split - 1; end if; end loop; return 0; end Search;

现在让我们使用SPARK 2014技术来进行正式的验证。1SPARK 2014包括一个语言(Ada 2012的子集)和一个静态分析工具,可以用来证明各种程序属性。它的好处在某种程度上取决于你付出了多少努力。您可以尝试消除运行时错误(即,确保不会出现超出边界的索引这样的错误)。或者,您可以针对正式指定的需求进行完整的功能验证。然而,这通常需要添加契约。

在Ada 2012和SPARK 2014中,契约是布尔表达式,可以附加到子程序声明中,指定它们对调用者的要求(前置条件)和保证结果(后置条件)。在本例中,二进制搜索只适用于已排序的数组(这是前提条件),如果数组中没有值,它将返回0,或者为找到的值返回一个索引。在Ada 2012(和SPARK 2014)中,你可以将此类合同表述如下:

函数Sorted (A: Integer_Array)返回Boolean is (A'Length < 2或else(对于A'First ..A'Last - 1 =>(对于X + 1中的所有Y。A'Last => A (X) <= A (Y))));函数搜索(A: Integer_Array;值:整数)返回自然与Pre =>排序(A), Post =>(如果搜索'结果= 0然后(所有索引在A'范围=> A(索引)/=值)else A(搜索'结果)=值));

我们首先引入了一个布尔函数“Sorted”,如果它的参数是排序的,则返回True。请注意Ada 2012量化表达的使用。然后,通过将表达式sorted (A)放在前置条件中,将Search函数的应用限制在排序数组中。最后,在后置条件中表示函数结果的预期值:如果返回0,则Value不在数组中,如果返回其他数字,则这是数组的一个下标,在这个下标处的数组元素包含Value。

通过在代码警报上运行SPARK工具,我们可以得到一些问题的警报。它确定了三个可能的运行时错误,并指出它不能建立后置条件。让我们逐一看看这些问题。

第一个可能的运行时错误在这一行中:

拆分:=(左+右)/ 2;

显然,加法可能会溢出,也就是说,当我们有一个很大的数组,并且在靠近右边的地方搜索时。同样的错误也在其他二进制搜索的实现中被发现。2

实际上,在Ada中,即使使用小阵列,这个问题也会出现。这是因为ADA中的数组的第一个索引可以是靠近最大可代表整数的一些大的正数。因此,我们需要避免溢出,这意味着写入分裂点的计算有点不同:

分割:=左+(右-左)/ 2;

毫无疑问,我们不是通过测试发现这一点的;我们应该用非常大的数组来测试。CodePeer的默认设置试图最小化“假警报”,因此CodePeer没有报告潜在的溢出。在工具的另一组选项下,问题就会被检测到。

下一个可能的运行时错误如下:

如果A (Split) = Value then

这里的火花抱怨拆分可能不在阵列的范围内。但是,由于左右在阵列的范围内,拆分清楚地在范围内。

实际上,这是SPARK工具的错误警报。问题是,在这一点上,SPARK不能单独计算出这些变量的可能范围,特别是因为它们是在循环中修改的。实际上,需要向SPARK解释在循环中修改的变量发生了什么。你可以使用pragma Loop_Invariant来做到这一点。这里,有趣的属性是Left和Right总是在数组范围内:

while Left <= Right循环pragma Loop_Invariant(左在A'Range内);pragma Loop_Invariant(在A'Range的右边);...

这消除了关于Split的错误消息。同样,这不是一个真正的错误,而是用户没有提供足够的信息使SPARK意识到问题永远不会发生的情况。

Spark报告的最后一次运行时错误是此处添加的可能溢出:

elsif a(split)<值然后左:= split + 1;

为什么Split会变得这么大,添加一个就会使它溢出?这可能发生在A'Last接近最大整数值;例如,对于非常大的数组,或者,就像上面的溢出一样,对于Ada中下界较大的小数组。不管怎样,Left不应该保持在数组范围内吗?事实上,这个错误与我们通过测试发现的第一个错误非常相似。我们需要防止查找的值大于数组中最大(最右边)值的情况。

要解决此问题,我们在While循环之前添加以下测试:

如果a(右)<值然后返回0;万一;

这使得我们的代码没有运行时错误!但是它总是返回正确的结果吗?SPARK仍然抱怨后置条件。

再一次,通过告诉它维持哪个财产,我们需要帮助火花的情况。在这种情况下,它实际上非常简单:我们只需指示我们正在搜索的数组区域。换句话说,我们需要指示我们已经排除在外的阵列的区域。我们通过Pragma Loop_Invariant表示此属性:

pragma Loop_Invariant (A'Range => (if Index < Left or Index > Right then A (Index) /= Value));

SPARK现在可以证明整个代码是正确的,没有运行时错误,功能上是完全正确的(也就是说,如果前提条件得到满足,它的后置条件将为真)。

下面是最终的实现:

功能搜索(A:Integer_Array;值:Integer)返回自然左,右,分裂:正面;如果A'Length = 0,则返回0;如果;左:= 'First;右:= 'Last;如果A(左)>值然后返回0;如果;如果a(右)<值然后返回0;如果;while Left <= Right循环pragma Loop_Invariant(左在A'Range内); pragma Loop_Invariant (Right in A'Range); -- Value is not before left and not after right pragma Loop_Invariant (for all Index in A'Range => (if Index < Left or Index > Right then A (Index) /= Value)); Split := Left + (Right - Left) / 2; if A (Split) = Value then return Split; elsif A (Split) < Value then Left := Split + 1; else Right := Split - 1; end if; end loop; return 0; end Search;

总结

在我的比较中,不同的验证方法如何票价?首先,我的初始实施并不是太糟糕 - 它总是计算正确的结果,但无法防止一些可能的运行时错误。因此,也许比较不是那么有意义,因为没有找到功能性错误。

测试让我们确信程序在功能上是正确的,它发现了一个常见的运行时错误(搜索比数组中最小项小的值),但错过了其他一些错误(搜索空数组和搜索比最大元素大的值)。如果进行更广泛的测试,可能会发现空数组的情况。

CodePeer非常容易运行;比编写测试简单得多。在其默认设置中,CodePeer发现了一个很可能发生的错误,即空数组的情况。当将CodePeer设置为潜在错误的最大检测时,它将发现示例中的所有运行时错误。

像CodePeer这样的静态分析工具的一个限制是,它主要用于查找可能的运行时错误。但是,它通常找不到功能错误,因为CodePeer不知道代码应该做什么。尽管如此,它将指出许多可能影响功能正确性的可疑情况。但在我的例子中并非如此。也就是说,静态分析非常容易应用,所以它应该始终是验证活动的一部分。您可以控制潜在错误的最大检测和最小的“假警报”报告之间的权衡。

最后,形式化验证(例如,使用SPARK)可以以与静态分析相似的方式进行全部代码中的运行时间错误,即使是多年可能未被发现的最不可能的错误,例如拆分计算中的数值溢出。此外,正式验证可以告诉您您的代码在所有情况下是否在功能上正确。这是正式验证(Spark)提供比静态分析(Codepeer)提供更多功率的地方,因为表达功能正确性通常需要复杂的断言(如对所有人对于静态分析工具来说太复杂而无法使用。

但是,正式核查需要更多的努力,因为您需要包括计划要求的正式规范(例如,通过合同)。根据您的应用,这项努力可能是矫枉过正(例如,仅在内部使用的实用程序)或相反,可能对用户的安全或安全性至关重要。

更多信息请参阅嵌入式软件系列:使用ADA合同强制编码

下载本文以.pdf格式

引用:

1.Spark 2014.

2.额外的,额外 - 阅读所有内容:几乎所有二进制搜索和合并都被打破了


来自我们的合作伙伴

功率完整性表征信心

定价适合任何预算,WavesURERER 4000HD具有比您想象的电源轨,电源测序和电源管理更多的能力更多。

超高可靠性和低延迟

当你读这篇文章时,5G正在美国推广。有些人有一个兼容5G的手机,可以连接到AT&T网络,T-Mobile,等等。

协处理器架构:用于快速原型设计的嵌入式系统架构

2021年7月6日
编者注:尽管它以数字处理性能和吞吐量而闻名,但协处理器体系结构提供了嵌入式系统…

粘合材料解决设计难题

幸运的是,已经找到了一个简单的解决方案,用于热管理和EMI屏蔽或吸收 - 3M提供热界面材料和选择......

GaN基和硅基fet基有源箝位反激变换器的比较

摘自2018年德州仪器电源设计研讨会。SEM2300,话题3。TI文献编号:SLUP380。©2018德州仪器公司…

11种方法保护你的动力之路2德州仪器

我2019 TI.com。11种方法保护您的电源路径设计技巧和权衡使用TI的电源开关。目录简介:权力基础…

声音你的意见!

本网站要求您注册或登录后发表评论。
尚未添加任何评论。想要开始对话?

来自我们的合作伙伴

功率完整性表征信心

定价适合任何预算,WavesURERER 4000HD具有比您想象的电源轨,电源测序和电源管理更多的能力更多。

超高可靠性和低延迟

当你读这篇文章时,5G正在美国推广。有些人有一个兼容5G的手机,可以连接到AT&T网络,T-Mobile,等等。

协处理器架构:用于快速原型设计的嵌入式系统架构

编者注:尽管它以数字处理性能和吞吐量而闻名,但协处理器体系结构提供了嵌入式系统…

粘合材料解决设计难题

幸运的是,已经找到了一个简单的解决方案,用于热管理和EMI屏蔽或吸收 - 3M提供热界面材料和选择......

GaN基和硅基fet基有源箝位反激变换器的比较

摘自2018年德州仪器电源设计研讨会。SEM2300,话题3。TI文献编号:SLUP380。©2018德州仪器公司…
Baidu