Deductive Program Verification with Why3 Jean-Christophe Filliâtre CNRS Mathematical Structures of Computation Formal Proof, Symbolic Computation and Computer Arithmetic Lyon, February 2014
definition program + specification verification conditions proof
this is not new A. M. Turing. Checking a large routine. 1949. STOP r = 1 u = 1 v = u TEST r n s = 1 u = u + v s = s + 1 r = r + 1 TEST s r
this is not new Tony Hoare. Proof of a program: FIND. Commun. ACM, 1971. k v v v
which programs? which specs? program + specification verification conditions proof programs pseudo code / mainstream languages / DSL small / large specs safety, i.e. the program does not crash absence of arithmetic overflow complex behavioral property, e.g. sorts an array
which logic? program + specification verification conditions proof too rich: we won t be able to automate proofs too poor: we can t model programming languages and we can t specify programs typically, a compromise e.g. first-order logic + equality + arithmetic
what about proofs? program + specification verification conditions proof a gift: theorem provers proof assistants: Coq, PVS, Isabelle, etc. TPTP provers: Vampire, Eprover, SPASS, etc. SMT solvers: CVC3, Z3, Yices, Alt-Ergo, etc. dedicated provers
checking a large routine (Turing, 1949) STOP r = 1 u = 1 v = u TEST r n s = 1 u = u + v s = s + 1 r = r + 1 TEST s r
checking a large routine (Turing, 1949) STOP r = 1 u = 1 v = u TEST r n s = 1 u = u + v s = s + 1 r = r + 1 TEST s r u 1 for r = 0 to n 1 do v u for s = 1 to r do u u + v
checking a large routine (Turing, 1949) STOP r = 1 u = 1 v = u TEST r n s = 1 u = u + v s = s + 1 r = r + 1 TEST s r requires {n 0} u 1 for r = 0 to n 1 do v u for s = 1 to r do u u + v ensures {u = fact(n)}
checking a large routine (Turing, 1949) STOP r = 1 u = 1 v = u TEST r n s = 1 u = u + v s = s + 1 r = r + 1 TEST s r requires {n 0} u 1 for r = 0 to n 1 do v u for s = 1 to r do u u + v ensures {u = fact(n)} invariant {u = fact(r)} invariant {u = s fact(r)}
verification condition function fact(int) : int axiom fact0: fact(0) = 1 axiom factn: n:int. n 1 fact(n) = n * fact(n-1) goal vc: n:int. n 0 (0 > n - 1 1 = fact(n)) (0 n - 1 1 = fact(0) ( u:int. ( r:int. 0 r r n - 1 u = fact(r) (1 > r u = fact(r + 1)) (1 r u = 1 * fact(r) ( u1:int. ( s:int. 1 s s r u1 = s * fact(r) ( u2:int. u2 = u1 + u u2 = (s + 1) * fact(r))) (u1 = (r + 1) * fact(r) u1 = fact(r + 1))))) (u = fact((n - 1) + 1) u = fact(n))))
verification condition function fact(int) : int axiom fact0: fact(0) = 1 goal vc: n:int. n 0 (0 > n - 1 1 = fact(n))
verification condition function fact(int) : int axiom factn: n:int. n 1 fact(n) = n * fact(n-1) goal vc: n:int. n 0 (0 n - 1 ( u:int. ( r:int. 0 r r n - 1 u = fact(r) (1 r ( u1:int. (u1 = (r + 1) * fact(r) u1 = fact(r + 1)))))
the SMT revolution SMT means Satisfiability Modulo Theories an SMT solver combines + SAT + Equality + Arith +... e.g. n 0 0 > n 1 (Arith) n = 0 1 = fact(n) fact(0) = 1 (Ax) (Equality)
extracting verification conditions a well-known technique (weakest preconditions, Dijkstra 1971, Barnett/Leino 2005) yet doing it for a realistic programming language is a lot of work
extracting verification conditions a well-known technique (weakest preconditions, Dijkstra 1971, Barnett/Leino 2005) yet doing it for a realistic programming language is a lot of work as in a compiler, we rather design an intermediate language from which we extract VCs two examples: Boogie (Microsoft Research) Why3 (Univ. Paris Sud / Inria)
Why3 in a nutshell a simple programming language, with polymorphism pattern-matching exceptions mutable data structures, with controlled aliasing a polymorphic first-order logic, with algebraic data types recursive definitions inductive and coinductive predicates file.why file.mlw WhyML VCgen Why transform/translate print/run http://why3.lri.fr/ Coq Alt-Ergo CVC3 Z3 etc.
1. a taste of program verification
election given a multiset of N votes A A A C C B B C C C B C C determine the majority, if any
an elegant solution due to Boyer & Moore (1980) linear time constant extra space
principle A A A C C B B C C C B C C cand = A k = 1
principle A A A C C B B C C C B C C cand = A k = 2
principle A A A C C B B C C C B C C cand = A k = 3
principle A A A C C B B C C C B C C cand = A k = 2
principle A A A C C B B C C C B C C cand = A k = 1
principle A A A C C B B C C C B C C cand = A k = 0
principle A A A C C B B C C C B C C cand = B k = 1
principle A A A C C B B C C C B C C cand = B k = 0
principle A A A C C B B C C C B C C cand = C k = 1
principle A A A C C B B C C C B C C cand = C k = 2
principle A A A C C B B C C C B C C cand = C k = 1
principle A A A C C B B C C C B C C cand = C k = 2
principle A A A C C B B C C C B C C cand = C k = 3
principle A A A C C B B C C C B C C cand = C k = 3 then we check if C indeed has majority, with a second pass (in that case, it has: 7 > 13/2)
Fortran code
let mjrty (a: array candidate) : candidate = let n = length a in let cand = ref a[0] in let k = ref 0 in for i = 0 to n-1 do if!k = 0 then begin cand := a[i]; k := 1 end else if!cand = a[i] then incr k else decr k done; if!k = 0 then raise Not found; try if 2 *!k > n then raise Found; k := 0; for i = 0 to n-1 do if a[i] =!cand then begin incr k; if 2 *!k > n then raise Found end done; raise Not found with Found!cand end Why3 code
demo
specification precondition let mjrty (a: array candidate) requires { 1 length a } postcondition in case of success ensures { 2 * numof a result 0 (length a) > length a } postcondition in case of failure raises { Not found c: candidate. 2 * numof a c 0 (length a) length a }
loop invariants first loop for i = 0 to n-1 do invariant { 0!k numof a!cand 0 i } invariant { 2 * (numof a!cand 0 i -!k) i -!k } invariant { c: candidate. c!cand 2 * numof a c 0 i i -!k }... second loop for i = 0 to n-1 do invariant {!k = numof a!cand 0 i } invariant { 2 *!k n }...
proof the verification condition expresses safety array access within bounds termination validity of annotations invariants are initialized and preserved postconditions are established automatically discharged by SMT solvers
2. one logic to use them all
using theorem provers there are many theorem provers SMT solvers: Alt-Ergo, Z3, CVC3, Yices, etc. TPTP provers: Vampire, Eprover, SPASS, etc. proof assistants: Coq, PVS, Isabelle, etc. dedicated provers, e.g. Gappa (Guillaume s talk on Thursday) we want to use all of them if possible we make a compromise
in a nutshell logic of Why3 = polymorphic first-order logic, with (mutually) recursive algebraic data types (mutually) recursive function/predicate symbols (mutually) inductive predicates let-in, match-with, if-then-else more details: Expressing Polymorphic Types in a Many-Sorted Language (FroCos 2011) One Logic To Use Them All (CADE 2013)
declarations types abstract: type t alias: type t = list int algebraic: type list α = Nil Cons α (list α) function / predicate uninterpreted: function f int : int defined: predicate non empty (l: list α) = l Nil inductive predicate inductive trans t t =... axiom / lemma / goal goal G: x: int. x 0 x*x 0
theories logic declarations organized in theories theory end theories can be reused e.g. the theory of integers theory end generic theories can be instantiated e.g. notion of path theory end
under the hood a technology to talk to provers central concept: task a context (a list of declarations) a goal (a formula) goal
workflow theory end Alt-Ergo theory end Z3 theory Vampire end
workflow theory end Alt-Ergo theory end goal Z3 theory Vampire end
workflow theory end Alt-Ergo theory end goal T 1 goal Z3 theory Vampire end
workflow theory end Alt-Ergo theory end T 1 T 2 goal goal goal Z3 theory Vampire end
workflow theory end Alt-Ergo theory end T 1 T 2 P goal goal goal Z3 theory Vampire end
transformations eliminate algebraic data types and match-with eliminate inductive predicates eliminate if-then-else, let-in encode polymorphism, encode types etc. efficient: results of transformations are memoized
driver a task journey is driven by a file transformations to apply prover s input format syntax predefined symbols / axioms prover s diagnostic messages more details: Why3: Shepherd your herd of provers (Boogie 2011)
example: Z3 driver (excerpt) printer "smtv2" valid "^unsat" invalid "^sat" transformation "inline trivial" transformation "eliminate builtin" transformation "eliminate definition" transformation "eliminate inductive" transformation "eliminate algebraic" transformation "simplify formula" transformation "discriminate" transformation "encoding smt" prelude "(set-logic AUFNIRA)" theory BuiltIn syntax type int "Int" syntax type real "Real" syntax predicate (=) "(= %1 %2)" meta "encoding : kept" type int end
proof sessions proofs are stored into an XML file and read/written/updated by various tools why3ide why3replayer yes/no OCaml API proof session why3session L A TEX HTML... more details: Preserving User Proofs Across Specification Changes (VSTTE 2013)
3. to do what?
option 1 Why3 as a front-end to many theorem provers saves you a lot of work currently supports Coq, PVS, Isabelle Alt-Ergo, CVC3, CVC4, Simplify, Yices, Yices2, Z3 E, iprover, SPASS, Vampire, Zenon Gappa, Mathematica through files or via the OCaml API
example Why3 comes with a Coq tactic to call external ATPs as oracles Coq let why3 p gl =... Why3... generalize (h6 (i+1)%z h). why3 "alt-ergo"....
option 2 Why3 as a programming language to prove algorithms more than 80 examples in our gallery, e.g. Knuth-Morris-Pratt algorithm Bellman-Ford algorithm Red-black trees etc. automatically translated to OCaml code
option 3 Why3 as an intermediate language, to verify programs written in other, more complex programming languages Java programs: Krakatoa (Marché Paulin Urbain) C programs: Frama-C plug-ins Jessie (Marché), WP (Correnson) Ada programs: GNATprove (Adacore) probabilistic programs: EasyCrypt (Barthe et al.)
various ways of using Why3 Ada Java C prob. pgms GNATprove Krakatoa Frama-C Jessie WP Easycrypt Why3 WhyML logic proof assistants SMT solvers ATP systems other provers
how to tackle a realistic programming language you have to model most aspects pointers objects dynamic allocation arithmetic etc.
example 1: modeling the heap to verify programs with arbitrary aliasing, you have to come up with a memory model type loc type value =... type state = map loc value... this is what is done for C, Java, Ada, etc.
example 2: modeling integer arithmetic type int32 function to int (n: int32) : int constant min : int = - 0x8000 0000 constant max : int = 0x7fff ffff predicate in bounds (n: int) = min n max axiom to int in bounds: n: int32. in bounds (to int n) val of int (n: int) : int32 requires { in bounds n } ensures { to int result = n } val (+) (a b: int32) : int32 requires { in bounds (to int a + to int b) } ensures { to int result = to int a + to int b }
example 3: modeling floating-point arithmetic following the IEEE-754 standard type mode = NearestTiesToEven ToZero Up Down NearestTiesToAway function round mode real : real... type single function value single: real... type class = Finite Infinite NaN function class single : class... mode details: Sylvie s talk on Tuesday
conclusion
future work to simultaneously increase proof automation e.g. automatic induction enrich the specification logic e.g. higher-order logic support more programming constructs e.g. higher-order functions e.g. module subtyping
more details at http://why3.lri.fr/ source code under LGPL related publications more than 80 examples documentation, lecture notes