Tachio Terauchi Nagoya University
description
Transcript of Tachio Terauchi Nagoya University
Relatively Complete Verification of Higher-Order Programs
(via Automated Refinement Type Inference)
Tachio TerauchiNagoya University
Verifying higher-order functional programs
let rec F x c = c x inand G x y = assert x+1 ¸ y; F (y+1) (G y) inand Main w = F (w+1) (G w)
Show : 8w:int. Main w is assertion safe
• Refinement types approach:• F : x:int -> c:({u|u ¸ x} -> *) -> *• G : x:int -> y:{u|x+1 ¸ y} -> *• Main: w:int -> *
Much recent work on automated inference• Liquid types [Rondon, Kawaguchi, Jhala PLDI’08]• Dependent type inference with interpolants [Unno,
Kobayashi PPDP’09]• Dependent types from counterexamples [Terauchi POPL’10]• Predicate abstraction and CEGAR for higher-order model
checking [Kobayashi, Sato, Unno PLDI’11]• HMC: Verifying functional programs using abstract
interpreters [Jhala, Majumdar, Rybalchenko CAV’11]• “?” [Jagannathan 2011]• Leverages advances in first-order program
verification: – predicate abstraction, interpolation, CEGAR, SMT solvers,
Test generation, etc.• “Model Checkers” for higher-order programs
Incomplete
Completeness and Incompleteness
• Def: Refinement type system is sound iff– If a program is typable then it is safe.
• Def: Refinement type system is complete iff– A program is typable iff it is safe.
• Existing refinement type systems are sound, but incomplete– Different from incompleteness of inference algorithms
Relative Completeness
• Higher-order program verification is undecidable– Because it contains 1st-order program verification
• Complete verification for 1st-ord programs– E.g., Hoare logic– Relative to expressive 1st-ord theory like PA
• Allow arbitrary PA formulas as refinement predicates? i.e., { u | µ } µ 2 PA– Sufficient for ord-1 programs– Not for general higher-order programs
Refinement types incompleteness in detail• Problem: Higher-order functions
• A typical relative completeness proof of Hoare logic– wpre(x = e, µ) = µ[x/e]– wpre(s1;s2, µ) = wpre(s1,wpre(s2, µ)) , …– Show the logic (PA) can express wpre(…)– Then, s is safe iff wpre(s,true) = true.
• What’s wpre(f x, µ) where f : int -> * and x : int? • Or wpre(g f, µ) with g : (int -> *) -> * and f : (int -
> *)?
Weakest pre. of higher-order terms
• Ideally: – wpre(f x, true) = “f x runs safe”– Ex. F f x = f x
• F : f:(int->*)->x:{ u : int | “f u runs safe” }->*– But, ref. pred. inference becomes higher-order
program verification!– To prevent circularity, we only allow 1st-ord formulas
• I don’t have relative completeness for all higer-order programs yet
• I will show: How to get relative completeness for a class that haven’t been covered previously
Closure Boudedness
• Def: Size of a closure is the number of base-type values captured in the closure
• Def: A program is closure-bounded if its evaluation only generates closures of bounded size
• Def: Closure pattern is a closure with base-type values abstracted– E.g., F (G ®) ® represent infinite number of
closures F (G 0) 0, F (G 0) 1, F (G 1) -1, …– In lambda, ¸x.(y (¸z.z ®) ®), etc.
• Lemma: Closure-bounded iff finite # of patterns
Contribution
• Relative completeness for closure-bounded programs
• High-level Idea : Environment Passing F c f = c f G x k = k x H x y = assert x · y; F (G y+1) (H y)Main w = F (G w+1) (H w)
Parametrize type of F with G ®, H ®
F : 8a1.8a2.c:(({u|µ1}->*)->*)->f:({u|µ2} ->*) ->*
Intersection types to handle different contexts
(this example needs none)
Show: 8w:int. Main w as safe
Details
• Naïve Approach :– Symbolically evaluate the program for n steps
and record closure patterns seen– Build type shapes from the patterns– Check if typable restricted to the type shapes– If not typable, increase n and repeat– (In parallel) check for unsafety
Symbolic Eval => Closure Patterns
• Patterns A := F | ® | F A– Ex.
• Evaluate from M with w : int• Patterns: F (G ®) ®, …
F c x = c xG x y = assert x+1 · y ; F (G y) (y+1) M w = F (G w) (w+1)
Patterns => Type shapes
• Types t := * | x:t -> t | {u|µ} | 8x.t | [A].t Æ [A].t• Type shapes = N £ types with µ erased
– E.g., (3, f:{u|_}->*)
• ord(A) = order of simple type of A • A => (v,t’) inductively on the order of A
– Ex. A = F : – Seen patterns: F (G ®) ®, F (H ® ®) ®, …– ty(F) = (0, [(G ®), ®].8a1.t1->®.s->* Æ [(H ® ®),
®].8a18a2.t2->®.s->*)– ty(®) = (0,s), ty(G ®) = (1,t1), ty(H ® ®) = (2, t2), …
Checking typability
• Shapes & patterns => derivation structure– For each F, have patterns A1, …, An for its args– Make type derivation per F, Ai– Ex. Patterns : F (G ®) (K ®) ® for F c f x = c f x
• Track concrete patterns: c: G a1, f: K a2, x: x, …, up to base-type parameter variables
• At function applications– Look up ty(...) for matching abstract patterns– Instantiate with captured base-type variables– Use “top” type to handle unmatched shapes
• Infer satisfying assignment for µ’s– If none found, fail
Summary of naïve approach
• Symbolically evaluate the program for n steps and record closure patterns seen
• Build type shapes from the patterns• Check if typable restricted to the type shapes• If not typable, increase n and repeat• (In parallel) check for unsafety
• Thm: This is rel. comp. for closure-bounded programs– Pf. Like that of Hoare logic. “Thread” weakest
precondition through the type derivation.• Cor: 1st-order program verification can be “lifted”
to closure-bounded higher-order program verification
Naïve approach
• Relatively complete but not very clever– Patterns sufficient but not always necessary– Fails for non-closure-bounded programs
• Better approach: – Try type inference w/o patterns– If type inference fails, then infer patterns– Repeat with the added patterns
– Also, just add candidate 8x.t, e[e’] and have type inf. alg. figure out the rest (i.e., rid patterns from type inference)
Check typability w/o patterns
• Leverage existing algorithms– Liquid types [Rondon, Kawaguchi, Jhala PLDI’08]– Dependent type inference with interpolants [Unno,
Kobayashi PPDP’09]– Dependent types from counterexamples [Terauchi
POPL’10]– Predicate abstraction and CEGAR for higher-order
model checking [Kobayashi, Sato, Unno PLDI’11]– HMC: Verifying functional programs using abstract
interpreters [Jhala, Majumdar, Rybalchenko CAV’11]
A) Use the symbolic evaluation as in naïve
B) Or use counterexample from type inference
[Terauchi POPL’10][Kobayashi, Sato, Unno PLDI’11]– Unwound program slice without recursion– Infer patterns that occur in slice via flow
analysis
If inference fails, infer patterns
Check typability with the added patterns• Like the naïve approach, but• Instead of using the patterns
– i.e., c : G w, …, and [(G ®), ®].8x.t1->®.s->*, …, etc.
• Use the built type shapes minus the patterns– c : 8x.{u | _} -> *, … – And how to instantiate them– Let backend type inference alg. resolve type
matching (as well as µ inference)• Implement as program translation
Program Translation
• Universal types 8x.t modeled by x:int -> t• Instantiations e[e’] modeled by e e’ F c f = c f G x k = k x H x y = assert x · y main w = F (G w) (H w)
F a1 a2 c f = c f G x k = k x H x y = assert x · y main w = F w (G w) w (H w) ty(F) = (nil,8a1.8a2.c:(({u|_}->*)->*)->f:({u|_} ->*) -
>*)ty(G ®) = ([w],…)ty(H ®) = ([w],…)
Multiple trans. when ty(…) has intersection types(none needed for this example)
Summary of translation approach
• Try type inference w/o patterns• If type inference fails, then infer patterns• Repeat with the added patterns
• Uses off-the-shelf refinement type inference algorithms
• Complete relative to– Underlying refinement type inference
• And pattern generation– Incomplete in practice (obviously)
Preliminary Experimental Results
• Depcegar [Terauchi POPL’10]P rogram T ime (ms) T -P GEN T -T P Multiple
up1direct 33 0.9% 91.7% Nd0up1 36 1.4% 88.2% Ndmax 64 1.2% 90.3% N
nonuni 295 0.7% 92.0% Yunbounded 346 0.9% 94.4% N
Conclusions
• Relatively complete verification framework for higher-order programs– Based on refinement types– Good for “model checking” like automation
• Frees the backend theorem prover/decision prover from directly reasoning about higher-order functions
– High-level : Environment passing– Theory : Rel. comp. for closure-bounded
programs– Practice: Iterative translation