文章目录
- 代码 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 toSpark → Examine File
from the GPS menu.
This reports that the variableOk
is used without being initialised, and the variablesI
andJ
are only initialised ifOk
is true. Note the difference between a variable beingnever
initialised (Ok), and a variablepossibly
being uninitialised (I
andJ
).
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
的判断,这其实已经是一个无效语句,因为它的行为是不确定的。 但如果我们想要明确添加一个无效语句,我们可以添加一些不会改变程序行为的代码。比如,我们可以在程序的末尾添加一个对变量I
或J
的赋值,如下:
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 thenrun
the SPARK examiner and GNATProve over the file. To run GNATProve, selectSpark → 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;
- 根据提供的代码片段,这段程序可能无法通过 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 了,因此,我们应该将代码改成下面的形式:
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 intask4.ads
.
-
在 SPARK 程序中,对于任何可能引发运行时错误的操作,都需要提供足够的前提条件来证明这种错误不会发生。 在
Task4Procedure
中,直接访问数组元素AnArray(AnIndex)
可能会导致越界错误,如果AnIndex
不在AnArray
的索引范围内。 -
同时,还要注意的是
AnArray(AnIndex) + 1
可能会导致整数溢出,如果AnArray(AnIndex)
已经是Integer'Last
。 -
所以,需要提供前提条件,以证明
AnIndex
在AnArray
的索引范围内,并且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
首先没有越界风险- 通过
anIndex
从AnArray
中索引出的值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
这样就可以了