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) and
  • SP(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:

  1. There is a crash if we run S1 on a state satisfying P; we can prevent that by requiring SAFE(P, S1).
  2. There is no crash if we run S1 on a state satisfying P but there is a crash if we run S2 afterward. In other words, there is a crash if we run S2 on some state reached after executing S1 on a state satisfying P, that is, some state described by SP(P, S1). Hence, to prevent such crashes, we require SAFE(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:

  1. Assume |= P ==> forall x :: Q. Moreover, for an arbitrary, but fixed state s, let s |= (exists x :: P); otherwise there is nothing to show. By assumption, it follows that s |= forall x :: Q. In particular, this means s |= Q. Since we have considered an arbitrary state, we have |= (exists x :: P) ==> Q.
  2. Assume |= (exists x :: P) ==> Q. Moreover, for an arbitrary, but fixed state s, let s |= P; otherwise there is nothing to show. Then, s[x := v] |= (exists x :: P) for every integer v (we can choose s(x) as value of x). By assumption, this means s[x := v] |= Q. By the substitution lemma, we have s |= Q[x/v] for every integer v. 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:

  1. Assume |= P ==> Q[x/a]. Moreover, for an arbitrary, but fixed state s, let s |= (exists x0 :: P[x/x0] && x == a[x/x0]); otherwise there is nothing to show. By assumption and since x0 is not a free variable of P,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.

  1. Assume |= (exists x0 :: P[x/x0] && x == a[x/x0]) ==> Q. Moreover, for an arbitrary, but fixed state s, let s |= P; otherwise there is nothing to show. Then s[x0 := s(x)] |= P[x/x0] && x == a[x/x0]. By assumption, this also means s[x0 := s(x)] |= Q && x == a[x/x0]. Hence, by the second conjunct, s[x0 := s(x)][x := s(a[x/x0])] |= Q. Since x0 is not a free variable of Q, this simplifies to s[x := s(a)] |= Q. By the substitution lemma, this means s |= 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.