ECI 2007: Specification and Verification of ObjectOriented Programs Lecture 1 The Verifying Compiler “A verifying compiler uses automated ..
Download ReportTranscript ECI 2007: Specification and Verification of ObjectOriented Programs Lecture 1 The Verifying Compiler “A verifying compiler uses automated ..
ECI 2007: Specification and
Verification of ObjectOriented Programs
Lecture 1
The Verifying Compiler
“A verifying compiler uses
automated .. reasoning to check the correctness
of the program that it compiles.
Correctness is specified by
types, assertions, .. and other redundant
annotations
that accompany the program.”
[Hoare, 2004]
Theorem Proving and Software
Semantics
Program
Meets spec/Found Bug
Specification
Validity
VC
generation
Provability
(theorem proving)
Theorem
in a logic
• Soundness:
– If the theorem is valid then the program
meets specification
– If the theorem is provable then it is valid
Spec# Approach for a Verifying
Compiler
• As source language we use C#
• As specifications we use method contracts,
invariants, and also class, field and type annotations
• As program logic we use Dijkstra’s weakest
preconditions
• For automatic verification we use type checking,
verification condition generation (VCG) and automatic
theorem proving (ATP)
Demo (Spec#)
Spec# Tool Architecture
Spec# (annotated C#)
Spec# Compiler
Boogie PL
VC Generator
Formulas
Automatic Theorem Prover
Z3 SMT solver
Lectures
1. Logic of Object-oriented
Programs
From Spec#
To BoogiePL
2. Invariants and Ownership
3. Verification Condition
Generation
4. Proving Verification
Conditions
From Boogie PL
To Formulas
SMT solvers
Programs ! Theorems =
Axiomatic Semantics
• Consists of:
– A language for making assertions about programs
– Rules for establishing when assertions hold
• Typical assertions:
– During the execution, only non-null pointers are
dereferenced
– This program terminates with x = 0
• Partial vs. total correctness assertions
– Safety vs. liveness properties
– Usually focus on safety (partial correctness)
Hoare Triples
• Partial correctness: { A } s { B}
– When you start s in any state that satisfies A
– If the execution of s terminates
– It does so in a state that satisfies B
• Total correctness: [ A ] s [ B ]
– When you start s in any state that satisfies A
– The execution of s terminates and
– It does so in a state that satisfies B
• Defined inductively on the structure of
statements
Hoare Rules
{A} s1 {C}
{C} s2 {B}
{ A } s1; s2 {B}
{A Æ E} s1 {B}
{A Æ :E} s2 {B}
{A} if E then s1 else s2 {B}
{I Æ E} s {I}
IÆ:E)B
{ I} while E do s {B}
Hoare Rules: Assignment
• Example: { A } x := x + 2 {x >= 5 }. What is A?
• General rule:
{ B[E/x] } x := E {B}
• Surprising how simple the rule is !
• The key is to compute “backwards” the precondition
from the postcondition
• Forward rule is more complicated:
{ A } x := E {9 x’. A[x’/x] Æ x = E[x’/x] }
Weakest preconditions
Dijkstra’s idea: To verify that { A } s { B}
a) Let Pre(s, B) = {A’ | { A’} s { B} }
b) (Pre(s,B), ) is a lattice
- false Pre(s,B)
- if Pre(s,B) and Pre(s,B), then Pre(s,B)
- if Pre(s,B) and Pre(s,B), then Pre(s,B)
c) WP(s, B) = lub(Pre(s, B))
d) Compute WP(s, B) and prove A WP(s, B)
Predicate lattice
false
)
Pre(s, B)
strong
A
weakest
precondition: WP(s, B)
true
weak
Weakest preconditions
• WP(x := E, B) = B[E/x]
• WP(s1; s2, B) = WP(s1, WP(s2, B))
• WP(if E then s1 else s2, B) =
E ) WP(s1, B) Æ : E ) WP(s2, B)
• WP(assert E, B) = E B
Example
returns c
requires true
ensures c = a b
bool or(bool a, bool b) {
if (a)
c := true
S
else
c := b
}
WP(S, c = a b) = (a true = a b) (a b = a b)
Conjecture to be proved:
true (a true = a b) (a b = a b)
Weakest preconditions (Contd.)
• What about loops?
• Define a family of weakest preconditions
– WPk(while E do S, B) = weakest precondition from which if the
loop terminates in k or fewer iterations, then it terminates in B
WP0 = : E ) B
WPi+1 = WPi
(E ) WP(s, WPi))
• WP(while E do S, B) = Æk ¸ 0 WPk = glb{WPk | k ¸ 0}
– Hard to compute
– Can we find something easier yet sufficient ?
Not quite weakest preconditions
• Recall what we are trying to do:
false
)
Pre(s, B)
strong
weakest
A precondition: WP(s, B)
verification
condition: VC(s, B)
true
weak
• We shall construct a verification condition: VC(s, B)
– The loops are annotated with loop invariants !
– VC is guaranteed stronger than WP
– But hopefully still weaker than A: A ) VC(s, B) ) WP(s, B)
Desugaring loops
• WP(havoc x, B) = x. B
• WP(assume E, B) = E B
• whileI,T E do s
assert I; havoc T; assume I;
if (E) { s; assert I; assume false }
– I is the loop invariant (provided by the
programmer)
– T is the set of loop targets (can be approximated
by scanning the loop body)
Example 1
returns c
requires b >= 0
returns c
ensures c = a + b
requires b >= 0
int add(int a, int b) {
ensures c = a + b
int t;
int add(int a, int b) {
t := b;
int t;
c := a;
t := b;
assert t 0 c = a+b-t;
c := a;
havoc c,t;
invariant
assume t 0 c = a+b-t;
t 0 c = a+b-t
A
A’
if (t > 0) {
while (t > 0) {
c := c + 1;
c := c + 1;
L
t := t – 1;
t := t – 1;
assert t 0 c = a+b-t;
}
assume false;
}
}
Conjecture to be proved:
}
b 0 VC(A, c = a + b)
returns c
requires b >= 0
ensures c = a + b
int add(int a, int b) {
int t;
t := b;
c := a;
assert t 0 c = a+b-t;
havoc c,t;
assume t 0 c = a+b-t;
A’
if (t > 0) {
c := c + 1;
L
t := t – 1;
assert t 0 c = a+b-t;
assume false;
}
}
VC(L, c = a + b)
c,t.
(t 0 c = a + b – t
t>0t-10
c + 1 = a + b – (t - 1)
t 0 c = a + b)
VC(A’, c = a + b)
b0a=a+b–b
c,t.
(t 0 c = a + b – t
t>0t-10
c + 1 = a + b – (t - 1)
t 0 c = a + b)
Conjecture to be proved:
b 0 VC(A’, c = a + b)
Example 2
returns c
ensures c = a + b
int add(int a, int b) {
int t;
t := b;
c := a;
assert c = a+b-t;
havoc c,t;
assume c = a+b-t;
A’
if (t 0) {
c := c + 1;
L
t := t – 1;
assert c = a+b-t;
assume false;
}
Conjecture to be proved:
}
true VC(A, c = a + b)
returns c
ensures c = a + b
int add(int a, int b) {
int t;
t := b;
c := a;
invariant
c = a+b-t
while (t 0) {
A
c := c + 1;
t := t – 1;
}
}
Invariants Are Not Easy
• Consider the following code from QuickSort
int partition(int *a, int L0, int H0, int pivot) {
int L = L0, H = H0;
while(L < H) {
while(a[L] < pivot) L ++;
while(a[H] > pivot) H --;
if(L < H) { swap a[L] and a[H] }
L++;
}
return L
}
• Consider verifying only memory safety
• What is the loop invariant for the outer loop ?
Verification conditions (Contd.)
• What about procedure calls?
– Annotate each procedure with a
precondition pre, a modifies clause M, a
postcondition post
– f() assert pre; havoc M; assume post;
Program verification
• Annotate each procedure with a
precondition, a modifies clause, and a
postcondition and each loop with an
invariant and loop targets
• Verify each procedure separately
requires pre
modifies M
ensures post
f() {
S;
}
Verify that the formula
VC(assume pre; S; assert post, true)
is valid and the targets of S
are contained in M
Exponential
Verification Conditions
The following two culprits may result in a VC
that is exponential in the size of the program.
• WP(x := E, B) = B[E/x]
• VC(if E then s1 else s2, B) =
(E ) VC(s1, B)) Æ (: E ) VC(s2, B))
s1
x := x + x;
.
.
.
x := x + x;
s2
if (b1) x := x+1;
else x := x+2;
.
.
.
if (bn) x := x+1;
else x := x+2;
n statements
n statements
VC(s1, x > 1) has 2n
occurrences of x.
VC(s2, x > 1) has at least
2n-1 occurrences of .
Efficient VC Generation
How can we get a formula that is linear in
the size of the (loop-free and call-free)
program?
Eliminate assignments
• Dynamic single assignment (DSA) form
– There is at most one definition for each
variable on each path
– Replace defs/uses with new incarnations
• x := x+1 with xn+1 = xn + 1
– Replace havoc x with new incarnations xn+1
– At join points unify variable incarnations
• Eliminate assignments by replacing
– x := E with assume x = E
Example
assume x = 1;
assume x0 = 1;
x := x + 1;
assume x1 = x0 + 1;
if (x = 0) {
if (x1 = 0) {
x := x + 2;
} else {
assume x2 = x1 + 2;
assume x4 = x2;
} else {
x := x + 3;
assume x3 = x1 + 3;
assume x4 = x3;
}
}
assert x = 5
assert x4 = 5;
Efficient VC Generation (I)
WP(x := E, B) = B[E/x]
compare with
WP(assume x = E, B) = (x = E B)
Intuition:
The decorated variables essentially give names to
sub-expressions in the original VC. The unique definition
of each variable now occurs as an assumption.
Efficient VC Generation (II)
VC(if E then s1 else s2, B) =
E VC(s1, B)
:E VC(s2, B)
compare with
VC(if E then s1 else s2, B) =
E VC(s1, true) B
:E VC(s2, true) B
E VC(s1, true)
B
:E VC(s2, true)
=
B occurs only once in
the VC eliminating the
exponential blowup.
Unstructured control flow
• Control flow is not always structured
– exceptions, goto statements
• How do we generated verification
conditions for programs with
unstructured control flow?
Unstructured control flow
• Eliminate loops, procedure calls, assignments
– Passive program with acyclic control flow
• Eliminate if-then-else statements using goto
statements
• Finally, program is a collection of blocks
• Each block is
– l: A1,…,Am; goto l1,…,ln
– l is block label
– A is either assume E or assert E
• Distinguished “start” label
Example
assume x0 = 1;
assume x1 = x0 + 1;
if (x1 = 0) {
assume x2 = x1 + 2;
assume x4 = x2;
} else {
}
assume x3 = x1 + 3;
assume x4 = x3;
assert x4 = 5;
start:
assume x0 = 1; goto l1;
l 1:
assume x1 = x0 + 1; goto l2, l3;
l2:
assume x1 = 0;
assume x2 = x1 + 2;
assume x4 = x2; goto l4;
l3:
assume x1 0;
assume x3 = x1 + 3;
assume x4 = x3; goto l4;
l4: assert x4 = 5
VC Generation for
Unstructured Control Flow
• For each block A = l: S goto l1,..,ln introduce a
boolean variable Aok
– Aok holds iff all executions starting at A do not
end in a failed assertion
• Introduce a Block Equation for each block A:
BEA Aok VC(S, BSucc(A). Bok)
• VC of entire program:
(A. BEA) Startok
Spec# Tool Architecture
Spec# (annotated C#)
Spec# Compiler
Boogie PL
VC Generator
Formulas
Automatic Theorem Prover
Z3 SMT solver
Boogie PL: Parts
Boogie PL source contains
• a first order theory to encode the
background semantics of the source
language and the program, described
– constants, functions and axioms
• an imperative part used to encode the
traces of the source program,
described by
– procedures, pre and postconditions,
mutable variables, and unstructured code
Limits of Boogie PL
Boogie PL does not contain
• structured types
• a heap
• expressions with side effects
• visibility
• subtyping
• dynamic dispatch
Boogie PL: Procedures
• Declaration
procedure Find(xs: [int] int, ct: int, x: int) returns
(result: int);
• Implementation
implementation Find(xs: [int] int, ct: int, x: int)
returns (result: int)
{…}
• Call
call r := Find(bits, 100, 32)
Boogie PL: Procedure
Specifications
proc Find(xs: [int] int, ct: int, x: int) returns (result: int);
requires ct ≥ 0;
ensures result ≥ 0 result < ct xs[result] = x;
ensures result < 0 !( i:int :: 0 ≤ i i < ct xs[i] = x);
A Bogus Implementation?
var xs: [int] int;
var ct: int;
proc Find(x: int) returns (result: int);
requires ct ≥ 0;
ensures result ≥ 0 result < ct xs[result]=x;
ensures result < 0 !( i:int :: 0 ≤ i i < ct xs[i] = x);
impl Find(x: int) returns (result: int)
{ start: ct := 0; result := -1; return; }
More about Postconditions
Postconditions
• must say which variables x might change
– modifies x;
variables not mentioned are not allowed
to change
• often relate pre-state and post-state
– ensures x == old(x)+1;
proc Find(x: int) returns (result: int);
…
modifies ct;
// would allow the previous implementation
ensures ct == old(ct);
// would disallow the change despite modifies clause