Upload
augustus-nicholson
View
214
Download
0
Embed Size (px)
Citation preview
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 in
and G x y = assert x+1 ¸ y; F (y+1) (G y) in
and 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 x
G 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 Multipleup1direct 33 0.9% 91.7% N
d0up1 36 1.4% 88.2% N
dmax 64 1.2% 90.3% Nnonuni 295 0.7% 92.0% Y
unbounded 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