Ada Tutorial(2)SPARK Examiner + SPARK Prover

文章目录

  • 代码 Task1.adb
  • 代码 task3.adb
  • task4.adb

在Ada和SPARK中,SPARK_Mode是一个编译指示,它表示随后的代码将使用SPARK语言规则进行编译和分析。

在with SPARK_Mode => On的影响下,编译器会在编译过程中应用SPARK语言规则,它比Ada有更严格的要求,例如禁止某些可能导致不确定行为的构造。此外,打开SPARK_Mode还会允许一些只有在SPARK中才有的特性,例如契约(即前置条件和后置条件)。

SPARK_Mode => On 的具体影响可能会因编译器和SPARK工具的版本而略有不同,但基本上,这个编译指示都会让编译器对随后的代码应用SPARK语言规则。在上面的代码中,SPARK_Mode => On应用于整个Task4包体,这意味着这个包体的所有代码都将使用SPARK规则进行编译和分析。


package body Task4 with SPARK_Mode 表示你将要开始定义一个名为 Task4 的包体,并且这个包体会使用 SPARK 的一些规则进行编译和分析。不过这里缺少 => On=> Off 来显式地开启或关闭 SPARK_Mode

SPARK_Mode => On 表示你要在这个包体中使用 SPARK 的语言规则。这通常意味着代码需要满足更严格的要求,例如避免使用可能导致不确定行为的构造。
SPARK_Mode => Off 表示你要在这个包体中使用标准 Ada 的语言规则,而不使用 SPARK 的规则。
请注意,这只是编译指示,不会改变程序的行为,而是改变编译器和工具对代码的理解和处理方式。它可以帮助你编写更安全、更可靠的代码,特别是当你使用 SPARK 的一些高级特性(例如契约)时。

代码 Task1.adb

package body Task1 with
     SPARK_Mode => On
is

   procedure Task1Procedure(Result : out Integer) is
      Ok : Boolean;
      I, J : Integer;
   begin
      if Ok then
         I := 0;
         J := 0;
      end if;

      Result := I + J;
   end Task1Procedure;

end Task1;

题目:run SPARK examiner over the source code in task1.adb by going to Spark → Examine File from the GPS menu.
This reports that the variable Ok is used without being initialised, and the variables I and J are only initialised if Ok is true. Note the difference between a variable being never initialised (Ok), and a variable possibly being uninitialised (I and J).
Note the differences between the compiler errors/warnings, and the SPARK examiner errors/warnings.

  • SPARK Examiner 是 SPARK 工具集中的一个组件,它负责对 SPARK 程序进行语法和语义分析,以及数据和信息流分析。

  • 语法和语义分析: SPARK Examiner 负责检查程序是否遵守 SPARK Ada 的语法和语义规则。因为 SPARK 是 Ada 的一个子集,其规则更加严格,用来保证程序的可靠性和安全性。

  • 数据和信息流分析: 这是 SPARK Examiner 的一个主要功能,它会分析数据在程序中的流动路径,以及数据之间的依赖关系。通过这个分析,Examiner 可以确保所有的数据在被读取之前已经被赋值,所有的输入都被正确地使用,没有数据竞态等问题。

通过这些分析,SPARK Examiner 可以在程序执行之前就发现许多潜在的错误和问题,从而帮助提高程序的可靠性和安全性。这些分析结果也为后续的 SPARK Prover(一个用于进行形式化证明的工具)提供了基础。

问题:Modify the source code from task 1 to include an ineffective statement, which is a statement that can be removed from a program without changing the behaviour of that program. Run the examiner over the modification, and analyse the results. Do these types of problems look familiar? (HINT: Think back to data-flow analysis in SWEN90006!)

  • 在上述的 Ada 代码中,Ok 变量被声明但从未初始化或赋值,但在程序中仍进行了 if Ok then 的判断,这其实已经是一个无效语句,因为它的行为是不确定的。 但如果我们想要明确添加一个无效语句,我们可以添加一些不会改变程序行为的代码。比如,我们可以在程序的末尾添加一个对变量 IJ 的赋值,如下:
package body Task1
with
     SPARK_Mode => On
is

   procedure Task1Procedure(Result : out Integer) is
      Ok : Boolean;
      I, J : Integer;
   begin
      if Ok then
         I := 0;
         J := 0;
      end if;

      Result := I + J;
      I := I;  -- 添加的无效语句
   end Task1Procedure;

end Task1;

代码 task3.adb

问题:Now, open and compile the source code in task3.adb, and then run the SPARK examiner and GNATProve over the file. To run GNATProve, select Spark → Prove File. Click Execute.
Why do you think the proof from task3.adb could not be proved?

package body Task3 with
     SPARK_Mode => On
is

   procedure Task3Procedure(Input : in Integer; Result : out Integer)
   is
   begin
--      if Input * 10 <= Integer'Last and
--        Input * 10 >= Integer'First then
         Result := Input * 10;
--      else
--         Result := Input;
--      end if;
   end Task3Procedure;
end Task3;

Ada Tutorial(2)SPARK Examiner + SPARK Prover_第1张图片

  • 根据提供的代码片段,这段程序可能无法通过 SPARK Prover 的证明,最可能的原因是它没有明确设定Result的范围。
  • 在这段代码中,我们的程序将输入参数Input乘以10并将结果存储在Result中。这里可能的问题是,如果Input的值过大(比如,接近整数类型的上限),那么乘以10之后可能会导致整数溢出。这样,Result就可能会包含一个无效的值,从而导致证明失败。

题目:Go to the source file task3.adb and uncomment the commented lines. Re-run the the SPARK examiner and GNATProve, and see what changes.
Is the program still not proved? If not, try to change the program to correct this problem.
HINT: Remember Integer’First Integer’Last return the lowest and highest integers respectively.
If you are struggling with this task, complete the rest of the workshop and come back to it.

package body Task3 with
     SPARK_Mode => On
is

   procedure Task3Procedure(Input : in Integer; Result : out Integer)
   is
   begin
    if Input * 10 <= Integer'Last and
      Input * 10 >= Integer'First then
         Result := Input * 10;
    else
       Result := Input;
    end if;
   end Task3Procedure;
end Task3;
  • 当我们将原本注释的部分解开之后,发现还是无法证明
  • 这是因为当验证 Input * 10 的时候这个值就已经 overflow 了,因此,我们应该将代码改成下面的形式:
    Ada Tutorial(2)SPARK Examiner + SPARK Prover_第2张图片

task4.adb

-- task4.adb
package body Task4 with
SPARK_Mode => On
is
   procedure Task4Procedure(AnArray : in out MyArray; AnIndex : in Index) is
   begin
      AnArray(AnIndex) := AnArray(AnIndex) + 1;
   end Task4Procedure;
end Task4;
-- task4.ads 文件
package Task4 with
SPARK_Mode => On
is

   subtype Index is Integer range 1 .. 10;
   type MyArray is array(Index) of Integer;

   -- Returns the element of AnArray at the specified index, AnIndex.
   procedure Task4Procedure(AnArray : in out MyArray; AnIndex : in Index);
end Task4;

问题: Run the SPARK examiner and GNATProve over the code task4.adb.
The inability to prove this is actually an indication that the program is not a valid SPARK program (although this is not always the case – sometimes the tools are just not powerful enough to prove some properties). What do you think this failed proof relates to? How could you re-write this to arrive at a correct SPARK program? The relevant types are declared in task4.ads.

  • 在 SPARK 程序中,对于任何可能引发运行时错误的操作,都需要提供足够的前提条件来证明这种错误不会发生。Task4Procedure 中,直接访问数组元素 AnArray(AnIndex) 可能会导致越界错误,如果 AnIndex 不在 AnArray 的索引范围内。

  • 同时,还要注意的是 AnArray(AnIndex) + 1 可能会导致整数溢出,如果 AnArray(AnIndex) 已经是 Integer'Last

  • 所以,需要提供前提条件,以证明 AnIndexAnArray 的索引范围内,并且 AnArray(AnIndex) 不是 Integer'Last。在 SPARK 中,可以使用合同 (contracts) 来提供这样的前提条件。具体的修订版本如下:

-- task4.ads 文件
package Task4 with
SPARK_Mode => On
is

   subtype Index is Integer range 1 .. 10;
   type MyArray is array(Index) of Integer;

   -- Returns the element of AnArray at the specified index, AnIndex.
   procedure Task4Procedure(AnArray : in out MyArray; AnIndex : in Index) with
     Pre => (AnIndex < Integer'Last and AnArray(AnIndex) < Integer'Last);
end Task4;
  • 在 ads 文件中的 Task4Procedure 定义中通过 with 关键字加入 pre 的检查,要保证两个方面:
    • anIndex 首先没有越界风险
    • 通过 anIndexAnArray 中索引出的值 AnArray(AnIndex) 没有 overflow

问题:Modify line 5 of task4.adb from:
AnArray(AnIndex) := AnArray(AnIndex) + 1;
to:
AnArray(AnIndex) := AnArray(0);
Run the SPARK examiner over note the error message. This error will also be generated by the GNAT compiler.

package body Task4 with
SPARK_Mode => On
is

   procedure Task4Procedure(AnArray : in out MyArray; AnIndex : in Index) is
   begin
      AnArray(AnIndex) := AnArray(0);
   end Task4Procedure;
end Task4;

  • ads 文件中可以看出:subtype Index is Integer range 1 .. 10;
  • 索引 index 的范围是 1..10 因此如果是 Anarray(0) 则一定会报错
  • 同时注意这里报错是:Constraint_Error

在 Ada 中,Constraint_Error 是一种预定义的异常,通常在尝试超越数据类型的允许范围时引发。例如,如果一个整数类型的变量被限制在1到10的范围内,然后你尝试将此变量赋值为11,那么就会触发 Constraint_Error 异常。
在处理数组时,如果你尝试访问超出数组索引范围的元素,也会引发 Constraint_Error。例如,对于一个大小为10的数组,尝试访问第11个元素会导致 Constraint_Error
此外,如果一个函数或过程的参数不满足指定的先决条件,也会引发 Constraint_Error。
处理 Constraint_Error 的常见方法是使用异常处理语句捕获并处理它。例如:

begin
   -- code that might raise a Constraint_Error
exception
   when Constraint_Error =>
      -- handle the error
end;
  • 这种处理可以使程序在遇到 Constraint_Error 时不会立即崩溃,而是可以执行错误处理代码,可能是记录错误、通知用户或尝试恢复。

题目:Modify line 5 of task4.adb from:
AnArray(AnIndex) := AnArray(AnIndex) + 1;
to:
AnArray(AnIndex) := AnArray(AnIndex + 1);
Run the SPARK examiner over this and see what has failed to prove. What do you think this failed proof relates to?

package body Task4 with
SPARK_Mode => On
is

   procedure Task4Procedure(AnArray : in out MyArray; AnIndex : in Index) is
   begin
      AnArray(AnIndex) := AnArray(AnIndex + 1);
   end Task4Procedure;
end Task4;

  • 这时候可能出问题的情况是:AnArray(AnIndex + 1) 中的 AnIndex + 1 可能超过 Index 的上界,因此在这里我们更改 ads 代码中的 pre 条件
package Task4 with
SPARK_Mode => On
is

   subtype Index is Integer range 1 .. 10;
   type MyArray is array(Index) of Integer;

   -- Returns the element of AnArray at the specified index, AnIndex.
   procedure Task4Procedure(AnArray : in out MyArray; AnIndex : in Index)
     with
       Pre => (AnIndex < Index'Last and AnArray(AnIndex) < Integer'Last);
end Task4;
  • 使得 AnIndex < Index'Last 这样就可以了

你可能感兴趣的:(软件工程学习内容,spark,ada)