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