如何检查Spark_Ada中的存储_错误

如何检查Spark_Ada中的存储_错误,ada,spark-ada,Ada,Spark Ada,根据Spark2014文档,不允许处理Spark代码中的异常 通过验证,大多数运行时错误都可以排除在编写的程序中,但不能排除诸如存储错误之类的异常 由于每次过程/函数调用或使用new动态分配内存时都可能发生Storage\u错误(如果我在这一点上出错,请纠正我),因此在Spark\u Mode=off的代码段中捕获和处理此异常仅在程序的最高级别(程序的入口点)有效. 我真的不喜欢这种方法,因为您几乎失去了对这种异常做出反应的所有可能性 我想做什么: 假设使用过程Add()创建一个无界树。在这个过

根据Spark2014文档,不允许处理Spark代码中的异常

通过验证,大多数运行时错误都可以排除在编写的程序中,但不能排除诸如
存储错误
之类的异常

由于每次过程/函数调用或使用
new
动态分配内存时都可能发生
Storage\u错误
(如果我在这一点上出错,请纠正我),因此在Spark\u Mode=off的代码段中捕获和处理此异常仅在程序的最高级别(程序的入口点)有效. 我真的不喜欢这种方法,因为您几乎失去了对这种异常做出反应的所有可能性

我想做什么:

假设使用过程
Add()
创建一个无界树。在这个过程中,我想检查堆上是否有足够的空间在树中创建一个新节点。 如果有,为节点分配内存并将其添加到树中,否则可以给出out参数,其中设置了某种标志

我已经搜索了Spark用户指南,但没有找到一种处理方法,只是程序员必须确保有足够的可用内存,但没有找到如何处理的方法


如何处理Spark中的此类异常?

我认为您可以创建自己的存储池(ff)以支持附加操作“新建是否可以?”。使其免受并发攻击将更加复杂

我想您可以让它的
分配
吞下异常并返回
null
。不管怎样,我想你得把这样的东西编成“外部火花”

SPARK确实无法证明(保证)没有存储错误,因为这些错误是从程序范围之外产生的。这对于失败的堆分配以及堆栈空间耗尽都是正确的

然而,如下面的示例所示,通过避免分配例程进行SPARK分析,可能会有一点欺骗。分配子程序
New_Integer
具有SPARK可用于分析指针的后置条件,但子程序的主体不进行分析。这允许处理
存储错误
。当然,现在必须注意主体确实符合规范:
Ptr
字段在
Valid
为true时不得为
null
。SPARK现在只假设这是真的,但不会验证这一点

注意:可以使用GNAT CE 2021证明所有指针解引用和内存泄漏的存在。但是,如果在
免费期间将
Valid
鉴别器设置为
False
,并使用类似于
Post=>P.Valid=False
的后置条件,那就太好了。不幸的是,这使得SPARK抱怨可能的鉴别检查失败

更新(2021年6月3日)

我根据@YannickMoy的提示更新了示例(见下文)
Free
现在可以确保弱指针的
Valid
鉴别器在返回时设置为
False

main.adb

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;
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;
测试.广告

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;
test.adb

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;
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)

摘要(由OP添加)


提供的代码有助于防止使用
new
关键字动态分配
Storage\u Error
。由于SPARK已经证明了无限递归(如注释中所述。请参阅),唯一可能导致
存储错误的公开问题将是程序在正常执行期间需要比可用堆栈更多的堆栈。但是,这可以通过诸如GNATstack之类的工具(也在注释中提到。请参见)在编译时进行监视和确定。

这确实很好,但用户定义的存储池(尚未)在SPARK中可用(另请参见,静态语义,第4段)。不过,您当然可以始终静态分配一些阵列,并将其用作“存储池”,而不使用Ada的存储池设施。谢谢您的回答。正如@DeeDee所提到的,使用阵列作为“存储池”也可能是一种选择,但在这种情况下,您将被限制在阵列的大小上,这并不像我想要实现的那样没有绑定。感谢您的详细回答和代码示例。在free之后将Valid标志设置为True并不是很好,但是对于一个简短的示例来说,这非常好。令人遗憾的是,您必须关闭Spark_模式,而此解决方案只能防止
存储错误
动态分配。超过堆栈大小太多的递归仍然可能发生,对吗?@mhatzl是的,这仍然可能发生。尽管SPARK可以保证调用堆栈(即堆栈帧数)始终保持有界(例如,没有无限递归;另请参见),但它仍可能超过存储限制。要分析堆栈使用情况和相关风险,可以使用其他(商业)分析工具,例如。事实上,您可以添加想要释放的后置条件,前提是您指定参数P不受前置条件约束
不受P'constrated
,并且您为type
弱Int\u Ptr
提供一个默认判别式,以便您可以声明该类型的无约束值(其判别式可以更改)如果
类型弱\u Int\Ptr(有效:布尔:=False)是记录…
则GNATprove证明了代码。@YannickMoy感谢您的提示。我更新了答案。