Ada 阵列中火花计数元件的程序验证

Ada 阵列中火花计数元件的程序验证,ada,spark-ada,Ada,Spark Ada,我写了一个非常简单的程序,但我没能证明它的功能正确性。它使用一个项目列表,每个项目都有一个字段,指示它是免费的还是已使用的: type t_item is record used : boolean := false; value : integer := 0; end record; type t_item_list is array (1 .. MAX_ITEM) of t_item; items : t_item_lis

我写了一个非常简单的程序,但我没能证明它的功能正确性。它使用一个项目列表,每个项目都有一个字段,指示它是免费的还是已使用的:

   type t_item is record
      used  : boolean := false; 
      value : integer   := 0;
   end record;

   type t_item_list is array (1 .. MAX_ITEM) of t_item;
   items       : t_item_list;
还有一个计数器,指示所用元件的数量:

  used_items  : integer   := 0;
append_item过程检查used_items计数器以查看列表是否已满。如果不是,则第一个自由条目将标记为已使用,并且已使用项计数器将递增:

   procedure append_item (value : in  integer; success : out boolean)
   is
   begin

      if used_items = MAX_ITEM then
         success := false;
         return;
      end if;

      for i in items'range loop
         if not items(i).used then
            items(i).value := value;
            items(i).used  := true;
            used_items     := used_items + 1;
            success := true;
            return;
         end if;
      end loop;

      -- Should be unreachable
      raise program_error;
   end append_item;
我不知道如何证明used_items等于列表中used元素的数量。
还请注意,gnatprove消息有时令人费解,我不知道在许多gnatprove/*文件中从何处查找更多信息。事实上,对我来说,主要的困难是找出验证者需要什么。如果您对此有一些指示,我将非常高兴。

将此规范用于
Append\u Item
并不能证明
已使用的\u Items
等于列表中已使用元素的数量,但(删除
raise Program\u Error
)至少可以证明

procedure-Append\u项(值:in-Integer;成功:out-Boolean)
使用Pre=>
已使用项而非项。已使用项),
Post=>
(已用项目的旧项目<最大项目
和Used_Items=Used_Items'Old+1
成功=真)
或(已使用项的旧项=最大项,成功项=假);

计算数据结构中具有给定属性的元素确实很难表达。为了解决这个问题,我们在引理库中提供了泛型计数函数的SPARK pro。《用户指南》中介绍了此高级函数库:

要使用它,您应该修改项目文件以使用引理库的项目文件,并将SPARK_BODY_MODE设置为Off

您还应该将环境变量SPARK_LEMMAS_OBJECT_DIR设置为要为lemma库创建编译和验证工件的对象目录的绝对路径

然后,您可以实例化SPARK.Higher_Order.Fold.Count以满足您的需要。它需要一个无约束的数组类型和一个函数来选择应该计数的元素。因此,我重写了您的代码以提供此信息,并将泛型实例化如下:

   type t_item_list_b is array (positive range <>) of t_item;
   subtype t_item_list is t_item_list_b (1 .. MAX_ITEM);

   function Is_Used (X : t_item) return Boolean is
     (X.used);

   package Count_Used is new SPARK.Higher_Order.Fold.Count
     (Index_Type => Positive,
      Element    => t_item,
      Array_Type => t_item_list_b,
      Choose     => Is_Used);
procedure append_item
 (value    : in  integer;
  success  : out boolean)
 with ...
is
  Old_Items : t_item_list := items with Ghost;
begin

  if used_items = MAX_ITEM then
     success := false;
     return;
  end if;

  for i in items'range loop
     if not items(i).used then
        items(i).value := value;
        items(i).used  := true;
        used_items     := used_items + 1;
        success := true;
        Count_Used.Update_Count (items, Old_Items, I);
        return;
     end if;
  end loop;

  -- Should be unreachable
  raise program_error;
end append_item;
  • 证明计数的常用引理:Count_Zero证明Count的结果为0,即数组中没有元素具有该属性,Update_Count知道更新数组时如何修改Count。这些特性对于一个人来说是显而易见的,但事实上,它们需要归纳来证明,因此它们通常是自动求解器无法实现的。为了证明append_item,我现在只需要在item更新后调用Update_Count,如下所示:

       type t_item_list_b is array (positive range <>) of t_item;
       subtype t_item_list is t_item_list_b (1 .. MAX_ITEM);
    
       function Is_Used (X : t_item) return Boolean is
         (X.used);
    
       package Count_Used is new SPARK.Higher_Order.Fold.Count
         (Index_Type => Positive,
          Element    => t_item,
          Array_Type => t_item_list_b,
          Choose     => Is_Used);
    
    procedure append_item
     (value    : in  integer;
      success  : out boolean)
     with ...
    is
      Old_Items : t_item_list := items with Ghost;
    begin
    
      if used_items = MAX_ITEM then
         success := false;
         return;
      end if;
    
      for i in items'range loop
         if not items(i).used then
            items(i).value := value;
            items(i).used  := true;
            used_items     := used_items + 1;
            success := true;
            Count_Used.Update_Count (items, Old_Items, I);
            return;
         end if;
      end loop;
    
      -- Should be unreachable
      raise program_error;
    end append_item;
    
  • 我希望这有帮助


    致以最诚挚的问候,

    我喜欢西蒙斯的方法,我想它已经接近成功了

    我以此为出发点,并应用了一些更改,我能够使用SPARK community edition证明这些更改,而不需要额外的支持包

    我做的第一件事就是利用Ada强大的类型来尽可能多地约束类型。特别是,我没有将Used_Items定义为整数,而是定义了一个Element_Count子类型,其范围不能超过Max_Items。应用这些约束越多,传递给验证者的工作量就越少

    然后我创建了一个整数列表类型作为更高级别的抽象类型, 并将数组类型和元素类型移动到包的私有部分

    这样做,我发现简化了界面,我想。因此,创建在前置条件中使用的辅助函数(Length和Is_Full)是有意义的 更简单地向客户机表达属性,这很有帮助,因为它们在前置和后置条件中重复了多次,但在包的私有部分进行了扩展,以更具体地提供细节。 我在前置条件和后置条件中使用了条件表达式,因为我认为 更清楚地向读者表达合同

    我发现我唯一需要添加的另一件事是身体中的循环不变量 附加_项的。验证人告诉我我丢失了一个循环不变量, 我补充道。您基本上需要证明,如果没有 陷入if语句中,找到一个槽来添加新值

    package Array_Item_Lists with SPARK_Mode is
    
       Max_Item : constant := 3;
    
       subtype Element_Count is Natural range 0 .. Max_Item;
    
       type Integer_List is private;
    
       function Length (List : Integer_List) return Element_Count;
    
       function Is_Full (List : Integer_List) return Boolean;
    
       procedure Append_Item (List    : in out Integer_List;
                              Value   : Integer;
                              Success : out Boolean)
         with
           Pre  => (if Length (List) < Max_Item
                          then not Is_Full (List)
                          else Is_Full (List)),
           Post =>
                 (if Length (List'Old) < Max_Item
                  then Length (List) = Length (List'Old) + 1
                  and then Success 
                 else (Length (List'Old) = Max_Item and then Success = False));
    
    private
    
       type t_item is record
          used  : Boolean := False; 
          value : Integer   := 0;
       end record;
    
       type t_item_list is
         array (Element_Count range 1 .. Element_Count'Last) of t_item;
    
       type Integer_List is
          record
             Items : t_item_list;
             used_items : Element_Count := 0;
          end record;
    
       function Length (List : Integer_List) return Element_Count is
          (List.used_items);
    
       function Is_Full (List : Integer_List) return Boolean is
          (for all Item of List.Items => Item.used);
    
    end Array_Item_Lists;
    
    
    pragma Ada_2012;
    package body Array_Item_Lists with SPARK_Mode is
    
       procedure Append_Item (List    : in out Integer_List;
                              Value   : Integer;
                              Success : out Boolean) is
       begin
    
          Success := False;
    
          if List.used_items = Max_Item then
             return;
          end if;
    
          for i in List.Items'Range loop
    
             pragma Loop_Invariant
               (for some j in i .. Max_Item => not List.Items (j).used);
    
             if not List.Items (i).used then
                List.Items (i).value := Value;
                List.Items (i).used  := True;
                List.used_items     := List.used_items + 1;
                Success := True;
                return;
             end if;
          end loop;
    
       end Append_Item;
    
    end Array_Item_Lists;
    
    使用SPARK_模式的包数组_项目_列表已禁用
    最大项:常数:=3;
    子类型元素计数的自然范围为0。。最大项;
    类型整型_列表是私有的;
    函数长度(列表:整数列表)返回元素计数;
    函数已满(列表:整数列表)返回布尔值;
    程序追加\u项(列表:输入输出整数\u列表;
    值:整数;
    成功:输出布尔值)
    具有
    Pre=>(如果长度(列表)
    (如果长度(列表旧项)Item.used的所有项);
    结束数组\项目\列表;
    布拉格马阿达_2012;
    使用SPARK_模式的包体数组_项_列表为
    程序追加\u项(列表:输入输出整数\u列表;
    值:整数;
    成功:欧
    
    package Array_Item_Lists with SPARK_Mode is
    
       Max_Item : constant := 3; -- Set to whatever limit is desired
    
       subtype Element_Count is Natural range 0 .. Max_Item;
       subtype Element_Index is Natural range 1 .. Max_Item;
    
       type Integer_List is private;
    
       function Create return Integer_List
         with Post => Length (Create'Result) = 0
           and then Used_Count (Create'Result) = 0
           and then not Is_Full (Create'Result)
           and then Not_Full (Create'Result)
           and then (for all I in 1 .. Max_Item =>
                       not Has_Element (Create'Result, I));
    
       function Length (List : Integer_List) return Element_Count;
       function Used_Count (List : Integer_List) return Element_Count;
    
       --  Is_Full is based on Length being = Max_Item
       function Is_Full (List : Integer_List) return Boolean;
    
       --  Not_Full is based on there being empty slots in the list available
       --  Since the length is kept in sync with number of used slots, the
       --  negation of one result should be equivalent to the result of the other 
       function Not_Full (List : Integer_List) return Boolean;
    
       function Next_Index (List : Integer_List) return Element_Index
         with Pre => Used_Count (List) = Length (List)
         and then Length (List) < Max_Item and then Not_Full (List),
              Post => not Has_Element (List, Next_Index'Result);
    
       function Element (List  : Integer_List;
                         Index : Element_Index) return Integer;
    
       function Has_Element (List  : Integer_List;
                             Index : Element_Index) return Boolean;
    
       procedure Append_Item (List    : in out Integer_List;
                              Value   : Integer;
                              Success : out Boolean)
       with
         Pre  => Used_Count (List) = Length (List)
            and then (if Length (List) < Max_Item
                       then Not_Full (List) and then
                         not Has_Element (List, Next_Index (List))
                     else Is_Full (List)),
         Post =>
           (if not Is_Full (List) then Not_Full (List)) and then
             (if Length (List'Old) < Max_Item
                      then Success
              and then Length (List) = Length (List'Old) + 1
              and then Element (List, Next_Index (List'Old)) = Value
              and then Has_Element (List, Next_Index (List'Old))
              and then (for all I in 1 .. Max_Item =>
                          (if I /= Next_Index (List'Old) then
                               Element (List'Old, I) = Element (List, I)
                           and then
                             Has_Element (List'Old, I) = Has_Element (List, I)))
              and then Used_Count (List) = Used_Count (List'Old) + 1
    
               else not Success and then
                    Length (List) = Max_Item and then List'Old = List
             and then Used_Count (List) = Max_Item);
    
    private
    
       type t_item is record
          Used  : Boolean := False; 
          Value : Integer   := 0;
       end record;
    
       type t_item_list is
         array (Element_Count range 1 .. Element_Count'Last) of t_item;
    
       type Integer_List is
          record
             Items      : t_item_list := (others => (Used => False, Value => 0));
             Used_Items : Element_Count := 0;
          end record;
    
       function Element (List  : Integer_List;
                         Index : Element_Index) return Integer is
         (List.Items (Index).Value);
    
       function Has_Element (List  : Integer_List;
                             Index : Element_Index) return Boolean is
          (List.Items (Index).Used);
    
       function Length (List : Integer_List) return Element_Count is
          (List.Used_Items);
    
       function Is_Full (List : Integer_List) return Boolean is
         (for all Item of List.Items => Item.Used
          and then Length (List) = Max_Item);
    
       function Not_Full (List : Integer_List) return Boolean is
         (for some Item of List.Items => not Item.Used
          --  Used_Count (List) < Max_Item
         );
    
    end Array_Item_Lists;
    
    pragma Ada_2012;
    package body Array_Item_Lists with SPARK_Mode is
    
       procedure Append_Item (List    : in out Integer_List;
                              Value   :        Integer;
                              Success : out    Boolean)
       is
          Old_Used_Count : constant Element_Count := Used_Count (List);
       begin
    
          if List.Used_Items = Max_Item then
             Success := False;
             return;
          end if;
    
          declare
             Update_Index : constant Element_Index := Next_Index (List);
          begin
    
             pragma Assert (List.Items (Update_Index).Used = False);
    
             List.Items (Update_Index) := (Value => Value, Used => True);
             List.Used_Items     := List.Used_Items + 1;
             Success := True;
             pragma Assert (List.Items (Update_Index).Used = True);
    
             --  We have proven that one the one element of the array
             --  has been modified, and that it was previous not used,
             --  and that not it is used. From this, we can now assume that
             --  the use count was incremented by one
             pragma Assume (Used_Count (List) = Old_Used_Count + 1);
    
             --  If the length isn't full (Is_Full) we can assume the
             --  number of used items has room also. We incremented both
             --  of these above, and the two numbers are always in sync.
             pragma Assume (if not Is_Full (List) then Not_Full (List));
    
          end;
    
       end Append_Item;
    
       -----------------------------------------------------------------
    
       function Create return Integer_List is
          Result : Integer_List := (Items => <>,
                                    Used_Items => 0);
       begin
    
          for I in Result.Items'Range loop         
             Result.Items (I) := (Used => False, Value => 0);
    
             pragma Loop_Invariant
               (for all J in 1 .. I => Result.Items (J).Used = False);
    
          end loop;
    
          pragma Assert (for all Item of Result.Items => Item.Used = False);
    
          --  Since we have just proven that all items are not used, we know
          --  the Used_Count has to be zero, and hence we are not full
          --  so we can make the following assumptions
          pragma Assume (Used_Count (Result) = 0);
          pragma Assume (Not_Full (Result));
    
          return Result;
       end Create;
    
       -----------------------------------------------------------------
    
       function Next_Index (List : Integer_List) return Element_Index
       is
          Result : Element_Index := 1;
       begin
    
          Search_Loop :
          for I in List.Items'Range loop
    
             pragma Loop_Invariant
                (for some J in I .. Max_Item => not List.Items (J).Used);
    
             if not List.Items (I).Used then
                Result := I;
                exit Search_Loop;
             end if;
          end loop Search_Loop;
    
          return Result;
       end Next_Index;
    
       function Used_Count (List : Integer_List) return Element_Count is
          Count : Element_Count := 0;
       begin
          for Item of List.Items loop
             if Item.Used then
                Count := Count + 1;
             end if;
          end loop;
    
          return Count;
       end Used_Count;
    
    end Array_Item_Lists;
    
    with Ada.Text_IO; use Ada.Text_IO;
    with Array_Item_Lists;
    
    procedure Main with SPARK_Mode
    is
       List : Array_Item_Lists.Integer_List := Array_Item_Lists.Create;
       Success : Boolean;
    begin
    
       Array_Item_Lists.Append_Item (List    => List,
                                     Value   => 3,
                                     Success => Success);
    
       pragma Assert (Success);
    
       Array_Item_Lists.Append_Item (List    => List,
                                     Value   => 4,
                                     Success => Success);
    
       pragma Assert (Success);
    
       Array_Item_Lists.Append_Item (List    => List,
                                     Value   => 5,
                                     Success => Success);
    
       pragma Assert (Success);
    
       Array_Item_Lists.Append_Item (List    => List,
                                     Value   => 6,
                                     Success => Success);
    
       pragma Assert (not Success);
    
       Put_Line ("List " &
                 (if Array_Item_Lists.Is_Full (List)
                    then "is Full!" else "has room!"));
    end Main;