Refinement reflection: complete verification with SMT

Niki Vazou, Anish Tondwalkar, Vikraman Choudhury, Ryan G. Scott, Ryan R. Newton, Philip Wadler, Ranjit Jhala
2017 Proceedings of the ACM on Programming Languages  
We introduce Refinement Reflection, a new framework for building SMT-based deductive verifiers. The key idea is to reflect the code implementing a user-defined function into the function's (output) refinement type. As a consequence, at uses of the function, the function definition is instantiated in the SMT logic in a precise fashion that permits decidable verification. Reflection allows the user to write equational proofs of programs just by writing other programs e.g. using pattern-matching
more » ... d recursion to perform case-splitting and induction. Thus, via the propositions-as-types principle, we show that reflection permits the specification of arbitrary functional correctness properties. Finally, we introduce a proof-search algorithm called Proof by Logical Evaluation that uses techniques from model checking and abstract interpretation, to completely automate equational reasoning. We have implemented reflection in Liqid Haskell and used it to verify that the widely used instances of the Monoid, Applicative, Functor, and Monad typeclasses actually satisfy key algebraic laws required to make the clients safe, and have used reflection to build the first library that actually verifies assumptions about associativity and ordering that are crucial for safe deterministic parallelism. and hence, explain why a given proof attempt fails [Leino and Pit-Claudel 2016] . At the other end, we have Type-Theory (TT) based theorem provers (e.g. Coq and Agda) that use type-level computation (normalization) to facilitate principled reasoning about terminating user-defined functions, but which require the user to supply lemmas or rewrite hints to discharge proofs over decidable theories. We introduce Refinement Reflection, a new framework for building SMT-based deductive verifiers, which permits the specification of arbitrary properties and yet enables complete, automated SMT-based reasoning about user-defined functions. In previous work, refinement types [Constable and Smith 1987; Rushby et al. 1998 ] Ð which decorate basic types (e.g. Integer) with SMT-decidable predicates (e.g. {v:Integer | 0 ≤ v && v < 100}) Ð were used to retrofit so-called shallow verification, such as array bounds checking, into several languages: ML [Bengtson et al. 2008; Rondon et al. 2008; Xi and Pfenning 1998] , C [Condit et al. 2007; Rondon et al. 2010], Haskell [Vazou et al. 2014], TypeScript [Vekris et al. 2016], and Racket [ Kent et al. 2016] . In this work, we extend refinement types with refinement reflection, leading to the following three contributions. 1. Refinement Reflection Our first contribution is the notion of refinement reflection. To reason about user-defined functions, the function's implementation is reflected into its (output) refinementtype specification, thus converting the function's type signature into a precise description of the function's behavior. This simple idea has a profound consequence: at uses of the function, the standard rule for (dependent) function application yields a precise means of reasoning about the function (ğ 4). Complete Specification Our second contribution is a library of combinators that lets programmers compose sophisticated proofs from basic refinements and function definitions. Our proof combinators let programmers use existing language mechanisms, like branches (to encode case splits), recursion (to encode induction), and functions (to encode auxiliary lemmas), to write proofs that look very much like their pencil-and-paper analogues (ğ 2). Furthermore, since proofs are literally just programs, we use the principle of propositions-as-types [Wadler 2015] (known as the Curry-Howard isomorphism [Howard 1980] ) to show that SMT-based verifiers can express any natural deduction proof, thus providing a pleasant implementation of natural deduction that can be used for pedagogical purposes (ğ 3). 3. Complete Verification While equational proofs can be very easy and expressive, writing them out can quickly get exhausting. Our third contribution is Proof by Logical Evaluation (PLE) a new proof-search algorithm that automates equational reasoning. The key idea in PLE is to mimic typelevel computation within SMT-logics by representing functions in a guarded form [Dijkstra 1975] and repeatedly unfolding function application terms by instantiating them with their definition corresponding to an enabled guard. We formalize a notion of equational proof and show that the above strategy is complete: i.e. it is guaranteed to find an equational proof if one exists. Furthermore, using techniques from the literature on Abstract Interpretation [Cousot and Cousot 1977] and Model Checking [Clarke et al. 1992 ], we show that the above proof search corresponds to a universal (or must) abstraction of the concrete semantics of the user-defined functions. Thus, since those functions are total, we obtain the pleasing guarantee that proof search terminates (ğ 6). We evaluate our approach by implementing refinement reflection and PLE in Liqid Haskell [Vazou et al. 2014 ], thereby turning Haskell into a theorem prover. Repurposing an existing programming language allows us to take advantage of a mature compiler and an ecosystem of libraries, while keeping proofs and programs in the same language. We demonstrate the benefits of this conversion by proving typeclass laws. Haskell's typeclass machinery has led to a suite of expressive abstractions and optimizations which, for correctness, crucially require typeclass instances to obey key algebraic laws. We show how reflection and PLE can be used to verify that widely used instances
doi:10.1145/3158141 dblp:journals/pacmpl/VazouTCSNWJ18 fatcat:kwduqhz3bzfmpckp74g7ujykle