HASKELL TO LOGIC THROUGH DENOTATIONAL SEMANTICS Dimitrios Vytiniotis, Koen Claessen, Simon Peyton Jones, Dan Rosén POPL 2013, January
Real programs contain assertions grep -i ASSERT./*hs./FamInst.lhs: = ASSERT( isAlgTyCon tycon )./Inst.lhs: ; wrap <- ASSERT( null rest && isSingleton theta )./TcCanonical.lhs: = ASSERT( tyConArity tc <= length tys )-- Type functions are saturated./TcCanonical.lhs: = ASSERT( not (isKind t1) && not (isKind t2) )./TcClassDcl.lhs: = ASSERT( ok_first_pred ) local_meth_ty./TcClassDcl.lhs: rho_ty = ASSERT( length sel_tyvars == length inst_tys )./TcDeriv.lhs: ASSERT( null sigs )./TcDeriv.lhs: = ASSERT2( equalLength rep_tc_tvs all_rep_tc_args, ppr cls ppr rep_tc )./TcDeriv.lhs: arg_ty <- ASSERT( isVanillaDataCon data_con )./TcEnv.lhs: -> ASSERT( lvl == lvl1 ) id./TcEnv.lhs: TopLevel -> ASSERT2( isEmptyVarSet id_tvs, ppr id $$ ppr (idType id) )./TcErrors.lhs: = ASSERT( isEmptyBag insols )./TcErrors.lhs: = ASSERT( not (null matches) )./TcErrors.lhs: = ASSERT( length matches > 1 )./TcEvidence.lhs: | otherwise = ASSERT( arity < n_tys )./TcEvidence.lhs:mkTcForAllCo tv (TcRefl ty) = ASSERT( isTyVar tv ) TcRefl (mkForAllTy tv ty)./TcEvidence.lhs:mkTcForAllCo tv co = ASSERT( isTyVar tv ) TcForAllCo tv co./TcEvidence.lhs:mkTcForAllCos tvs (TcRefl ty) = ASSERT( all isTyVar tvs ) TcRefl (mkForAllTys tvs ty)./TcEvidence.lhs:mkTcForAllCos tvs co = ASSERT( all isTyVar tvs ) foldr TcForAllCo co tvs./TcEvidence.lhs: = ASSERT (tc `hasKey` eqTyConKey)./TcEvidence.lhs: = ASSERT( equalLength tvs cos )./TcExpr.lhs: = ASSERT( not (isSigmaTy res_ty) )./TcExpr.lhs: = ASSERT( notNull upd_fld_names ) (from the GHC type checker) 2
This work Automated static verification of higher- order functional programs Tool works on subset of Haskell, uses GHC as frontend 3
Our setting Verify Haskell code: higher-order, lazy but pure Dont aim for high expressiveness, go for simple, easy-to-prove (e.g. structural) properties Automatically discharge all tedious but simple goals that a programmer has to manually and repeatedly check Re-use existing technology: Automated theorem provers (e.g. SMT solvers), model finders ACL2? Boogie? Prolog? Property-directed reachability? [Bjorner et al] No best solution yet. Our choice for this work 4
Programs and properties risers [] = [] risers [x] = [[x]] risers (x:y:ys) = case risers (y:ys) of [] -> error urk (s:ss) -> if x <= y then (x:s):ss else [x]:s:ss 1.can risers crash? 2.non-empty input non-empty result? Syntax of contracts (refinements more appropriate): C ::= {x | p} | (x:C1)->C2 | C1 && C2 | CF Just an ordinary Haskell expression of type Bool crash-free 5
Design module Foo f x y = … g x = … -- Prelude data [a] = [] | a : as data Bool = True | False … Functions over these… Haskell Source First Order Logic Formulae Unsatisfiable Contract holds! HALO translation to First Order Logic Z3/Equinox/E/ Vampire/Paradox Theorem Prover Satisfiable Probably contract doesnt hold but who knows Cant tell anything 6
Key idea: let denotational semantics guide us A λ/case-lifted language Continuous function space Distinguished one-element cpo Lifting 7
Interpreted as injection into the appropriate product 8
Function definitions become FOL axioms head (Cons x xs) = x head _ = error 9
data List a = Cons a (List a)| Nil 10
Higher-order functions head (Cons x xs) = x head _ = error double f x = f (f x) 11
Refinements denotationally and logically Logically Denotationally 12
Soundness via denotational semantics 13
Automating induction Currently support fixpoint induction Assume contract holds for uninterpreted function add_rec NB: A sound thing to do by admissibility of contracts 14
Admissibility and why it matters In Haskell, data types are not inductive. Hence your familiar induction principle is simply unsound! ones = 1 : ones f Z = [] f (S x) = 1 : f x Lemma: forall x. f x ones Proof: Holds for UNR Holds for Z Assume holds for x; then holds for (S x) Right? WRONG! Let: u = S u Then: f u = ones Logical inequality, not admissible! 15
Admissibility and induction Admissibility = If P is true for all elements of a chain, then true for the limit. Not all predicates are admissible Comes for-free! Base contracts are Haskell functions, and those are continuous! 16
Happily implemented on top of GHC API Z3 rocks for provable properties! Disclaimer: FOL axioms/problem Use of fixpoint induction 17
More features (and non-features) More features: Incremental verification Prove spec for g, use either the spec or definition of g, or both to prove other specifications … Some support for lemmas Mutual (automatic) fixpoint induction Primitive arithmetic constraints via SMT2 (in Z3) Experimental features: logical equality, finite unfoldings Not there: Pre/post inference, strengthening of IH Support for counterexamples (see next slide) 18
Whats next: counterexamples 19 Unprovable contracts (because theyre false or were incomplete) Paradox Equinox Z3 Vampire E-prover AnyMorphism.big_sat_app_any_morphism_fail_step P:---- X:---- Z:---- V:---- E:---- Loop.sat_id_loop_pred P:0.00 X:0.01 Z:0.01 V:---- E:0.01 Loop.sat_id_recursive_true P:---- X:---- Z:---- V:---- E:0.01 PredLog.sat_concatMap_cf_missing_step P:---- X:---- Z:---- V:---- E:---- PredLog.sat_concatMap_retains_missing_step P:---- X:---- Z:---- V:---- E:---- PredLog.sat_flattenAnd_cf_missing_step P:---- X:---- Z:---- V:---- E:---- PredLog.sat_flattenAnd_retains_missing_step P:---- X:---- Z:---- V:---- E: Recursion.sat_qfac_cf_broken_step P:---- X:---- Z:---- V:---- E:---- Recursion.sat_rev_cf_broken_step P:---- X:---- Z:---- V:---- E:---- Risers.big_sat_risersBy_nonEmpty_broken2_step P:---- X:---- Z:---- V:---- E:---- Risers.big_sat_risersBy_nonEmpty_broken_step P:---- X:---- Z:---- V:---- E:---- Risers.sat_risers_broken2_step P:---- X:---- Z:---- V:---- E:---- Risers.sat_risers_broken3_step P:---- X:---- Z:---- V:---- E:---- Risers.sat_risers_broken_step P:---- X:---- Z:---- V:---- E:---- Risers.sat_risers_missing_le_step P:---- X:---- Z:---- V:---- E:---- Shrink.big_sat_shrink_lazy_step P:---- X:---- Z:---- V:---- E:---- Timeouts … We now know why, and how to address this: stay tuned
Whats next: usability Proving is reasonably fast, now explore: Automatic strengthening of induction hypotheses Pretty printing models as counterexamples More induction principles Testing in larger scale Interfacing with theorem provers for manual proofs? 20 Lots of man-hours needed, come help please!
Related work Liquid Types [Jhala et al] Predicate abstraction Inference Quantifiers driven by type system ESC/Haskell [Xu et al] Contracts are programs Symbolic execution/inlining Zeno [Sonnex et al] Automated equality proofs Clever heuristics Strict semantics Catch [Mitchell] Pattern match errors Via dataflow analysis Dafny & Boogie [Leino et al], ACL2 Leon [Suter et al] Specialized decision procedure for FP Good for first-order F7/F* [Swamy et al] Hoare logic for FP [Regis-Gianas & Pottier] HO logics CBV *really* helps HO model checking, MoChi [Kobayashi et al] Specialized decision procedures Lots of techniques stacked Good for inference, good for counterexamples Symbolic execution-based [ Tobin- Hochstadt and Van Horn][Xu] Abstraction, lots of smaller queries to theorem prover HOLCF-based verification [Huffman] Reasoning in a very rich logic that contains formalization of a domain theory More sophisticated axiomatization, ability to reason about parametricity and monad laws
What we did and what I learnt Weve given a semantic basis for the verification of Haskell programs We demonstrated that it is implementable 22 We can verify FP in a simple and robust way: For this particular case a simple solution seems to do the job. It appears affordable to use a very precise abstraction of your program and trust your 2013 theorem proving technology Thank you for your attention!