Solutions to Homework Tasks
Task 1 (5 points): Forward reasoning with Viper
For each of the Viper programs below, replace TODO
by the strongest predicate such that the contract verifies; try to find a predicate that is as simple as possible.
Hint: Use the Viper verifier to check whether the program verifies for your your proposed predicates.
(a)
method a(x: Int, y: Int) returns (z: Int)
requires 0 <= x && x <= y && y < 100
ensures 0 <= x && x <= y && y < 100 && z == y - x
{
// START HERE
assert 0 <= x && x <= y && y < 100
z := y - x
// We do not need an existential since z is fresh
assert 0 <= x && x <= y && y < 100 && z == y - x
}
(b)
method b()
{
assert true
var x: Int
assert exists x0:Int :: true
assert true
assume 0 <= x && x < 100
assert 0 <= x && x < 100
x := 2 * x
assert exists x0:Int :: 0 <= x0 && x0 < 100 && x == 2*x0
assert 0 <= x && x < 199
}
(c)
method c() {
assert true
var x: Int
assert true
var y: Int
assert true
assume x > 0 && x < y
assert x > 0 && x < y
x := x + 23
assert x-23 > 0 && x-23 < y
y := y - 3 * x
//assert exists y0:Int :: x-23 > 0 && x-23 < y0 && y == y0 - 3 *x
assert x-23 > 0 && x-23 < y + 3*x
}
(d)
method d() {
assert true
var x: Int
assert true
var y: Bool
assert true
assume x > 0
assert x > 0
x := x + 1
//assert exists x0:Int :: x0 > 0 && x == x0+1
//assert exists x0:Int :: x0 > 0 && x-1 == x0
assert x-1 > 0
if (y) {
assert y && x-1 > 0
var z: Int
assert y && x-1 > 0
x := x + z
// x == x0 + z <==> x0 == x - z
assert y && (x-z)-1 > 0
assert y // we cannot say more after leaving scope
} else {
assert !y && x-1 > 0
x := 42
// x == 42
assert !y && 41 > 0
}
//assert (y && (x-z)-1 > 0) || (!y && 41 > 0)
assert (y && true) || (!y && 41 > 0)
}
Task 2 (5 points): Backward reasoning with Viper
For each of the Viper programs below, replace TODO
by the weakest predicate such that the contract verifies; try to find a predicate that is as simple as possible.
Hint: Use the Viper verifier to check whether the program verifies for your your proposed predicates.
(a)
method a(x: Int, y: Int) returns (X: Int, Y: Int)
requires true
ensures X == y && Y == x
{
assert true
assert y - (y-x) == x
X := y - x
assert y - X == x
assert y == y && y - X == x
assert y - X + X == y && y-X == x
Y := y - X
assert Y+X == y && Y == x
X := Y + X
assert X == y && Y == x
// START HERE
}
(b)
method b() {
var x: Int
var y: Int
assume 0 > (x + y) * (y-1) // TODO
assert x + y > (x + y) * y
x := x + y
assert x > x * y
y := x * y
assert x > y
}
(c)
method c() {
var x: Int
var y: Int
assume (y > 5 ==> x > 7) && (!(y > 5) ==> y - x > 7)
assert (y > 5 ==> x > 7) && (!(y > 5) ==> y - x > 7)
if (y > 5) {
assert x > 7
y := x - y
assert x > 7
} else {
assert y - x > 7
x := y - x
assert x > 7
}
assert x > 7
}
(d)
method d(x: Int) returns (y: Int)
requires !(x < 17) || (x < 17 && !(x > 3))
ensures y % 2 == 0
{
assert (x < 17 ==> (x > 3 ==> false) && (!(x > 3) ==> true))
&& (!(x < 17) ==> true)
if (x < 17) {
assert (x > 3 ==> false) && (!(x > 3) ==> true)
if (x > 3) {
assert false
assert 1 % 2 == 0
y := 1
assert y % 2 == 0
} else {
assert true
assert 2 % 2 == 0
y := 2
assert y % 2 == 0
}
assert y % 2 == 0
} else {
assert true
assert 6 % 2 == 0
y := 6
assert y % 2 == 0
}
assert y % 2 == 0
}
Task 3 (7 points): Encoding conditionals
The language PL0
is simplistic and does not support basic statements (see full definition), such as conditionals if (b) { S1 } else { S2 }
.
Such a statement evaluates the Boolean expression b
in the current program state; if the result is true
, we execute S1
; otherwise, we executes S2
.
In terms of our formal operational semantics, the semantics of conditionals is given by the following inference rules:
(a) Define the weakest precondition of conditionals, that is,
WP(if (b) { S1 } else { S2 }, Q)
.
Solution
WP(if (b) { S1 } else { S2 }, Q)
::=
(b ==> WP(S1, Q)) && (b ==> WP(S2, Q))
(b) Define the strongest postcondition of conditionals, that is, SP(P, if (b) { S1 } else { S2 })
.
SP(P, if (b) { S1 } else { S2 })
::=
SP(b && P, S1) || SP(!b && P, S2)
(c) Encode the conditional if (b) { S1 } else { S2 }
as a PL0
program.
That is, write a PL0
program ENC(if (b) { S1 } else { S2 })
that models the effect of a conditional.
Hint: You may write ENC(S1)
and ENC(S2)
do denote statements the S1
and S2
to which the encoding of conditionals has already been applied.
Solution
ENC(if (b) { S1 } else { S2 })
::=
{
assume b;
ENC(S1)
} [] {
assume !b;
ENC(S2)
}
(d) Prove that your encoding in (c) is correct in the sense that it yields verification conditions that are logically equivalent to the direct definitions of WP
and SP
from (a) and (b). That is, show that
WP(if (b) { S1 } else { S2 }, Q) <==> WP(ENC(if (b) { S1 } else { S2 }), Q)
andSP(P, if (b) { S1 } else { S2 }) <==> SP(P, ENC(if (b) { S1 } else { S2 }))
are valid, where<==>
denotes logical equivalence.
Solution
First, we compute WP(ENC(if (b) { S1 } else { S2 }), Q)
(read from bottom to top):
{ b ==> WP(S1, Q) && !b ==> WP(S2, Q) }
{
{ b ==> WP(S1, Q) }
assume b;
{ WP(S1, Q) } // by I.H.
ENC(S1)
{ Q }
} [] {
{ !b ==> WP(S2, Q) }
assume !b;
{ WP(S2, Q) } // by I.H.
ENC(S2)
{ Q }
}
{ Q }
Clearly, the result at the top is identical to WP(if (b) { S1 } else { S2 }, Q)
as defined in (a).
Second, we compute SP(P, ENC(if (b) { S1 } else { S2 }))
(read from top to bottom):
{ P }
{
{ P }
assume b;
{ b && P }
ENC(S1)
{ SP(b && P, S1) } // by I.H.
} [] {
{ P }
assume !b;
{ !b && P }
ENC(S2)
{ SP(b && P, S2) } // by I.H.
}
{ SP(b && P, S1) || SP(!b && P, S2) }
Clearly, the result at the top is identical to SP(P, if (b) { S1 } else { S2 })
as defined in (b).
Task 4 (8 points): Forward reasoning about total correctness
In the lecture, we noticed a difference between weakest preconditions and strongest postconditions: for strongest, the computed verification condition is valid even if the program encounters a runtime error. This corresponds to the notion of partial correctness. By contrast, the verification conditions obtained from computing weakest preconditions consider total correctness, that is, they enforce safety (i.e., the absence of runtime errors) and termination.
To enable forward reasoning about total correctness, we need to compute a second verification condition that enforces safety.
(a)
Give a recursive definition of a predicate transformer
SAFE: Pred --> PL0 --> Pred
such that the predicate SAFE(P, S)
is the verification condition that needs to be checked to enforce that starting S
on any state in precondition P
does not lead to a runtime error.
As for strongest postconditions, SAFE(P, S)
should reason about statements in forward direction.
Hint: It might help to first express the same safety property in terms of weakest preconditions.
Solution
Intuitively, SAFE(P, S)
should be valid for a precondition P
and a program S
if and only if S
does not crash when running it on any initial state that satisfies P
.
We go through each PL0
statement S
and ask "on which states could an execution of S
cause a runtime error?". SAFE(P, S)
should then characterize all other states, that is, those that never lead to a crash.
First, we observe (for example by looking at the operational semantics) that variable declarations, assumptions, and assignments never lead to a runtime error. In fact, the only non-composed statement that could cause such an error is a failing assertion.
Hence, we set
SAFE(P, var x) = SAFE(P, x := a) = SAFE(P, assume R) ::= true.
Now, an assertion assert R
crashes the program if R
does not hold in the current program state. Hence, R
must hold for any initial state on which we may run our program, that is, for all states that satisfy precondition P
.
In other words, if a state satisfies P
, then it must also satisfy R
.
Formally, we define SAFE(P, assert R) ::= P ==> R
.
Next, we consider the composed statements S1 [] S2
and S1; S2
.
The nondeterministic choice is straightforward: S1 [] S2
does not crash if neither S1
nor S2
crashes.
Put positively, S1 [] S2
is safe if running S1
and running S2
is safe.
Hence, we define SAFE(P, S1 [] S2) ::= SAFE(P, S1) && SAFE(P, S2)
.
The sequential composition S1; S2
is a little tricky, since there are two cases that could lead to a crash:
- There is a crash if we run
S1
on a state satisfyingP
; we can prevent that by requiringSAFE(P, S1)
. - There is no crash if we run
S1
on a state satisfyingP
but there is a crash if we runS2
afterward. In other words, there is a crash if we runS2
on some state reached after executingS1
on a state satisfyingP
, that is, some state described bySP(P, S1)
. Hence, to prevent such crashes, we requireSAFE(SP(P, S1), S2)
.
Since we need to rule out both of the above cases, we define
SAFE(P, S1;S2) ::= SAFE(P, S1) && SAFE(P, S2)
In summary, the predicate transformer SAFE
is given by the following table.
S SAFE(P, S)
==================================================================
var x true
assert R P ==> R
assume R true
x := a true
S1; S2 SAFE(P, S1) && SAFE(SP(P, S1), S2)
S1 [] S2 SAFE(P, S1) && SAFE(P, S2)
(b)
Using your definition in (a), prove that, for all PL0
statements S
and all predicates P
, Q
, we have
P ==> WP(S, Q) valid
if and only if
SAFE(P, S) valid and SP(P, S) ==> Q valid
Hint: Proceed by structural induction on the syntax of statements S
.
Solution
For simplicity, we denote states by s
instead of .
Moreover, we write |= R
to denote that predicate R
is valid, that is,
|= R
iff s |= R
holds for all states s
.
We then show by structural induction on the syntax of PL0
statements S
that, for all predicates P
and Q
, we have
(I.H.) |= P ==> WP(S, Q) if and only if |= SAFE(P, S) and |= SP(P, S) ==> Q
The case var x
|= P ==> WP(var x, Q)
iff (Def. of WP)
|= P ==> forall x :: Q
iff (<>, see below)
|= (exists x :: P) ==> Q
iff (Def. of SP)
|= SP(P, var x) ==> Q
iff (Def. of SAFE)
|= SP(P, var x) ==> Q and |= SAFE(P, var x)
For the step marked with <>
, we consider both directions separately:
- Assume
|= P ==> forall x :: Q
. Moreover, for an arbitrary, but fixed states
, lets |= (exists x :: P)
; otherwise there is nothing to show. By assumption, it follows thats |= forall x :: Q
. In particular, this meanss |= Q
. Since we have considered an arbitrary state, we have|= (exists x :: P) ==> Q
. - Assume
|= (exists x :: P) ==> Q
. Moreover, for an arbitrary, but fixed states
, lets |= P
; otherwise there is nothing to show. Then,s[x := v] |= (exists x :: P)
for every integerv
(we can chooses(x)
as value ofx
). By assumption, this meanss[x := v] |= Q
. By the substitution lemma, we haves |= Q[x/v]
for every integerv
. Hence, s |= forall x :: Q. Since we have considered an arbitrary state, we have|= P ==> forall x :: Q
.
The case assume R
|= P ==> WP(assume R, Q)
iff (Def. of WP)
|= P ==> R ==> Q
iff
|= (P && R) ==> Q
iff (Def. of SP)
|= SP(P, assume R) ==> Q
iff (Def. of SAFE)
|= SP(P, assume R) ==> Q and |= SAFE(P, assume R)
The case assert R
|= P ==> WP(assert R, Q)
iff (Def. of WP)
|= P ==> (R && Q)
iff
|= P ==> R and |= (P && R) ==> Q
iff (Def. of SAFE and SP)
|= SAFE(p, assert R) and |= SP(P, assert R) ==> Q
The case x := a
|= P ==> WP(x := a, Q)
iff (Def. of WP)
|= P ==> Q[x/a]
iff (<>, see below)
|= (exists x0 :: P[x/x0] && x == a[x/x0]) ==> Q
iff (Def. of SP)
|= SP(P, x := a) ==> Q
iff (Def. of SAFE)
|= SP(P, x := a) ==> Q
and |= SAFE(P, x := a)
For the step marked with <>
, we consider both directions separately:
- Assume
|= P ==> Q[x/a]
. Moreover, for an arbitrary, but fixed states
, lets |= (exists x0 :: P[x/x0] && x == a[x/x0])
; otherwise there is nothing to show. By assumption and sincex0
is not a free variable ofP,Q,a
, we have|= P[x/x0] ==> Q[x/a][x/0]
. Consequently,
s |= exists x0 :: Q[x/a][x/x0] && x == a[x/x0]
implies s |= exists x0 :: Q[x/a[x/x0]] && x == a[x/x0]
implies s |= exists x0 :: Q
implies s |= Q
where the last implication follows since x0
is not a free variable of Q
.
- Assume
|= (exists x0 :: P[x/x0] && x == a[x/x0]) ==> Q
. Moreover, for an arbitrary, but fixed states
, lets |= P
; otherwise there is nothing to show. Thens[x0 := s(x)] |= P[x/x0] && x == a[x/x0]
. By assumption, this also meanss[x0 := s(x)] |= Q && x == a[x/x0]
. Hence, by the second conjunct,s[x0 := s(x)][x := s(a[x/x0])] |= Q
. Sincex0
is not a free variable ofQ
, this simplifies tos[x := s(a)] |= Q
. By the substitution lemma, this meanss |= Q[x/a]
. Since we have considered an arbitrary state, we have|= P ==> Q[x/a]
.
The case S1 [] S2
|= P ==> WP(S1 [] S2, Q)
iff (Def. of WP)
|= P ==> WP(S1, Q) && WP(S2, Q)
iff
|= (P ==> WP(S1, Q)) && (P ==> WP(S2, Q))
iff (I.H. twice)
|= (SP(P,S1) ==> Q
and |= SAFE(P, S1)
and |= (SP(P,S2) ==> Q
and |= SAFE(P, S2)
iff (Def. of SAFE)
|= (SP(P,S1) ==> Q
and |= (SP(P,S2) ==> Q
and |= SAFE(P, S1 [] S2)
iff
|= ((SP(P,S1) || SP(P,S2)) ==> Q
and |= SAFE(P, S1 [] S2)
iff (Def. of SP)
|= SP(P,S1 [] S2) ==> Q
and |= SAFE(P, S1 [] S2)
The case S1;S2
|= P ==> WP(S1;S2, Q)
iff (Def. of WP)
|= P ==> WP(S1, WP(S2, Q))
iff (I.H.)
|= SP(P, S1) ==> WP(S2, Q)
and |= SAFE(P, S1)
iff (I.H.)
|= SP(SP(P, S1), S2) ==> Q
and |= SAFE(SP(P, S1), S2)
and |= SAFE(P, S1)
iff (Def. of SP and SAFE)
|= SP(P, S1;S2) ==> Q
and |= SAFE(P, S1;S2)
Hence, the claim holds by the principle of structural induction.