SPARK 确实不能证明(保证)不存在存储错误,因为这些错误是从程序范围之外引发的。对于失败的堆分配以及堆栈空间耗尽时都是如此。
但是,可以通过避免 SPARK 分析中的分配例程来作弊,如下面的示例所示。分配子程序New_Integer
具有 SPARK 可以用来分析指针的后置条件,但子程序的主体被禁止分析。这允许Storage_Error
处理 a 。当然,现在必须注意主体确实符合规范:该Ptr
字段不能是null
whenValid
为真。SPARK 现在只假设这是真的,但不会验证这一点。
注意:可以使用 GNAT CE 2021 证明所有指针取消引用和不存在内存泄漏。但是,将Valid
鉴别器实际设置为False
duringFree
并使用类似Post => P.Valid = False
. 不幸的是,这使得 SPARK 抱怨可能的判别检查失败。
更新(2021 年 6 月 3 日)
我根据@YannickMoy 的提示更新了示例(见下文)。Free
现在确保弱指针的Valid
鉴别器设置为False
返回。
主文件
with Test;
procedure Main with SPARK_Mode is
X : Test.Weak_Int_Ptr := Test.New_Integer (42);
Y : Integer;
begin
-- Dereference.
if X.Valid then
Y := X.Ptr.all;
end if;
-- Free.
Test.Free (X);
end Main;
测试广告
package Test with SPARK_Mode is
type Int_Ptr is access Integer;
-- A weak pointer that may or may not be valid, depending on
-- on whether the allocation succeeded.
type Weak_Int_Ptr (Valid : Boolean := False) is record
case Valid is
when False => null;
when True => Ptr : Int_Ptr;
end case;
end record;
function New_Integer (N : Integer) return Weak_Int_Ptr
with Post => (if New_Integer'Result.Valid then New_Integer'Result.Ptr /= null);
-- Allocates a new integer.
procedure Free (P : in out Weak_Int_Ptr)
with
Pre => not P'Constrained,
Post => P.Valid = False;
-- Deallocates an integer if needed.
end Test;
测试.adb
with Ada.Unchecked_Deallocation;
package body Test with SPARK_Mode is
-----------------
-- New_Integer --
-----------------
function New_Integer (N : Integer) return Weak_Int_Ptr is
pragma SPARK_Mode (Off); -- Refrain body from analysis.
begin
return Weak_Int_Ptr'(Valid => True, Ptr => new Integer'(N));
exception
when Storage_Error =>
return Weak_Int_Ptr'(Valid => False);
end New_Integer;
----------
-- Free --
----------
procedure Free (P : in out Weak_Int_Ptr) is
procedure Local_Free is new Ada.Unchecked_Deallocation
(Object => Integer, Name => Int_Ptr);
begin
if P.Valid then
Local_Free (P.Ptr);
P := Weak_Int_Ptr'(Valid => False);
end if;
end Free;
end Test;
输出(gnatprove)
$ gnatprove -Pdefault.gpr -j0 --level=0 --report=all
Phase 1 of 2: generation of Global contracts ...
Phase 2 of 2: flow analysis and proof ...
main.adb:5:04: info: absence of memory leak at end of scope proved
main.adb:13:13: info: discriminant check proved
main.adb:13:18: info: pointer dereference check proved
main.adb:17:08: info: precondition proved
test.adb:31:23: info: discriminant check proved
test.adb:32:12: info: discriminant check proved
test.adb:32:12: info: absence of memory leak proved
test.ads:22:16: info: postcondition proved
Summary logged in [...]/gnatprove.out
摘要(由OP添加)
提供的代码有助于防止Storage_Error
使用new
关键字进行动态分配。由于 SPARK 已经可以证明无限递归(如评论中所述。请参见此处),唯一可能导致 , 的未解决问题是Storage_Error
在正常执行期间需要比可用堆栈更多的程序。然而,这可以通过 GNATstack 等工具在编译时进行监控和确定(在评论中也提到。请参见此处)。