CITS5501 Software Testing and Quality Assurance Semester 1, 2022 Workshop 9 (week 10) – Formal methods – solutions
It is strongly suggested you complete the recommended readings for weeks 1-9 before attempting this lab/workshop.
Discuss the following scenarios. Would you use formal methods for any of the following systems? If so, which systems, and why? Do you think the enterprise developing the system would share your view?
The next version of Confectionery Crush Story, a web– and mobile-app–based puzzle video game your company develops. The game is available for both Android and iOS mobile devices, and the previous version grossed over $US 1 billion in revenue.
Exemplarex, software produced for Windows and MacOS operating systems and licensed to educational institutions. The software semi-automatically invigilates exams set by the institutions: machine-learning techniques are used to analyse audio and video of exam candidates to identify possible academic misconduct.
The online banking website provided by a major Australian bank, EastPac. Over 5 million customers use the website to perform banking transactions on personal or business bank accounts.
A radiation therapy system used to treat cancer patients. The system has two principal components:
The aim of the exercise is just to get you to think about what factors might make formal methods appropriate.
However, some possible answers are:
Dafny is a “verification-aware” programming language. The compiler for the language incorporates a program verifier – as you write a program, the verifier checks to see whether what you have written can be proved correct, and flags any errors.
You can also compile (as well as run) Dafny programs using Gitpod, which provides you with an online development environment based on Microsoft VS Code.
Visit https://gitpod.io/#https://github.com/arranstewart-dev/dafny-gitpod in your browser. Gitpod will download a Docker image containing the Dafny compiler, and create an online IDE environment (this may take a couple of minutes).
Once it appears, open the “sample.dfy” file in the editor, and an “editor extension” will be installed which tells the editor how to highlight Dafny code and show errors.
(You can ignore any error messages saying the “dafny-lang.ide-cscode extension is not synced” – those are a limitation of the VS Code setup, and can’t easily be got rid of.)
Toward the bottom of the screen is also a “Terminal” tab, which gives
you command-line access to the virtual machine in which Microsoft VS
Code is running. Typing dafny /help
in the terminal will
display the compiler online help (warning: it’s very long).
In case you are interested, the paper introducing Dafny is available here.
Documentation for the language (including detailed tutorials and reference material) is available at:
Take a look at the sample source file open in VS Code:
Main() {
method "hello, Dafny\n";
print 10 < 2;
assert }
You should see that the Dafny compiler reports an assertion violation in line 3; the assertion “\(10 < 2\)” is not true.
Now comment out the assertion (using double forward-slashes –
“//
” – the same as Java.) The code should now compile.
In the “Terminal” tab, you should be able to invoke the
dafny
command, which lets you compile code to different
targets (such as Java, C# and Go) and run the program.
$ dafny /version
should print the Dafny version and verify the command works.
$ dafny sample.dfy
will verify and compile the sample.dfy
– it is actually
short for the command:
$ dafny /compile:1 hello.dfy
Either command should produce output like the following:
Dafny program verifier finished with 0 verified, 0 errors
Compiled assembly into sample.dll
By default, Dafny compiles programs into libraries for use on Windows
(.dll
files). If you list the directory contents, you will
see sample.dll
, and a file
sample.runtimeconfig.json
which the compiler creates.
The following command verifies, compiles, and runs you program:
$ dafny /compile:3 sample.dfy
Main
methodChange the name of the Main
method to
Random
(or any other name you wish).
Try compiling, verifying and running the program again:
$ dafny /compile:3 sample.dfy
Although the program verifies and compiles properly, it will not
execute and produce any output, because the Dafny runtime looks for a
Main
method to execute. If it does not find one, no warning
or error is produced (you might have intended to create a library,
rather than a program), but nothing will be executed.
Try compiling and running the following code:
method Main() {
PrintInt(-1);
}
method PrintInt(x : int)
{
print "the int is: \n";
print x;
}
The int -1 should be printed with a message. Now add a precondition
to PrintInt, “requires x >= 0
”:
method Main() {
PrintInt(-1);
}
method PrintInt(x : int)
requires x >= 0
{
print "the int is: \n";
print x;
}
If you try compiling again, you will see that Dafny won’t permit you
to call the PrintInt
method, unless it can verify all the
preconditions hold. Change the int
being passed from -1 to
1, and you should see the code now compiles.
Create a new file square.dfy
consisting of the following
code.
SquareMe(n:int)
method {
*n > 0;
assert n}
and try to verify the program using:
$ dafny square.dfy
It should generate the messages:
square.dfy(3,13): Error: assertion might not hold
Dafny program verifier finished with 0 verified, 1 error
Even though the assertion is true for almost all values of
n
, it fails for n == 0
, and just one failure
is enough to make the assertion false. Dafny requires that assertions be
true for all possible inputs.
An assertion which is true, however, is the following – try inserting it instead:
!=0 ==> n*n>0 assert n
This reads “n*n
is greater than 0,
if n
does not equal 0”. This
assertion is true, so Dafny should allow the program to be verified and
compiled.
The following Dafny code is intended to find the position of the largest element of an array. It is only guaranteed to produce a result if the array is non-empty, however. We won’t compile this (yet), but look at the following code:
method FindMax(arr: array<int>) returns (r: int)
{
var max_val : int := arr[0];
var max_idx : int := 0;
var i : int := 1;
while (i < arr.Length)
{
if arr[i] > max_val
{
max_idx := i;
max_val := arr[i];
}
i := i + 1;
}
return max_idx;
}
At what points in the code might we insert the following, and what Dafny keywords would be used?
See if you can state what the preconditions and postconditions are (in English is fine).
Solution:
Correct locations would be:
method FindMax(arr: array<int>) returns (r: int)
requires /* **preconditions go here** */
ensures /* **postconditions go here** */
{
var max_val : int := arr[0];
var max_idx : int := 0;
var i : int := 1;
/* **assertions could go anywhere in the body** */
while (i < arr.Length)
invariant /* **loop invariants go here** */
{
if arr[i] > max_val
{
max_idx := i;
max_val := arr[i];
}
i := i + 1;
}
return max_idx;
}
Preconditions (in English):
Postcondition (in English):
r
is greater than or equal to every
element of the array.Challenge exercise: If you have some experience with
formal logic and would like a challenge, try verifying the above code
using the online Dafny verifier. You will probably want to work through
the online Dafny tutorial first. This will explain how to use the
forall
keyword, which we have not covered, but which is
needed for the challenge.
Sample solution:
Fully verified code:
method FindMax(arr: array<int>) returns (r: int)
requires arr.Length > 0
ensures (0 <= r < arr.Length) &&
(forall k :: 0 <= k < arr.Length ==> arr[r] >= arr[k])
{
var max_val : int := arr[0];
var max_idx : int := 0;
var i : int := 1;
assert i >= 1;
assert max_idx <= i;
while (i < arr.Length)
// probably could get away with fewer loop invariants
invariant 1 <= i <= arr.Length
invariant 0 <= max_idx <= arr.Length
invariant 0 <= max_idx <= i
invariant forall k :: 0 <= k <= i-1 ==> max_val >= arr[k]
invariant (0 <= max_idx < arr.Length)
&& (max_val == arr[max_idx])
&& (forall k :: 0 <= k <= i-1 ==> arr[max_idx] >= arr[k])
{
if arr[i] > max_val
{
max_idx := i;
max_val := arr[i];
}
i := i + 1;
}
return max_idx;
}
The following code does not yet compile:
method Max(a: int, b:int) returns (c: int)
ensures c >= a
{
return 0;
}
Write the body of the method Max. It should take two integer parameters and return their maximum. Add appropriate annotations (one is already provided) and make sure your code verifies. Refer to the documentation and tutorials, if necessary.
Solution:
method Max(a: int, b:int) returns (c: int)
ensures c >= a
ensures c >= b
{
if (b > a) {
return b;
} else {
return a;
}
}
A Moodle submission sandbox is available for the CITS5501 project on the CSSE Moodle server. If you haven’t yet attempted any of the project questions, now would be a good opportunity to do so.