K. Rustan M. Leino Microsoft Research, Redmond, WA, USA 19 July 2007 Invited talk, CADE-21 Bremen,...

Preview:

Citation preview

Designing verification conditions for software

K. Rustan M. LeinoMicrosoft Research, Redmond, WA, USA

19 July 2007Invited talk, CADE-21Bremen, Germany

Recent collaboratorsMike BarnettNikolaj BjørnerEvan ChangErnie CohenÁdám DarvasLeo de MouraManuel FähndrichBart JacobsFrancesco Logozzo

Ronald MiddelkoopRosemary MonahanPeter MüllerRalf SasseWolfram SchulteJan SmansHerman VenterAngela WallenburgBurkhart Wolff

Software engineering problemProblem

Building and maintaining programs that are correct

ApproachSpecifications record design decisions

bridge intent and codeTools amplify human effort

manage detailsfind inconsistenciesensure quality

Grand Challenge of

Verified SoftwareHoare, Joshi, Leavens, Misra, Naumann, Shankar, Woodcock, et al.

“We envision a world in which computer programs are always the most reliable component of any system or device that contains them” [Hoare & Misra]

Spec# programming systemSpec# language

Object-oriented .NET languageSuperset of C#, adding:

more typesspecifications (pre- and postconditions, etc.)

Usage rules (methodology)Checking:

Static type checkingRun-time checkingStatic verification (optional)

Static verificationSound modular verificationFocus on automation, not full functional correctness specificationsNo termination verificationNo verification of temporal properties

Spec# demo

stati

c veri

fier

(Boogie

)

MSIL (“bytecode”)

SMT solver

V.C. generator

Inference engine

Translator

verification condition

“correct” or list of errors

Spec# compiler

Spec#

BoogiePL

Spec# verifier architecture

BoogiePL – a verification tool bus C

HAVOC and VerifiedC

Eiffel …?Java

bytecode + BML

Spec#

Z3Simplif

yZap 2

SMT Lib

Fx7

Isabelle/HOL

…?

BoogiePL

abstract interpreter

predicate abstraction?

termination detector?

…?

Requirements of proverEUF, arithmetic, quantifiersCounterexamplesFast, for both valid and invalid formulas

Achieving goodSMT-solver performance

Non-chronological backtrackingAvoid “exponential splitting”Incremental matching

-- Leonardo de Moura, on Z3 for Boogie

BoogiePL declarationstypeconstfunctionaxiomvarprocedureimplementation

BoogiePL statementsx := Ea[ i ] := Ehavoc xassert Eassume E;call P()

ifwhilebreaklabel:goto A, B

BoogiePL demo: DutchFlag

Example: source program

class C : object {int x;C( ) { … } virtual int M(int n) { … } static void Main() {

C c = new C( );c.x = 12;int y = c.M(5);

}}

Example: BoogiePL translation (0)

// class typesconst unique System.Object: name;const unique C: name;

axiom C <: System.Object;

function typeof(ref) returns (name);

// fieldstype field;const unique C.x: field;const unique allocated: field;

// the heapvar Heap: [ref, field] int;

class C: object {

int x;

Example: BoogiePL translation (1)

// method declarations

procedure C..ctor(this: ref); requires this != null && typeof(this) <: C; modifies Heap;

procedure C.M(this: ref, n: int) returns (result: int); requires this != null && typeof(this) <: C; modifies Heap;

procedure C.Main(); modifies Heap;

C() { … }

virtual int M(int n)

static void Main()

Example: BoogiePL translation (2)

// method implementations

implementation C.Main(){ var c: ref, y: int;

havoc c; assume c != null; assume Heap[c, allocated] ==

0; assume typeof(c) == C; Heap[c, allocated] := 1; call C..ctor(c);

assert c != null; Heap[c, C.x] := 12;

call y := C.M(c,

5);

}

C c = new C();c.x = 12;

int y = c.M(5);

Computing weakest preconditions0. Cut back edges1. Remove assignments2. (Recursively) apply wp (with some

form of memoization)

Modeling the memoryclass C { int f; object g; }var f: [ref] int;var g: [ref] ref;type Field; type Value;var Heap: [ref, Field] Value;function V2I(Value) returns (int);function I2V (int) returns (Value);axiom ( v: Value I2V(V2I(v)) == v);axiom ( i: int V2I(I2V(i)) == i);type Field ;var Heap: . [ref, Field ] ;For C:

select(Memory, Region, Offset, Length)store(Memory, Region, Offset, Length, Value)

x = c.f;

x := f [ c ];x = c.f;

x := V2I(Heap[ c, f ]);

x = c.f;

x := Heap[ c, f ];

Example:Chunker.NextChunk specificationpublic string NextChunk() modifies this.*; ensures result.Length <= ChunkSize;

Chunker.NextChunk translationprocedure Chunker.NextChunk(this: ref where $IsNotNull(this, Chunker)) returns ($result: ref where $IsNotNull($result, System.String)); // in-parameter: target object free requires $Heap[this, $allocated]; requires ($Heap[this, $ownerFrame] == $PeerGroupPlaceholder || !($Heap[$Heap[this, $ownerRef], $inv] <: $Heap[this, $ownerFrame]) ||

$Heap[$Heap[this, $ownerRef], $localinv] == $BaseClass($Heap[this, $ownerFrame])) && (forall $pc: ref :: $pc != null && $Heap[$pc, $allocated] && $Heap[$pc, $ownerRef] == $Heap[this, $ownerRef] && $Heap[$pc, $ownerFrame] == $Heap[this, $ownerFrame] ==> $Heap[$pc, $inv] == $typeof($pc) && $Heap[$pc, $localinv] == $typeof($pc));

// out-parameter: return value free ensures $Heap[$result, $allocated]; ensures ($Heap[$result, $ownerFrame] == $PeerGroupPlaceholder || !($Heap[$Heap[$result, $ownerRef], $inv] <: $Heap[$result, $ownerFrame]) ||

$Heap[$Heap[$result, $ownerRef], $localinv] == $BaseClass($Heap[$result, $ownerFrame])) && (forall $pc: ref :: $pc != null && $Heap[$pc, $allocated] && $Heap[$pc, $ownerRef] == $Heap[$result, $ownerRef] && $Heap[$pc, $ownerFrame] == $Heap[$result, $ownerFrame] ==> $Heap[$pc, $inv] == $typeof($pc) && $Heap[$pc, $localinv] == $typeof($pc));

// user-declared postconditions ensures $StringLength($result) <= $Heap[this, Chunker.ChunkSize]; // frame condition modifies $Heap; free ensures (forall $o: ref, $f: name :: { $Heap[$o, $f] } $f != $inv && $f != $localinv && $f != $FirstConsistentOwner && (!IsStaticField($f) || !

IsDirectlyModifiableField($f)) && $o != null && old($Heap)[$o, $allocated] && (old($Heap)[$o, $ownerFrame] == $PeerGroupPlaceholder || !(old($Heap)[old($Heap)[$o, $ownerRef], $inv] <: old($Heap)[$o, $ownerFrame]) || old($Heap)[old($Heap)[$o, $ownerRef], $localinv] == $BaseClass(old($Heap)[$o, $ownerFrame])) && old($o != this || !(Chunker <: DeclType($f)) || !$IncludedInModifiesStar($f)) && old($o != this || $f != $exposeVersion) ==> old($Heap)[$o, $f] == $Heap[$o, $f]);

// boilerplate free requires $BeingConstructed == null; free ensures (forall $o: ref :: { $Heap[$o, $localinv] } { $Heap[$o, $inv] } $o != null && !old($Heap)[$o, $allocated] && $Heap[$o, $allocated] ==>

$Heap[$o, $inv] == $typeof($o) && $Heap[$o, $localinv] == $typeof($o)); free ensures (forall $o: ref :: { $Heap[$o, $FirstConsistentOwner] } old($Heap)[old($Heap)[$o, $FirstConsistentOwner], $exposeVersion] ==

$Heap[old($Heap)[$o, $FirstConsistentOwner], $exposeVersion] ==> old($Heap)[$o, $FirstConsistentOwner] == $Heap[$o, $FirstConsistentOwner]);

free ensures (forall $o: ref :: { $Heap[$o, $localinv] } { $Heap[$o, $inv] } old($Heap)[$o, $allocated] ==> old($Heap)[$o, $inv] == $Heap[$o, $inv] && old($Heap)[$o, $localinv] == $Heap[$o, $localinv]);

free ensures (forall $o: ref :: { $Heap[$o, $allocated] } old($Heap)[$o, $allocated] ==> $Heap[$o, $allocated]) && (forall $ot: ref :: { $Heap[$ot, $ownerFrame] } { $Heap[$ot, $ownerRef] } old($Heap)[$ot, $allocated] && old($Heap)[$ot, $ownerFrame] != $PeerGroupPlaceholder ==> old($Heap)[$ot, $ownerRef] == $Heap[$ot, $ownerRef] && old($Heap)[$ot, $ownerFrame] == $Heap[$ot, $ownerFrame]) && old($Heap)[$BeingConstructed, $NonNullFieldsAreInitialized] == $Heap[$BeingConstructed, $NonNullFieldsAreInitialized];

Working with quantifiersInstantiation via e-graph matchingA matching pattern (trigger) is a set of terms that together mention all the bound variables, and none of which is just a bound variable by itselfExamples:

(x { f(x) } 0 ≤ f(x))(x,y { g(x,y) } f(x) < g(x,y))

More trigger examples(x,y { f(x), f(y) } x ≤ y f(x) ≤ f(y))(x { f(x) } x ≠ null f(x) ≤ f(next(x)))(x { f(next(x)) } x ≠ null f(x) ≤ f(next(x)))(x,y { f(x), f(y) } f(x) = f(y) x = y)(x { f(x) } f Inv(f(x)) = x)(x { f(x+1) } f(x) ≤ f(x+1))(x,y,z { x*(y+z) } x*(y+z) = x*y + x*z)(x,y { P(x,y) } x = y P(x,y) = 10)(x { P(x,x) } P(x,x) = 10)

Axiomatizing characteristics of source language

Introduce IsHeap(h) to mean“h is a well-formed heap”class C { T f; … }(h,o IsHeap(h) o ≠ null

h[o,f ] = null typeof(o) <: T )(h,o IsHeap(h) o ≠ null h[o,allocated]

h[o,f ] = null h[h[o,f ], allocated] )Since the heap uses the select/store axioms, it cannot be enforced by the BoogiePL type system

Discharging the IsHeap antecedent

The encoding needs to introduce assumptions that IsHeap holds

o.f = E;Heap[o,f ] := E; assume IsHeap(Heap);

IsHeap(Heap) is a free pre- and postcondition of every method

free requires IsHeap(Heap);modifies Heap;free ensures IsHeap(Heap);

Show and tell: Array types

Demo: Aggregate objects

Object states: a picture of the heap

Mutable

Consistent

Committed

c: Chunker

StringBuilder

ownershipsb

expose (c) { … }

Valid

Object states: a picture of the heap

Mutable

Committed

c: Chunker

StringBuilder

ownership

expose (c) { … }

sb

Consistent

Valid

Object states: a picture of the heap

Mutable

Committed

c: Chunker

StringBuilder

ownership

expose (c) { … }

sb

Consistent

Valid

Object states: a picture of the heap

Mutable

Committed

c: Chunker

StringBuilder

ownership

expose (c) { … }

sb

Consistent

Valid

Validityclass C { int x; int y; invariant x ≤ y; … }(h, o

IsHeap(h) x ≠ null h[o,allocated]

typeof(o) <: C Valid(o, h) h[o,x] ≤ h[o,y] )

Peer consistencyo is peer consistent in h

(h[o, owner] = Valid(h[o, owner]))

(p h[p, owner] = h[o, owner] Valid(p,h))

Peer consistency is the default precondition of all parameters of all methodsNeeds to be proved (all over!)

Frame conditionsvoid M( ) modifies this.x, this.y; { … }modifies Heap;ensures (o, f

Heap[o,f] = old(Heap)[o,f] (o = this f = x) (o = this f = y) old(Heap)[o, allocated]// or o is committed in old(Heap) old(Heap[o,owner]

Valid(Heap[o,owner], Heap)))

Type system for intermediate verification languageTypes find errors in translation

Some types are required by some provers (e.g., SMT Lib)

Type example (0)type R; // type representing recordstype Field ;function Get: . R Field ;function Set: . R Field R;axiom ( (r: R, f: Field , g: Field , x:

f = g Get(Set(r, f, x), g) = x ));axiom (, (r: R, f: Field , g: Field , x:

f g Get(Set(r, f, x), g) = Get(r, g) ));

Type error

Type example (1)class C {

int x; bool y; void M() modifies this.x, this.y;

{ … }const x: Field int;const y: Field bool;procedure M(this: ref);

modifies Heap;ensures ( (o: ref, f: Field

Heap[o,f] = old(Heap)[o,f] (o = this f = x) (o = this f = y) … )

Type errors

Are types worth the trouble?How complicated does the type

system need to be?parametric polymorphism?subtyping?type (disequality) constraints?guarded types?

How is it translated into provers with less advanced type systems?Performance penalty (ironically)Does it really help the prover that much?

[Meng & Paulson][Couchot & Lescuyer]

Other prover featuresLabeled subformulas

Useful for:pin-pointing location and type error, anddetermining execution traces leading to error

ModelsCan be used to print better error messages

Proving existentialsNeeded to prove that a function is well-definedMatching not up to the task (there is nothing to match against)

Download

Spec# and

Boogie

from here

Summary and conclusionsSpec# system, Boogie, BoogiePLTo verify, use an intermediate language

Separates concernsPromotes sharing in verification community

Front ends for multiple languagesMultiple theorem proversShared benchmarks

Quantifiers are both crucial and convenientReduce the need for other theoriesTriggers are important

Need to be carefully designedInclude in competitions. SMT Comp?

What’s a good type system for prover input?Hardest part in designing VCs: programming methodology that

Fits common programming idioms andCan be handled well by automatic prover

Education

http://research.microsoft.com/specsharp

Recommended