Tracing Facts in Conditionals

Assignments and Compositions

In the last lecture we have studied assignments and compositions of assignments. The programs were of the simple shape.

1
2
3
4
5
val k: Z = randomInt()
var x: Z = 5
x = x + k
val y: Z = x - 1
x = x - y

In this lecture we explore conditionals

1
2
3
4
5
if (B) {
  S
} else {
  T
}

Execution of Conditionals

Of course, we understand the meaning of a conditional

1
2
3
4
5
if (B) {
  S
} else {
  T
}

Usually, we express this in terms of how it is executed:

  • If the condition B is true, then S is executed
  • If the condition B is false, then T is executed

Understanding the execution of a conditional precisely, we can also reason about it. We want to reason about the effect of the conditional without executing it.

Reasoning about Conditionals

We know that S or T is executed depending on the value of B

1
2
3
4
5
if (B) {
  S
} else {
  T
}

This means,

  • When first branch is entered, B becomes initially a fact in that branch
  • When second branch is entered, !B becomes initially a fact in that branch
1
2
3
4
5
6
if (B) {
  // deduce B
  S
} else {
  T // deduce !B
}

Example: Computing the Maximum of two Integers

Example A: Computing the Maximum

As an example of a program with a conditional, we use the program for computing the maximum of two integers below.

We focus on the highlighted fragment on the following slides assuming variables x and y and ignoring the final assertions for now.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// #Sireum #Logika
import org.sireum._
val x: z = randomInt()
val y: z = random Int()

var z: Z = 0
if (x < y) {
    z = y
} else {
    z = x
}
assert(z == x | z == y)
assert(y <= z & x <= z)

The assignment var z: Z = 0 yields the fact z == 0

1
2
3
4
5
6
7
var z: Z = 0
//deduce z ==0
if (x < y) {
  z = y
} else {
  z = x
}

Whichever branch of the conditional we enter, initially z == 0 must still hold because the boolean expression x < y does not modify z aside. This is why it’s a bad idea to use conditions with side effects: It complicates the reasoning about a program and impedes comprehensibility.

Tracing z == 0 into both branches, we get

1
2
3
4
5
6
7
8
9
var z: Z = 0
// deduce z == 0
if (x < y) {
  // deduce z == 0
  z = y
} else {
  // deduce z == 0
  z = x
}

Next, we trace the new facts we can deduce from the condition x < y. In the first branch we can deduce x < y, which implies x <= y. In the second branch we can deduce !(x < y), which implies y <= x. Now, we have

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var z: Z = 0
// deduce z == 0
if (x < y) {
  // deduce z == 0
  // deduce x < y
  // deduce x <= y
  z = y
} else {
  // deduce z == 0
  // deduce !(x < y)
  // deduce y <= x
  z = x
}

For the further reasoning we only need the last deductions, so we drop the earlier ones.

We already know how do deal with assignments.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var z: Z = 0
if (x < y) {
  // deduce x <= y
  z = y
  // deduce x <= y
  // deduce z == y
  // deduce x <= z
} else {
  // deduce y <= x
  z = x
  // deduce y <= x
  // deduce z == x
  // deduce y <= z
}

Let’s summarise what we know at this stage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var z: Z = 0
// deduce z == 0       // consequence of assignment
if (x < y) {
  // deduce z == 0     // old fact
  // deduce x < y      // new fact from condition
  // deduce x <= y     // proof by algebra
  z = y
  // deduce x <= y     // old fact
  // deduce z == y     // consequence of assignment
  // deduce x <= z     // proof by algebra
} else {
  // deduce z == 0     // old fact
  // deduce !(x < y)   // new fact from negated condition
  // deduce y <= x     // proof by algebra
  z = x
  // deduce y <= x     // old fact
  // deduce z == x     // consequence of assignment
  // deduce y <= z     // proof by algebra
}
assert(...)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var z: Z = 0
// deduce z == 0
if (x < y) {
  // deduce z == 0
  // deduce x < y
  // deduce x <= y
  z = y
  // deduce x <= y
  // deduce z == y
  // deduce x <= z
} else {
  // deduce z == 0
  // deduce !(x < y)
  // deduce y <= x
  z = x
  // deduce y <= x
  // deduce z == x
  // deduce y <= z
}
assert(...)

To continue, we lack information about what is true after the execution of the conditional. In order to show that an assertions following a conditional is true we need a method to deduce facts from both branches

Let’s have a look at the first assertion z == x | z == y

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var z: Z = 0
// deduce z == 0
if (x < y) {
  // deduce z == 0
  // deduce x < y
  // deduce x <= y
  z = y
  // deduce x <= y
  // deduce z == y
  // deduce x <= z
} else {
  // deduce z == 0
  // deduce !(x < y)
  // deduce y <= x
  z = x
  // deduce y <= x
  // deduce z == x
  // deduce y <= z
}
assert(z == x | z == y)

The fact z == y is true in the first branch and the fact z == x in the second. So, we know one or the other must be true after the conditional, and the assertion is true.

Concerning the second assertion y <= z & x <= z we have the following

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var z: Z = 0
// deduce z == 0
if (x < y) {
  // deduce z == 0
  // deduce x < y
  // deduce x <= y
  z = y
  // deduce x <= y
  // deduce z == y
  // deduce x <= z
} else {
  // deduce z == 0
  // deduce !(x < y)
  // deduce y <= x
  z = x
  // deduce y <= x
  // deduce z == x
  // deduce y <= z
}
assert(z == x | z == y)

The fact x <= z is true in the first branch and the fact y <= z in the second. This is not (yet) enough to show that the assertion is true.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var z: Z = 0
// deduce z == 0
if (x < y) {
  // deduce z == 0
  // deduce x < y
  // deduce x <= y
  z = y
  // deduce x <= y
  // deduce z == y
  // deduce x <= z
} else {
  // deduce z == 0
  // deduce !(x < y)
  // deduce y <= x
  z = x
  // deduce y <= x
  // deduce z == x
  // deduce y <= z
}
assert(z == x | z == y)

We need to know that both are true (independent of the considered branch) Remember, that z == y is true in the first branch and z == x in the second

We can deduce y <= z & x <= z in each branch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var z: Z = 0
// deduce z == 0
if (x < y) {
  // deduce z == 0
  // deduce x < y
  // deduce x <= y
  z = y
  // deduce x <= y
  // deduce z == y
  // deduce x <= z
} else {
  // deduce z == 0
  // deduce !(x < y)
  // deduce y <= x
  z = x
  // deduce y <= x
  // deduce z == x
  // deduce y <= z
}
assert(z == x | z == y)

Because z == y implies y <= z and z == x implies x <= z. Thus, y <= z & x <= z is true in both branches, and the assertion is true.

Reason about Conditionals (Completed)

We can complete our method for reasoning about conditionals now:

  • When first branch is entered, B becomes initially a fact in that branch
  • When second branch is entered, !B becomes initially a fact in that branch
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if (B) {
    // deduce B
    S
    // ... deduce C
} else {
    // deduce !B
    T
    // ... deduce D
}
// deduce C | D
  • If we can deduce some fact C at the end of the first and a fact D at the end of the second branch, then we deduce C | D after the conditional.

Reason about Conditionals (Completed Special Case)

We can complete our method for reasoning about conditionals now:

  • When first branch is entered, B becomes initially a fact in that branch
  • When second branch is entered, !B becomes initially a fact in that branch
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if (B) {
    // deduce B
    S
    // ... deduce C
} else {
    // deduce !B
    T
    // ... deduce D
}
// deduce C
  • If we can deduce some fact C at the end of the first and the second branch, then we deduce C after the conditional (because C | C is identical to C)

Exercise

Implement and verify a program computing the absolute value of an integer using Slang deductions according to the contract below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// #Sireum #Logika
import org.sireum._
val x: Z = randomInt()
val y: Z = randomtInt()
__________________
__________________
__________________
__________________
__________________
__________________
__________________
__________________
assert(x/y == 1 | x/y == -1)
assert(y >= 0)

Example B: Biased Difference

Let’s consider a small variation of the maximum program

1
2
3
4
5
6
7
8
var z: Z = 0
if (x < y) {
  z = y
} else {
  z = x
}
z = z - x
assert(z == 0 | z = y - x)

It has an additional assignment at the end. We can reuse the deductions from the maximum problem, only dealing with the added assignment. We already know the deductions we can make at the end of the conditional.

1
2
3
4
5
6
7
8
9
var z: Z = 0
if (x < y) {
  z = y
} else {
  z = x
}
// deduce z == x | z == y
z = z - x
assert(z == 0 | z = y - x)

Of course, the assignment z = z - x changes this fact.

We can deduce At(z, 1) == z + x and replace At(z, 1) in the disjunction.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var z: Z = 0
if (x < y) {
  z = y
} else {
  z = x
}
// deduce z == x | z == y
z = z - x
// deduce At(z, 1) == x | At(z, 1) == y
// deduce z == At(z, 1) - x
assert(z == 0 | z == y - x)

This gives the new fact z + x == x | z + x == y.

We already know the deductions we can make at the end of the conditional.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var z: Z = 0
if (x < y) {
  z = y
} else {
  z = x
}
// deduce z == x | z == y
z = z - x
// deduce At(z, 1) == x | At(z, 1) == y
// deduce z == At(z, 1) - x
// deduce z + x == x | z + x == y
assert(z == 0 | z == y - x)

The assertion now holds by simple algebra.

Example B: Biased Difference Program in Logika

Biased_Diff_1 The deductions and final assertions are verified by Logika.

Programs are Facts

Programs with conditionals correspond to facts of the shape (B -> Sfact) & (!B -> Tfact)

1
2
3
4
5
if (B) {
    S
} else {
    T
}

A little complication will appear later with respect to replacing “old variables” in Sfact and Tfact. Aside. We can also write the conjunction (B -> Sfact) & (!B -> Tfact) as the equivalent disjunction (B & Sfact) | (!B & Tfact). In Logika the first form is used. It permits writing larger formulas in tabular e.g., when dealing with nested conditionals.

Verifying the Maximum Program in Logika

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
\begin{lstlisting}[escapechar=~]
// #Sireum #Logika
import org.sireum._
val x: Z = randomInt()
val y: Z = randomInt()

var z: Z = 0
if (x < y) {
  Deduce(|- (x < y))
  Deduce(|- (x <= y))
  z = y
  Deduce(|- (x <= z))
  Deduce(|- (z == y))
} else {
  Deduce(|- (!(x < y)))
  Deduce(|- (y <= x))
  z = x
  Deduce(|- (y <= z))
  Deduce(|- (z == x))
}
assert(z == x | z == y)
assert(y <= z & x <= z)

We have added some Deduce commands to the program to document why it works. If all Deduce commands are removed, Logika verifies the program fully automatic. In the future, we will only state facts relevant for the understanding, leaving to Logika some of the work and relying on the reader to fill in the gaps.

The Maximum Program in Logika

Maximum_1 Facts known after the first assignment to z. Maximum_2 Facts known when entering the first branch. Maximum_3 Facts known after the assignment to z in the first branch. Maximum_4 Facts known when entering the second branch. Maximum_5 Facts known after the assignment to z in the second branch. Maximum_6 Facts known after the conditional.

Nested Conditionals

The Maximum of Three Integers

The following program computes the maximum of three integers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
if (x < y) {
  if (y < z) {
    m = z
  } else {
    m = y
  }
} else {
  if (x < z) {
    m = z
  } else {
    m = x
  }
}
assert(m == x | m == y | m == z)
assert(x <= m & y <= m & z <= m)

Exercise A: Add the facts that are initially true in each branch. Exercise B: Implement a program that chooses the maximum of four numbers.

The Program as a Fact

Programs with conditionals correspond to facts of the shape (B -> Sfact) & (!B -> Tfact).

1
2
3
4
5
if (B) {
    S
} else {
    T
}

We have the following law relating conjunction and implication: A -> (B -> C) is identical to (A & B) -> C. Thus, we have two equivalent ways to stack conditions as we move through nested conditionals. Let’s look directly at the way this is represented in Logika.

The Program in Logika

Nested-1 Facts following nested conditionals. The conditions are stacked horizontally reaching the inner blocks and vertically enumerating all blocks of the conditional.

The Maximum of Three Integers

Facts corresponding to the program with nested conditionals.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
if (x < y) {
  if (y < z) {
    m = z        // deduce   x < y  &   y < z  -> m == z
  } else {
    m = y        // deduce   x < y  & !(y < z) -> m == y
  }
} else {
  if (x < z) {
    m = z        // deduce !(x < y) &   x < z  -> m == z
  } else {
    m = x        // deduce !(x < y) & !(x < z) -> m == x
  }
}
// The facts are as seen from here

Each deduction is an implication C-> Bfact where C contains the conditions leading to block B and Bfact is the fact corresponding to that block.

The conjunction of these deductions is the fact corresponding to the program. Thus, we can easily relate the components of a Slang conditional P to the components of the corresponding fact Pfact

Example: Swapping two Integers

Blocks with Several Assignments

We have considered nested conditionals and seen that there is a close correspondence between programs and the deduced facts at all program locations. We look at a program with larger blocks next. Once we’ve observed these two variations it is not difficult to analyse more complex programs containing conditionals.

Example: Swapping two Integers

The following program chooses to use a local variable if x >= y

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// #Sireum #Logika
import org.sireum._

val m: Z = randomInt()
val n: Z = randomInt()
var x: Z = m
var y: Z = n

if (x < y) {
  x = x + y
  y = x - y
  x = x - y
} else {
  val t = x
  x = y
  y = t
}
assert(x == n & y == m)

The Example in Logika

Swap_choice_1 Note that the local variable t is only used in the “past form”" At(t, 0). This variable is not available in the “present form” after the block. Swap_Block_1 This is a property of local variables in block.

Exercise

Is the following program correct? Why? Add Slang deductions to document your argument

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// #Sireum #Logika
import org.sireum._

var a: B = randomInt() > 0
var b: B = randomInt() > 0

if (!a || b) {
  a = !a
} else {
  a = !b
}
assert(a | b)

Symbolic Execution

Facts as Values in Conditionals

Recall the maximum program:

1
2
3
4
5
6
7
var z: Z = 0
if (x < y) {
  z = y
} else {
  z = x
}
assert(z == x | z == y)

Symbolic execution of conditionals collects the conditions and their negations in different paths described by the corresponding path conditions. This corresponds closely to the way conditions are stacked when considering conditionals as facts.

Using Facts as Values

1
2
3
4
5
6
7
var z: Z = 0
if (x < y) {
  z = y
} else {
  z = x
}
assert(z == x | z == y)
  • Executing var z: Z = 0 yields (x: X, y: Y, z: 0), (PC: true)
  • Executing if (x < y) yields (x: X, x: X, z = 0), (PC: X < Y)
  • Executing z = y yields (x: X, y: Y, z: Y), (PC: X < Y)
  • Executing assert(...) yields (x: X, y: Y, z: Y), (PC: X < Y, Y == X | Y == Y)
  • Executing else yields (x: X, x: X, z: 0), (PC: !(X < Y))
  • Executing z = x yields (x: X, y: Y, z: X), (PC: !(X < Y))
  • Executing assert(...) yields (x: X, x: X, z: X), (PC: !(X < Y), X == X | X == Y)

Execution Tree

The two possible outcomes of a condition like x < y give rise to a tree structure of a symbolic execution.

flowchart TB
    A[(.., z: 0), (PC: true)]
    A --> B[(...,z: 0), (PC: X < Y)]
    A -- C[(...,z: 0), (PC:!( X < Y))]
    B -- D[(...,z: Y), (PC: X < Y)]
    C -- E[(...,z: X), (PC:!( X < Y))]
. . , , z z : : 0 Y ) ) , , ( ( P P C C : : X X . , < < z Y Y : ) ) 0 ) , ( P C : t r u . . e , , ) z z : : 0 X ) ) , , ( ( P P C C : : ! ! ( ( X X < < Y Y ) ) ) )

(Some information has been omitted from the tree to keep it readable)

Slang Examples

Absvalue nn

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// #Sireum #Logika
import org.sireum._
val x: Z = randomInt()
var y: Z = randomInt()

assume(x != 0)
if (x < 0) {
  y = -x
} else {
  y = x
}

assert(x/y == 1 | x/y == -1)
assert(y >= 0)

Boolor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// #Sireum #Logika
import org.sireum._

var a: B = randomInt() > 0
var b: B = randomInt() > 0

if (!a || b) {
  a = !a
} else {
  a = !b
}
assert(a | b)

Maximum Diff

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// #Sireum #Logika
import org.sireum._
val x: Z = randomInt()
val y: Z = randomInt()

var z: Z = 0
if (x < y) {
  z = y
} else {
  z = x
}
z = z - x
assert(z == 0 | z == y - x)

Maximum Diff Ded

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// #Sireum #Logika
import org.sireum._
val x: Z = randomInt()
val y: Z = randomInt()

var z: Z = 0
if (x < y) {
  z = y
} else {
  z = x
}
Deduce(|- (z == x | z == y))
z = z - x
Deduce(|- (At(z, 1) == x | At(z, 1) == y))
Deduce(|- (z == At(z, 1) - x))
Deduce(|- (z + x == x | z + x == y))
assert(z == 0 | z == y - x)

Maximum Simple

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// #Sireum #Logika
import org.sireum._
val x: Z = randomInt()
val y: Z = randomInt()

var z: Z = 0
if (x < y) {
  z = y
} else {
  z = x
}
assert(z == x | z == y)
assert(y <= z & x <= z)

Maximum Simple Ded

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// #Sireum #Logika
import org.sireum._
val x: Z = randomInt()
val y: Z = randomInt()

var z: Z = 0
if (x < y) {
  Deduce(|- (x < y))
  Deduce(|- (x <= y))
  z = y
  Deduce(|- (x <= z))
  Deduce(|- (z == y))
} else {
  Deduce(|- (!(x < y)))
  Deduce(|- (y <= x))
  z = x
  Deduce(|- (y <= z))
  Deduce(|- (z == x))
}
assert(z == x | z == y)
assert(y <= z & x <= z)

Nested Max

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// #Sireum #Logika
import org.sireum._
val x: Z = randomInt(); val y: Z = randomInt(); val z: Z = randomInt()
var m: Z = 0

if (x < y) {
  if (y < z) {
    m = z
  } else {
    m = y
  }
} else {
  if (x < z) {
    m = z
  } else {
    m = x
  }
}

assert(m == x | m == y | m == z)
assert(x <= m & y <= m & z <= m)

Swap Block

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// #Sireum #Logika
import org.sireum._

val m: Z = randomInt()
val n: Z = randomInt()
var x: Z = m
var y: Z = n

{
  val t = x
  x = y
  y = t
}

assert(x == n & y == m)

Swap Choice

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// #Sireum #Logika
import org.sireum._

val m: Z = randomInt()
val n: Z = randomInt()
var x: Z = m
var y: Z = n

if (x < y) {
  x = x + y
  y = x - y
  x = x - y
} else {
  val t = x
  x = y
  y = t
}
assert(x == n & y == m)

Summary

We have looked at conditionals:

  • by tracing facts through programs
  • by considering programs as facts
  • by symbolic execution

(Just as we did last week for assignments and compositions of assignments) We have reasoned about conditionals observing, in particular, the role of the conditions.