Slang Functions and Contracts

A Proof From Linear Algebra

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// linmap yields a * x + b for any a, x, and b
def linmap(a: Z, x: Z, b: Z): Z = {
  ...
}

// given (x - b) % a == 0 revmap yields (x - b) / a for any a, x, and b
def revmap(a: Z, x: Z, b: Z): Z = {
  ...
}

// compose yields x for any a, x, and b
def compose(a: Z, x: Z, b: Z): Z = {
  ...
  var y: Z = linmap(a, x, b)
  y = ... // use function revmap
  return y
}

The listing above shows three incompletely implemented functions linmap, revmap, and compose. Suggested implementations are provided in the comments. Let’s implement the functions step by step.

A Proof From Linear Algebra (linmap)

1
2
3
4
// linmap yields a * x + b for any a, x, and b
def linmap(a: Z, x: Z, b: Z): Z = {
  return a * x + b
}

The implementation of linmap is easiest. We can simply copy the expression from the comment into a return statement. Let’s leave it there for now.

A Proof From Linear Algebra (revamp)

1
2
3
4
// given (x - b) % a == 0 revmap yields (x - b) / a for any a, x, and b
def revmap(a: Z, x: Z, b: Z): Z = {
  return (x - b) / a
}

The implementation of revmap does not look challenging either. Variable a referred to in the return statement might be zero. We must add a requires clause to ensure the second operand of / is not zero.

1
2
3
Contract(
  Requires(a != 0)
)
1
2
3
4
5
6
7
// given (x - b) % a == 0 revmap yields (x - b) / a for any a, x, and b
def revmap(a: Z, x: Z, b: Z): Z = {
  Contract(
    Requires(a != 0)
  )
  return (x - b) / a
}

revmap_zero_operand

In fact we should also add (x - b) % a == 0 there as stated in the comment.

1
2
3
4
5
6
7
// given (x - b) % a == 0 revmap yields (x - b) / a for any a, x, and b
def revmap(a: Z, x: Z, b: Z): Z = {
  Contract(
    Requires(a != 0, (x - b) % a == 0)
  )
  return (x - b) / a
}

That’s it for now. Let’s turn to function compose.

A Proof From Linear Algebra (compose)

1
2
3
4
5
6
7
// compose yields x for any a, x, and b
def compose(a: Z, x: Z, b: Z): Z = {
  ...
  var y: Z = linmap(a, x, b)
  y = ... // use function revmap
  return y
}
  • Observe, z == linmap(a, x, b) && y == revmap(a, z, b)
  • implies z == a * x + b && y == (z - b) / a
  • implies y == (a * x + b - b) / a
  • implies y == (a * x) / a
  • implies y == x
  • So, the missing function call is revmap(a, y, b)
1
2
3
4
5
6
7
// compose yields x for any a, x, and b
def compose(a: Z, x: Z, b: Z): Z = {
  ...
  var y: Z = linmap(a, x, b)
  y = revmap(a, y, b)
  return y
}

There’s a problem with function compose. The pre-condition a != 0 of function revmap is not met. Because a is not modified in the function body, a != 0 can only be enforced by a pre-condition. We need to add a contract with the corresponding requires clause. compose_invalid_recond

1
2
3
4
5
6
7
// compose yields x for any a, x, and b
def compose(a: Z, x: Z, b: Z): Z = {
  ...
  var y: Z = linmap(a, x, b)
  y = revmap(a, y, b)
  return y
}

There’s a problem with function compose. The pre-condition a != 0 of function revmap is not met Because a is not modified in the function body, a != 0 can only be enforced by a pre-condition. We need to add a contract with the corresponding requires clause. compose_invalid_recond

1
2
3
4
5
6
7
8
9
// compose yields x for any a, x, and b
def compose(a: Z, x: Z, b: Z): Z = {
  Contract(
    Requires(a != 0)
  )
  var y: Z = linmap(a, x, b)
  y = revmap(a, y, b)
  return y
}

There’s another problem with function compose! The pre-condition (At(y, 0) - b) % a == 0 of function revmap is not met. (We’ve replaced x by At(y, 0) in the pre-condition according to the actual parameters.) Because y is assigned in var y: Z = linmap(a, x, b), the pre-condition will have to established by the result of function linmap. However, function linmap does not specify a post-condition. We need to add a contract with the corresponding ensures clause to linmap. The post-condition At(y, 0) == a * x + b implies the pre-condition (At(y, 0) - b) % a == 0 of revmap. Now let’s add the post-condition Res == x to compose. compose_invalid_recond linmap_with_contract

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// compose yields x for any a, x, and b
def compose(a: Z, x: Z, b: Z): Z = {
  Contract(
    Requires(a != 0),
    Ensures(Res == x)
  )
  var y: Z = linmap(a, x, b)
  y = revmap(a, y, b)
  return y
}

We can’t verify the function. The postcondition is invalid! We don’t have enough information to prove it. We’ve reasoned informally above that the postcondition should be true. However, we’ve not specified a postcondition for revmap yet. Let’s do this. Now it’s proved! We can summarise and document our reasoning in Slang by providing deduce commands (relying on the contracts). invalid_postcondition revmap_with_contract Logika_proved

A Proof From Linear Algebra (Summary)

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

// linmap yields a * x + b for any a, x, and b
def linmap(a: Z, x: Z, b: Z): Z = {
  Contract(
    Ensures(Res == a * x + b)
  )
  return a * x + b
}

// given (x - b) % a == 0
// revmap yields (x - b) / a for any a, x, and b
def revmap(a: Z, x: Z, b: Z): Z = {
  Contract(
    Requires(a != 0, (x - b) % a == 0),
    Ensures(Res == (x - b) / a)
  )
  return (x - b) / a
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// compose yields x for any a, x, and b
def compose1(a: Z, x: Z, b: Z): Z = {
  Contract(
    Requires(a != 0),
    Ensures(Res == x)
  )
  var y: Z = linmap(a, x, b)
  Deduce(|- (y == a * x + b))
  y = revmap(a, y, b)
  Deduce(|- (a != 0))
  Deduce(|- ((a * x + b - b) % a == 0))
  Deduce(|- (At(y, 0) == a * x + b))
  Deduce(|- (y == ((At(y, 0) - b) / a)))
  Deduce(|- (y == ((a * x + b - b) / a)))
  return y
}

Exercise 1

Provide functions linmap_spec, revmap_spec and compose_spec completing the Slang program below where x == compose_spec(a, x, b).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def linmap(a: Z, x: Z, b: Z): Z = {
  Contract(
    Ensures(Res == linmap_spec(a, x, b))
  )
  return a * x + b
}

def revmap(a: Z, x: Z, b: Z): Z = {
  Contract(
    Requires(a != 0, (x - b) % a == 0),
    Ensures(Res == revmap_spec(a, x, b))
  )
  return (x - b) / a
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def compose(a: Z, x: Z, b: Z): Z = {
  Contract(
    Requires(a != 0),
    Ensures(Res == compose_spec(a, x, b))
  )
  var y: Z = linmap(a, x, b)
  Deduce(|- (y == linmap_spec(a, x, b)))
  y = revmap(a, y, b)
  Deduce(|- (a != 0))
  Deduce(|- ((a * x + b - b) % a == 0))
  Deduce(|- (At(y, 0) == linmap_spec(a, x, b)))
  Deduce(|- (y == revmap_spec(a, At(y, 0), b)))
  Deduce(|- (y == revmap_spec(a, linmap_spec(a, x, b), b)))
  Deduce(|- (y == compose_spec(a, x, b)))
  return y
}

Exercise 2

Provide a function inverse with signature. def inverse(a: Z, x: Z, b: Z) (No return value!) that ensures: revmap_spec(a, linmap_spec(a, x, b), b) == x

Aside. This function corresponds to a mathematical theorem in Slang

Slang Functions and Frames

Example: Mutable Swapping with Frames

Recall the mutable swapping program.

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

val m: Z = randomInt()
val n: Z = randomInt()
var x: Z = m
var y: Z = n
x = x + y
y = x - y
x = x - y
Deduce(|- (x == n & y == m))

We’ve replaced the final assert statement with a Deduce command. Our intention is to prove this property of the swap program.

Recall the mutable swapping program.

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

val m: Z = randomInt()
val n: Z = randomInt()
var x: Z = m
var y: Z = n
x = x + y
y = x - y
x = x - y
Deduce(|- (x == n & y == m))

We’ve replaced the final assert statement with a Deduce command. Our intention is to prove this property of the swap program. Using what we’ve learned about programs and facts, we can express this without using variables m and n.

The simplifies the mutable swapping program

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

var x: Z = randomInt() // At(x, 0)

var y: Z = randomInt() // At(y, 0)

x = x + y
y = x - y
x = x - y
Deduce(|- (x == At(y, 0) & y == At(x, 0)))

For the sake of this example let’s restrict the values of the variables to positive integers.

Now, our example program looks as follows.

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

var x: Z = randomInt() // At(x, 0)
assume(x > 0)
var y: Z = randomInt() // At(y, 0)
assume(y > 0)
x = x + y
y = x - y
x = x - y
Deduce(|- (x == At(y, 0) & y == At(x, 0)))

With our example program in place, let’s focus on the three assignments.

They contain different assignments to variables x and y.

1
2
3
x = x + y
y = x - y
x = x - y

We’re interested in the contracts governing these assignments. Each of them:

  • modifies a variable
  • has a post-condition
  • (and, possibly, a pre-condition depending on the expression on its right-hand side)

Let’s consider one assignment after the other.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14



x = x + y




y = x - y




x = x - y
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// contract
//   modifies x
//   ensures x == At(x, 0) + y
x = x + y




y = x - y




x = x - y

The first assignment modifies x. The value At(x, 0) stems from the initial assignment.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// contract
//   modifies x
//   ensures x == At(x, 0) + y
x = x + y

// contract
//   modifies y
//   ensures y == x - At(y, 0)
y = x - y




x = x - y

The second assignment modifies y. The value At(y, 0) stems from the initial assignment.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// contract
//   modifies x
//   ensures x == At(x, 0) + y
x = x + y                      // At(x, 1)

// contract
//   modifies y
//   ensures y == x - At(y, 0)
y = x - y

// contract
//   modifies x
//   ensures x == At(x, 1) - y)
x = x - y

The first assignment modifies x. The value At(x, 1) stems from the indicated assignment.

Statements and Contracts

Not only assignments have contracts. Every Slang statement has a contract. As if each statement was a function with a contract specification. The contract reasoning for statements is built into Slang. Let’s make this explicit for the mutable swap program. We define a function for each assignment.

Example: Mutable Swapping with Frames

In a function contract we cannot refer to old values such as At(x, 0).

In a contract post-condition the old value is referred to In(x).

So, instead of writing

ensures x == At(x, 0) + y,

we write

ensures x == In(x) + y

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// contract
//   modifies x
//   ensures x == At(x, 0) + y
x = x + y




// contract
//   modifies y
//   ensures y == x - At(y, 0)
y = x - y




// contract
//   modifies x
//   ensures x == At(x, 1) - y)
x = x - y

We name the function for the first assignment xplus.

It encapsulates the assignment x = x + y with a contract.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def xplus() {
  Contract(
    Modifies(x),
    Ensures(x == In(x) + y)
  )
  x = x + y
}

// contract
//   modifies y
//   ensures y == x - At(y, 0)
y = x - y




// contract
//   modifies x
//   ensures x == At(x, 1) - y)
x = x - y

We name the function for the second assignment yminus.

It encapsulates the assignment y = x - y with a contract.

We treat this similar to the way we have dealt with the first assignment.

The last assignment is a little different.

It refers to the “different” old value At(x, 1).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def xplus() {
  Contract(
    Modifies(x),
    Ensures(x == In(x) + y)
  )
  x = x + y
}

def yminus() {
  Contract(
    Modifies(y),
    Ensures(y == x - In(y))
  )
  y = x - y
}



// contract
//   modifies x
//   ensures x == At(x, 1) - y)
x = x - y

The old value At(x, 1) must be provided by the context in which function xminus is called.

This was already the case At(x, 0) and At(y, 0).

But now it becomes apparent that In(x) might refer to either depending on at which point in a program the function is called.

Instead of:

  • x = x + y
  • y = x - y
  • x = x - y

We can write:

  • xplus()
  • yminus()
  • xminus()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def xplus() {
  Contract(
    Modifies(x),
    Ensures(x == In(x) + y)
  )
  x = x + y
}

def yminus() {
  Contract(
    Modifies(y),
    Ensures(y == x - In(y))
  )
  y = x - y
}

def xminus(): Unit = {
  Contract(
    Modifies(x),
    Ensures(x == In(x) - y)
  )
  x = x - y
}

Example: Mutable Swapping Function

Suppose we have defined a mutable swapping function swapA.

1
2
3
4
5
6
7
8
9
def swapA() {
  Contract(
    Modifies(x, y),
    Ensures(x == In(y), y == In(x))
  )
  x = x + y
  y = x - y
  x = x - y
}

We can replace the three assignments by the newly defined functions. We get the function swapB.

1
2
3
4
5
6
7
8
9
def swapB() {
  Contract(
    Modifies(x, y),
    Ensures(x == In(y), y == In(x))
  )
  xplus()
  yminus()
  xminus()
}

It has the same functionality as function swapA.

Exercise 3

Prove

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

var x: Z = randomInt() // At(x, 0)
assume(x > 0)
var y: Z = randomInt() // At(y, 0)
assume(y > 0)

...

swapA()
Deduce(|- (x == At(y, 0) & y == At(x, 0)))

Where you insert the definition of swapA for .... Prove that all intermediate values occurring in the body of function swapA are positive.

Exercise 4

Prove

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

var x: Z = randomInt() // At(x, 0)
assume(x > 0)
var y: Z = randomInt() // At(y, 0)
assume(y > 0)

...

swapB()
Deduce(|- (x == At(y, 0) & y == At(x, 0)))

Where you insert the definition of swapB and the supporting functions for .... Prove that all intermediate values occurring in the body of function swapB are positive.

Slang Functions as Facts

Example: Mutable Swapping Function (SwapA)

Consider function swapA once more.

1
2
3
4
5
6
7
8
9
def swapA() {
  Contract(
    Modifies(x, y),
    Ensures(x == In(y), y == In(x))
  )
  x = x + y
  y = x - y
  x = x - y
}

The fact corresponding to the three assignments is just like what we’ve seen before. We can look at it in Logika.

Example: Mutable Swapping Function (SwapA) as Fact

The fact for the function body of swapA is just as expected. Identifying At(x, 0) with In(x) and At(y, 0) with In(y) it is easy to see how the post-condition is established. This provides a view from the inside of the function. From the outside it is seen in a function call to swapA. Function_SwapA_Fact From the outside only the contract of swapA is seen. The post-condition x == In(y), y == In(x) of swapA provides directly the facts needed to prove the deduction. The modifies clause Modifies(x, y) specifies which variables need to be renamed using the At-notation Function_SwapA_Call

Example: Mutable Swapping Function (SwapB)

Consider function swapB with the function calls in the body.

1
2
3
4
5
6
7
8
9
def swapB() {
  Contract(
    Modifies(x, y),
    Ensures(x == In(y), y == In(x))
  )
  xplus()
  yminus()
  xminus()
}

The contracts for the three functions called in the body model the assignments closely.

Example: Mutable Swapping Function (SwapB) as Fact

The fact for the function body of swapB is the same as swapA. The contracts we’ve specified express the implicit contracts that govern assignments. Seen from the outside by way of a call swapA and swapB are indistinguishable: They have identical contracts. We can regard functions like theorem where the body is a proof. Using the theorem does not require knowledge of its proof. Function_SwapB_Fact

Slang Functions and Symbolic Execution

Problems

Function calls pose several challenges concerning symbolic execution.

  1. Function parameters introduce new temporary variables
  2. The same parameter name may be used in different functions
  3. Functions may call other functions
  4. Functions may contain recursive calls
  5. Functions may be nested
  6. Function calls may be nested

In fact, some of these problem already appear when dealing with loops. We will deal with (1) and (2) disallowing (3) to (6) for now.

Variable Names

Consider function shift below.

1
2
3
4
5
6
7
8
9
def shift(p: Z, y: Z, N: Z) {
  Contract(
    Requires(x * p + y * q == N),
    Modifies(x, q),
    Ensures(x * p + y * q == N)
  )
  x = x - y
  q = q + p
}

The function has the parameters p, y and N It refers to global variables q, x We need to rename p, y and N, the other two remain unchanged. Let’s prefix each of the three names with the name of the function shift_: shift_p, shift_y and shift_N

Consider function shift below.

1
2
3
4
5
6
7
8
9
def shift(p: Z, y: Z, N: Z) {
  Contract(
    Requires(x * p + y * q == N),
    Modifies(x, q),
    Ensures(x * p + y * q == N)
  )
  x = x - y
  q = q + p
}

As it were:

1
2
3
4
5
6
7
8
9
def shift(shift_p: Z, shift_y: Z, shift_N: Z) {
  Contract(
    Requires(x * shift_p + shift_y * q == shift_N),
    Modifies(x, q),
    Ensures(x * shift_p + shift_y * q == shift_N)
  )
  x = x - shift_y
  q = q + shift_p
}

We symbolically execute the function

1
2
3
def shift(p: Z, y: Z, N: Z) {
  ...
}

A call to the function now assigns values to the parameters

shift_p = ...
shift_y = ...
shift_N = ...

This approach does not generalise to arbitrary programs. Permitting (3) to (6) and (5) from slide (problem) makes this method unsound. Being unsound means that the symbolic execution would not describe the program behaviour accurately. We’re interested in sound symbolic execution that permits us to make predictions about program behaviour.

Initial Values of Global Variables

Consider function addy below.

1
2
3
4
5
6
def addy(y: Z) {
  Contract(
    Ensures(x == In(x) + y)
  )
  x = x + y
}

To deal with the value !In(x) we introduce an implicit parameter addy_In(x). The parameter addy_In(x) is assigned the value of variable x when the other parameters receive their value addy_In(x) = x

Return Values

Consider function add below

1
2
3
4
5
6
def add(x: Z, y: Z): Z = {
  Contract(
    Ensures(Res == x + y)
  )
  return x + y
}

Because calls are not nested add may only occur in assignments z = add(x, y). Symbolic execution of x + y is then simply subsumed by the assignment to z.

Symbolic Execution of the Program

Using function shift.

1
2
3
4
5
6
7
8
9
def shift(p: Z, y: Z, N: Z) {
  Contract(
    Requires(x * p + y * q == N),
    Modifies(x, q),
    Ensures(x * p + y * q == N)
  )
  x = x - y
  q = q + p
}

Let’s symbolically execute:

1
2
3
assume(x + q == N)
shift(1, 1, N)
assert(x + q == N)

ProSymExe_00

(x: X, q: Q, n: M), (PC: X + Q = M)

ProSymExe_01

ProSymExe_02

(x: X, q: Q, n: M, shift_p: 1, shift_y: 1, shift_N: 1, shift_In(x): X, shift_In(q): Q), (PC: X + Q = M)

ProSymExe_03

(x: X, q: Q, n: M, . . . ), (PC: X + Q = M, X * 1 + 1 * Q = M)

ProSymExe_04

(x: X - 1, q: Q, n: M, . . . ), (PC: X + Q = M, X * 1 + 1 * Q = M)

ProSymExe_05

(x: X - 1, q: Q + 1, n: M, . . . ), (PC: X + Q = M, X * 1 + 1 * Q = M)

ProSymExe_06

(x: X - 1, q: Q + 1, n: M, . . . ), (PC: X + Q = M, X * 1 + 1 * Q = M, (X - 1) * 1 + 1 * (Q + 1) = M)

ProSymExe_07

ProSymExe_08

(x: X - 1, q: Q + 1, n: M, . . . ), (PC: X + Q = M, X * 1 + 1 * Q = M, (X - 1) * 1 + 1 * (Q + 1) = M,

Exercise 5

Using

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def xplus() {
  Contract(
    Modifies(x),
    Ensures(x == In(x) + y)
  )
  x = x + y
}

def yminus() {
  Contract(
    Modifies(y),
    Ensures(y == x - In(y))
  )
  y = x - y
}

def xminus(): Unit = {
  Contract(
    Modifies(x),
    Ensures(x == In(x) - y)
  )
  x = x - y
}

Symbolically execute

1
2
3
4
5
6
val x0: Z = x
val y0: Z = y
xplus()
yminus()
xminus()
assert(x == y0 & y = x0)

Summary

  • We have looked at Slang functions in more detail
  • We have focussed on the notion on contract and proof
  • Considering assignments as a starting point we’ve analysed frames
  • We’ve looked at symbolic execution of a simplified version of Slang