k. rustan m. leino microsoft research, redmond, wa, usa 19 july 2007 invited talk, cade-21 bremen,...
TRANSCRIPT
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