380 likes | 541 Views
Program verification with Hoare Type Theory. Aleks Nanevski Microsoft Research, Cambridge Joint with Greg Morrisett, Lars Birkedal, Amal Ahmed, Rasmus Petersen Dagstuhl, February 2008. How to design a programming language from scratch with verification in mind?.
E N D
Program verification with Hoare Type Theory Aleks Nanevski Microsoft Research, Cambridge Joint with Greg Morrisett, Lars Birkedal, Amal Ahmed, Rasmus Petersen Dagstuhl, February 2008
How to design a programming language from scratch with verification in mind? • Simply-typed languages have been remarkably successful in preventing a class of programming errors. • But still resist the attempts at specification and verification of deeper semantic properties. • index-out-of-bounds • division-by-zero • invariants on mutable state • or almost anything involving effects. • What are the properties that make types work? • Can we harness these properties to help with specification and verification?
Two foundational approaches to program specification and verification • Hoare Logic • targets imperative, usually untyped, first-order languages • recent extensions to simply-typed functional languages [Honda’05],[Krishnaswami’06],[Birkedal’05] • Dependent type theory • targetspure higher-order lambda calculus • types may capture deep semantic properties of data • integer is even, list has 5 elements, etc. • I want to argue that we essentially want a combination of both.
Starting point: type-and-effect systems • Refine types with effect annotation. • Usually, annotations drawn from some finite set of labels.
But let’s be more semantic… • Draw effect annotations from logic. • y > 0 is a precondition that must be proved before running div x y. • We will also require postconditions, like in Hoare logic • And proofs!
Which logic to use for the annotations? • We want to specify many things: • practical data structures (e.g., hash-tables). • higher-order functions, polymorphism. • dynamically-generated pointers, aliasing, state ownership • recursion, jumps, IO, concurrency. • Thus, the logic better be very expressive. • we won’t exactly be proving Fermat’s Last Theorem, but we will need a lot of math. • Type theory (like Coq) seems perfect. • higher-order logic with proofs. • higher-orderness will come very handy for data abstraction and information hiding. • But need to reconcile Coq with effects.
Hoare Type Theory (HTT) • We introduce into Coq a type corresponding to specs in Hoare Logic. • so that pre/post-conditions can appear in negative positions. • Specifications-as-types principle. • Hoare type stands for • stateful programs (possibly diverging) with • precondition P • postcondition Q • result type A
Hoare Type Theory (cont’d) • Benefits from an interplay of important PL ideas: • verification by wp’s and sp’s • Curry-Howard isomorphism • monads as in Haskell • Separation Logic • Provably modular: • components can be specified and checked in isolation. • Prototype under construction as extension of Coq.
Type theories are unsound if effects are added naively • Propositions like (10 < 0) are types. • Effectful programs can often be given any type: • divergence via infinite recursion • exceptions • mutable state • IO • concurrency • An effectful program can prove that (10 < 0)! • Hence, leading to inconsistency awkward squad from Haskell
A solution: Monads • Like in Haskell, distinguish purity with types • pure fragment – the underlying type theory • e : nat • e is an integer value • e : ST nat • e is delayed effectful computation. • when executed, it may change the state and diverge. • but since it is delayed, it is actually considered pure. • hence, can safely appear in types, predicates, proofs. • e : ST (10 < 0) • a computation which must diverge when executed.
Refine the monad to capture effectful behavior • Hoare type is a “dependent” monad • parametrized by pre- and post-condition • Formation rule • ST{P}x:A{Q} : Type if • P : heap Prop • A : Type • x:A |- Q : heap heap Prop, where heap = loc option( a:Type. a), and loc = nat. • Note: postcondition is binary relation on heaps. Variant of VDM notation (goes back to I.J. Good).
Example: specify function that increments location contents and returns old value • where is true if x points to v:A in h. • Note: before running inc x, must prove that x stores a nat. • because x may store a value of some other type. • because x may be a dangling pointer.
Implementation of inc in Haskell-style do-notation. • HTT implementation typechecks inc as follows: • Compute P,Q=weakest pre/strongest post for the do-block • Then emit obligation to prove the consequence:
Primitive commands: lookup and update • Memory read • (Strong) Memory update
Primitive commands: allocation and deallocation • Memory allocation • Memory deallocation
Primitive commands: fixpoints • Giving p and q in type of fix corresponds to giving loop invariants in Hoare Logic
Monadic primitives: unit • Roughly, corresponds to Hoare Logic rule for assigning to variable r.
Monadic primitives: bind • Rule of sequential composition (but higher-order) • Note: quantifications over pre/posts and heaps is essential for obtaining tightest specs.
Monadic primitives: Haskell-style do • Rule of consequence • Interesting fact: “do” is not ordinary coercion • it is an introduction form for Hoare type • bind is corresponding elimination • No need for other structural rules of Hoare logic • no rule for auxiliary variables, or conjunction rule
Example: counter • Allocate a private location x • Export function that increments x • Executing fcounter; x0f; x1f; x2f will bind 0,1,2 to x0,x1,x2, respectively. • What is the spec for counter?
A specification with nested Hoare types • Problem: x is out of scope in return type.
Hide private state by existential abstraction • Introduce invariant into code to hide how count is kept. • Another problem: • fst(f) 0 h states (x0) h, but we lost connection with i • We will need Separation Logic to handle this.
Separation logic adds two new things: • Separating conjunction (easily definable in HTT): (P * Q) holds of heap h iff P and Q hold of disjoint parts of h • Frame rule of inference: If then • Can we add Frame rule to HTT? How to prove it is sound?
Employ a type-theoretic idea to expedite… • Impose that well-typed programs must satisfy Frame! • Define new monad STsep, over ST: • Then re-type the stateful commands, using rule of consequence.
Programs remain the same, but specs become much simpler • Example: allocation • empty subheap is consumed and replaced by rv • Hence r must be fresh
STsep monad correctly handles private state • Now (fst f) 0 replaces empty from the precondition. • Meaning: initial heap is extended with x0
Weakest pre and strongest post precisely capture the semantics of a program. • Problem: these may not be easy to read! • Remember the example 3-line program:
Here is the computed tightest spec for inc, in Coq syntax. inc : forall x : loc, ST (fun i : heap => (fun i0 : heap => exists v : nat, ptsto x v i0) i /\ (forall (x0 : nat) (m : heap), (fun (y : nat) (i0 m0 : heap) => m0 = i0 /\ ptsto x y i0) x0 i m -> (fun (xv : nat) (i0 : heap) => (fun i1 : heap => exists B : Type, exists w : B, ptsto x w i1) i0 /\ (forall (x1 : unit) (m0 : heap), (fun (_ : unit) (i1 m1 : heap) => m1 = update x (xv + 1) i1) x1 i0 m0 -> (fun (_ : unit) (_ : heap) => True) x1 m0)) x0 m)) (fun (y : nat) (i m : heap) => exists x0 : nat, exists h : heap, (fun (y0 : nat) (i0 m0 : heap) => m0 = i0 /\ ptsto x y0 i0) x0 i h /\ (fun (xv y0 : nat) (i0 m0 : heap) => exists x1 : unit, exists h0 : heap, (fun (_ : unit) (i1 m1 : heap) => m1 = update x (xv + 1) i1) x1 i0 h0 /\ (fun (_ : unit) (r : nat) (i1 f : heap) => r = xv /\ f = i1) x1 y0 h0 m0) x0 y h m)
Luckily, the spec has a lot of structure! • It literally represents the program as a predicate. • We apply the proving strategy from Hoare Logic: • symbolically evaluate the program, one step at a time. • at each step, discharge the verification condition that enables the next evaluation step. • With a twist: Evaluation/VC-generation can be implemented as a set of lemmas. • proving the lemmas verifies the VC-gen implementation.
Example lemma for symbolic evaluation (in Coq syntax) • If program starts with a read from location x: • first prove that x is initialized (ptsto x v i) • then proceed to prove the spec of the continuation. • Other lemmas similar (evals_bind_write, evals_bind_new…) • Applicable lemma can be determined by a tactic. Lemma evals_bind_read : forall (A B : Type) (x : loc) (v : A) (p2 : A -> heap -> Prop) (q2 : A -> B -> heap -> heap -> Prop) (i : heap) (q : B -> heap -> Prop), ptsto x v i -> (p2 v i /\ forall y m, q2 v y i m -> q y m) -> (bind_pre (read_pre A x) (read_post A x) p2 i /\ forall y m, (bind_post (read_pre A x) (read_post A x) p2 q2 y i m -> q y m.
Meta-theoretic properties:soundness, compositionality, equations
Verification reduces to typechecking • Theorem: If e:ST{P}r:A{Q}, then e evaluates as expected. • Proved via Preservation and Progress lemmas. • but much more demanding! • Preservation: evaluation preserves types, normal forms, and postconditions. • e.g: if e:ST{T}r:int{r = 55} then e does produce 55. • Progress demands soundness of assertion logic • requires a denotational model for HTT [Petersen,Birkedal’08].
Verification is syntax directed • Program properties independent of context. • No need for whole program reasoning. • Hence, a program is a proof of its spec: • in the pure case, by Curry-Howard. • in the impure case, by weakest pre/strongest post. • Formal statements of compositionality • In the pure case, substitution principles. • In the impure case, Hoare’s rule of composition.
Summary • HTT types = specifications in the style of Hoare and Separation logic. • generalizes monadic type-and-effect systems, but effect annotations are logical predicates over heaps. • HTT programs = proofs of their own correctness. • leading to compositional verification. • we are currently building libraries for basic data structures. • Verification methodology is itself implemented and verified inside the theory as a set of lemmas.
Related work • Extended static checking: • ESC/Java, JML, Spec#, SPlint, Cyclone, Sage • Hoare-like annotations verified during typechecking. • Restrictive strategies for dealing with undecidability • Dependent types and effects • [Augustson’98],[Mandelbaum’03],[Zhu,Xi’05],[Shao’05], [Sheard’05],[Westbrook’06],[Taha’07],[Condit’07]. • Programs and specs cannot share pure code (phase separation) • Hoare Logics for higher-order functions: • [Schoeder’02],[Honda’05],[Krishnaswami’06],[Birkedal’04] • Simply-typed underlying languages (with effects) • Hoare triples do not integrate into types.