320 likes | 459 Views
M odel-based reasoning meets code verification. Michael Butler 21 May 2014 WG 2.3 Meeting 55, Orlando. Talk is about. Distinguish algorithm from its implementation Use of patterns / ghost elements for Verification of algorithm wrt its spec independently from its implementation
E N D
Model-based reasoning meetscode verification Michael Butler 21 May 2014 WG 2.3 Meeting 55, Orlando
Talk is about • Distinguish algorithm from its implementation • Use of patterns / ghost elements for • Verification of algorithm wrt its spec independently from its implementation • Verification of implementation wrt algorithm • Mechanised verification by linking existing verification systems • Dafny & Event-B/Rodin
Model of a transition system SETS STATE CONSTANTSinit, tr, inv, reach tr∈ STATE ↔ STATE// transition relation inv ⊆ STATE // states satisfying invariant init⊆ inv // initial states (satisfy inv) reach = tr*[init] // set of reachable states
Algorithm Specification IF reach ⊆inv THEN result := SAFE ELSE result := UNSAFE END
Representing the transition system in Dafny class STATE {}; method initStatesreturns ( res: seq<STATE> ) ensures res == initial states of system; methodget_successors( s:STATE ) returns ( succ: seq<STATE> ) ensuressucc== “all successors of s” methodcheck_inv(s: STATE) returns( ok:bool ) ensures ok == “s satisfies invariant”
Implementation of algorithm methodCheckReach() returns( pass: bool ) { var safe : seq<STATE>:= initStates() ; var err: seq<STATE>:= [] ; vari:= 0 ; while ( i < |safe| && err == []) { varj := 0; s1 := safe[i]; succ:= get_successors(s1); while ( j < |succ| && err == [] ) { var s2 := succ[j]; if ( s2 !in safe && s2 !in err ) { if( check_inv(s2) ) {safe := safe + [s2]; } else{ err := err + [s2]; } } ; j := j + 1; } ; i := i + 1; } ; if ( err == [] ) { pass := true; } else { pass := false; } }
Search algorithm pattern • FindOk: ∃x∈S •P(x) or • NoFind: ∀x∈S• ¬P(x) Search xor FindOk NoFind StartFind StartFind Fail(i) FindOk Pass(i) NoFind some i all i
Could merge to form a (less abstract) sequential algorithm StartFind; for iin S do Fail(i) [] Pass(i) ; exit od; if exit then FindOkelse NoFindfi
Alternatively refine to parallel model FindOk • Partition S so that search is farmed out to multiple processors p ∈ P • This is a simple refinement step in Event-B StartFind FindOk Pass(p,i) some i:S[p] some p:P
What’s in a refinement pattern? • Determining the refined events is relatively easy once we decide on the appropriate abstract program structure to use • But determining the right gluing invariants can be difficult • Patterns: from a certain specification structure • determine refined events • gluing invariants • convergence variants
“All Condition” Pattern CondAll≙ when ∀i∈ S •Cond(i) then action end Search Fail: Cond(i) ≙ ¬Property(i)
Refinement of CondAll Step ≙ anyiwhere Cond(i) ∧ s ∉ oStep then oStep := oStep ∪ {i} end CondAll≙ when oStep = S then action end CondAll alli:S Step(i) CondAll i ∈ oStep iff Step(i) event has occurred invariant ∀i∈ oStep • Cond(i)
“Some Condition” Pattern CondSome≙ when ∃i . i∈ S ∧ Cond(i) then action end CondSome≙ any iwhere i∈ S Cond(i) then action(i) end
Refinement of CondSome Step ≙ anyiwhere Cond(i) ∧ s ∉ oStep then oStep := oStep ∪ {i} end CondSome≙ any iwhere i∈ oStep then action(i) end CondSome somei:S Step(i) CondSome invariant ∀i∈ oStep• Cond(i)
Refinement invariants for search • Invariant: oPass ⊆ S∩P • Invariant: oFail ⊆ S \ P Search xor FindOk NoFind StartFind StartFind Fail(i) FindOk Pass(i) NoFind some i all i
Convergence variant for search • Variant:S \ ( oPass ∪ oFail ) • Invariant: finite(S) Search xor FindOk NoFind StartFind StartFind Fail(i) FindOk Pass(i) NoFind some i all i
Event-B spec of reachability Pass ≙ WHEN reach ⊆inv THEN result := SAFE END Fail ≙ ANY e WHERE e ∈ reach e ∉inv THEN result := UNSAFE error_state := e END • reachis a ghost constant and it needs to be removed in the implementation • Refine the model to one that computes the reachable states step-by-step
Guards for reachability events • Fail: ∃s∈reach •¬Inv(s) or • Pass: ∀s∈reach • Inv(s) Reach xor Fail Pass Start Start Ok(s) Fail Error(s) Pass Some s All s
Invariants for reachability refinement • Invariant: oSafe⊆ reach ∩ inv • Invariant: oError ⊆ reach \ inv Reach xor Fail Pass Start Start Safe(s) Fail Error(s) Pass Some s All s
Spec of Safe event EventSafe // add a new successor state that satisfies invariant to oSafe ANY s WHERE s ∈ reach s ∈ inv s ∉ oSafe THEN oSafe := oSafe∪ {s} END
Convergence of Safe and Error events • Variant: S \ ( oSafe ∪oFail ) • Invariant: finite(S)
Refining the Fail event AbstractFail ≙ ANY e WHERE e ∈ reach e ∉inv THEN result := FAIL error_state := e END Refined Fail ≙ ANY e WHERE e ∈oError THEN result := FAIL error_state := e END Invariant: oError ⊆ reach ∖ inv Prove guard strengthening refined guard = e ∈oError ⇒ “invariant” e ∈reach ∖ inv = abstract guard
Refining the Pass event Abstract Pass ≙ WHEN reach ⊆inv THEN result := OK END Refined Pass ≙ WHEN oSafe = reach THEN result := OK END Invariant: oSafe ⊆ reach ∩ inv Prove guard strengthening oSafe= reach ⇒ “invariant” reach ⊆ reach ∩ inv ⇒ “set theory” reach ⊆ inv
The specter of reach remains • Fail events do not refer to reach so no further refinement required • Pass events still do refer to reach, so further refinement required • Refine the guard of the Safe(s) event: s ∈ reach ⇐ “reach is a fixed point” ∃s1 • s1∈ reach ∧ s1↦s∈tr ⇐“oSafe ⊆ reach” ∃s1 • s1∈ oSafe ∧ s1↦s∈tr
Add new parameter to Safe event EventSafe ANY s, s1 WHERE s1 ∈ oSafe s1↦s∈tr s ∈ inv s ∉ oSafe THEN oSafe := oSafe∪ {s} END
Extra parameter gives nested iteration Pass Pass all s1 alls alls Safe(s) Pass Safe(s1,s) Pass
Further refinement: tracking the explored states • A state is explored if it is in safe and all its successors are reached • Invariant: tr[ explored ] ⊆ ( oSafe ∪ oError ) • Introduce new event: EventMark_explored ANY s WHERE s ∈ rac s ∉ explored tr[{s}] ⊆ ( oSafe ∪ oError) THEN explored:= explored∪ {s} END
Refining the Pass event Pass ≙ WHEN oSafe = reach THEN result := OK END Refined Pass ≙ WHEN explored = oSafe oError = {} THEN result := OK END Invariant: tr[ explored ] ⊆ ( oSafe ∪ oError ) Prove guard strengthening explored = oSafe ∧ oError = {} ⇒ “invariant” tr[ oSafe ] ⊆ oSafe ⇒“reach is least fixed point” reach ⊆ oSafe
What to verify in Dafny? • Verify that the Dafny code is a correct refinement of the final Event-B model • Event-B variables become ghost variables in Dafny • Gluing invariants in Dafny: invariant oSafe == seqElems(safe); invariant oError == seqElems(err); invariant explored == seqElems(safe[0..i]); • Embed the events into the Dafny code
Embed events in the Dafny methodCheckReach() returns( pass: bool ) { … Init while ( i < |safe| && err == []) { varj := 0; s1 := safe[i]; succ:= get_successors(s1); while ( j < |succ| && err == [] ) { var s2 := succ[j]; if ( s2 !in safe && s2 !in err ) { if( check_inv(s2) ) {safe := safe + [s2]; Safe(s1,s2); }; else{ err := err + [s2]; Error(s2); } } ; j := j + 1; } ; Mark_Explored ; i := i + 1; } ; if ( err == [] ) { pass := true; Pass ; } else { pass := false; Fail ;} }
Ghost busters • Guarded event: when G then A end == (assume G) ; A • Eliminating guards with assertions (assert G) ; (assume G) ; A ⊑ A • Once guards are removed, ghost variables no longer influence the program so can be eliminated
Concluding • Correctness of algorithm is proved independently of Dafny (with help from patterns) • At Dafny level we prove that the code is a correct implementation of the algorithm • Avoided any duplication in proof except for Dafny variants • NB: proposed “embeddding” method for proving Dafny refines the algorithm only works if code is deterministic