Specifying and Verifying Software for Reliable Systems with Spec# and BoogiePL
Understand the approach, tools, and challenges for ensuring correctness in software engineering using Spec# and BoogiePL. Learn about the Spec# programming system, static verification, BoogiePL overview, and top-level declarations in BoogiePL. Explore defining OO semantics, translation into BoogiePL, and the type of the heap. This presentation delves into verifying software for reliability and correctness.
Specifying and Verifying Software for Reliable Systems with Spec# and BoogiePL
E N D
Presentation Transcript
Specifying and verifying software K. Rustan M. Leino Microsoft Research, Redmond, WA, USA 8 Nov 2007Invited talk, ASE 2007Atlanta, GA
Collaborators most relevant to this talk • Mike Barnett • Nikolaj Bjørner • Leonardo de Moura • Manuel Fähndrich • Bart Jacobs • Ronald Middelkoop • Michał Moskal • Peter Müller • Ralf Sasse • Wolfram Schulte • Jan Smans • Herman Venter • Angela Wallenburg
Software engineering problem • Problem • Building and maintaining programs that are correct • Approach • Specifications record design decisions • bridge intent and code • Tools amplify human effort • manage details • find inconsistencies • ensure quality
Grand Challenge ofVerified Software • Hoare, 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 system • Spec# language • Object-oriented .NET language • Superset of C#, adding: • more types • specifications (pre- and postconditions, etc.) • Usage rules (methodology) • Checking: • Static type checking • Run-time checking • Static verification (optional)
Static verification • Sound modular verification • Focus on automation, not full functional correctness specifications • No termination verification • No verification of temporal properties
Spec# verifier architecture Spec# Spec# compiler MSIL (“bytecode”) Translator BoogiePL Inference engine static verifier (Boogie) V.C. generator verification condition SMT solver “correct” or list of errors
BoogiePL – a verification tool bus Spec# C HAVOC and VerifiedC (vcc) Java bytecode + BML Eiffel …? abstract interpreter predicate abstraction? BoogiePL termination detector? …? Z3 Simplify Zap 2 SMT Lib Fx7 Isabelle/HOL …?
Challenges 0: specifications 1: specifications Spec# C 3: translation 4: translation BoogiePL 2: BoogiePL overview 5:V.C. generation Z3
0: Specifications in Spec# • Multi-variable invariants • Multi-object invariants
Multi-variable invaraints • Demo
Multi-object invariants :Chunker :Chunker :Classroom n: 84 n: 20 invstudentGrades.Count ≤ 20; invdict.Count ≤ n; invdict.Count ≤ n; rep dict: dict: studentGrades: owner :Dictionary Count: 21
1: Specifications in vcc • Based on dynamic frames [YannisKassios, FM 2006] class C {int x; int y; Dictionary d; …frame f { x, y, d, … } d.fr;predicate Valid reads f x ≤ y d.Valid … ;method M()requires Valid;modifies f;ensures Valid;}
2: Overview of BoogiePL • Top-level declarations • Statements
BoogiePL declarations • type T; • const x: T; • function f(A, B) returns (T); • axiom E; • var y: T; • procedure P(a: A, b: B) returns (x: T, y: U); requires pre; modifies w; ensures Q; • implementation P(a: A, b: B) returns (x: T, y: U) { … }
BoogiePL statements • x := E • a[ i ] := E • havoc x • assert E • assume E • ; • call P() • if • while • break • label: • goto A, B
BoogiePL demo Queue frame :Queue head: tail: … :Node :Node :Node :Node next: next: next:
3: Defining OO semantics by translation into BoogiePL class C : object{int x; C() { … } virtualint M(int n) { … } staticvoid Main() { C c = new C();c.x = 12;int y = c.M(5); }} Example source program
// class types constuniqueSystem.Object: name; constuniqueC: name; axiom C <: System.Object; functiontypeof(ref) returns(name); // fields typeField; constuniqueC.x: Field; constuniqueallocated: Field; // the heap var Heap: [ref, Field] int; Example: BoogiePL translation (0) class C : object { int x;
// 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; procedureC.Main(); modifies Heap; Example: BoogiePL translation (1) C() { … } virtualint M(int n) staticvoid Main()
// method implementations implementationC.Main() { var c: ref, y: int; havoc c; assume c != null; assume Heap[c, allocated] == 0; assumetypeof(c) == C; Heap[c, allocated] := 1; call C..ctor(c); assert c != null; Heap[c, C.x] := 12; call y := C.M(c, 5); } Example: BoogiePL translation (2) c.x = 12; C c = new C(); int y = c.M(5);
The type of the heap • type Field;var Heap: [ref, Field] int; • type Field;varHeapInt: [ref, Field] int;varHeapBool: [ref, Field] bool;… • type Field;type Value;var Heap: [ref, Field] Value; and conversion functions between Value and other types • type Field ;var Heap: .[ref, Field ] ;
Translation in more detail:Chunker.NextChunk specification publicstringNextChunk() modifiesthis.*; ensuresresult.Length <= ChunkSize;
Chunker.NextChunk translation procedureChunker.NextChunk(this: refwhere $IsNotNull(this, Chunker)) returns ($result: refwhere $IsNotNull($result, System.String)); // in-parameter: target object freerequires $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 freeensures $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; freeensures (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 freerequires $BeingConstructed == null; freeensures (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)); freeensures (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]); freeensures (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]); freeensures (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];
4: Defining C semantics by translation into BoogiePL • A pointer in C can go to any byte location in a segment • A pointer is a (segment, offset) pair • base(ptr) gives the segment • offset(ptr) gives the offset within the segment • Each segment has a fixed length • length(seg)
Modeling memory • Consider the translation of: *p = k; m = *p; • typePtr; type Segment;function base(Ptr) returns (Segment);function offset(Ptr) returns (int); • varMem: [Segment, int] int; • assert 0 ≤ offset(p);assertoffset(p) + 4 ≤ length(base(p));Mem[base(p), offset(p)] := B3(k);Mem[base(p), offset(p)+1] := B2(k);Mem[base(p), offset(p)+2] := B1(k); Mem[base(p), offset(p)+3] := B0(k); • assert 0 ≤ offset(p);assertoffset(p) + 4 ≤ length(base(p));m := Word( Mem[base(p), offset(p)+3],Mem[base(p), offset(p)+2],Mem[base(p), offset(p)+1],Mem[base(p), offset(p)]);
Alternative memory model • Define custom functions to access memory • typeMType; typePtr;typeByteSeq;function rd(MType, Ptr, int) returns (ByteSeq);functionwr(MType, Ptr, int, ByteSeq) returns (Mtype); • axiom ( m: MType, p: Ptr, q: Ptr,plen: int, qlen: int, val: ByteSeq base(p) = base(q) offset(p) = offset(q) 0 < plen plen = qlen rd(wr(m,p,plen,val), q, qlen) = val); • axiom ( m: MType, p: Ptr, q: Ptr,plen: int, qlen: int, val: ByteSeq base(p) base(q) offset(p) + plen ≤ offset(q) offset(q) + qlen ≤ offset(p) rd(wr(m,p,plen,val), q, qlen) = rd(m, q, qlen)); • axiom …
5: Verification-condition generation • Use of quantifiers • Matching triggers, available already in BoogiePL • Reducing redundancy • Paths across conditional statements • But also splitting VCs into smaller ones • wp versus sp
Weakest-precondition versus strongest-postcondition • { P } S { Q } • Started in a state satisfying P, • no execution of S will go wrong, and • every terminating execution of S will end in a state satisfying Q • P wp(S, Q) • or perhaps: sp(P, S) Q ?
wp versus sp • wp(x := E, Q) = Q[E/x] • wp(S;T, Q) =wp(S, wp(T, Q)) • wp(assume E, Q) = E Q • wp(assert E, Q) = E Q • sp(P, x := E) =(y P[y/x] x = E[y/x]) • sp(P, S;T) = sp(sp(P, S), T) • sp(P, assume E) = E P • sp(P, assert E) = E Pbut one would also like to check that E actually holds in P
Further challenges • Prove more programs • Extend structuring methodologies • Improve performance • Raise level of abstraction, e.g.: • Alloy • Meta programming • B method
Summary and conclusions • Verification for Spec#, C, and BoogiePL • To verify, use an intermediate language • Separates concerns • Promotes sharing in verification community • Front ends for multiple languages • Multiple theorem provers • Shared benchmarks • Build your own verifier using Boogie • Hardest part in designing VCs: programming methodology that • Fits common programming idioms and • Can be handled well by automatic prover • Education • Teach using Spec# • Teach verification using BoogiePL • http://research.microsoft.com/specsharp DownloadSpec# and Boogiefrom here