Implementing Closures in Dafny Research Project Report
Total Page:16
File Type:pdf, Size:1020Kb
Implementing Closures in Dafny Research Project Report • Author: Alexandru Dima 1 • Total number of pages: 22 • Date: Tuesday 28th September, 2010 • Location: Z¨urich, Switzerland 1E-mail: [email protected] Contents 1 Introduction 1 2 Background 1 2.1 Closures . .1 2.2 Dafny . .2 3 General approach 3 4 Procedural Closures 5 4.1 Procedural Closure Type . .5 4.2 Procedural Closure Specifications . .6 4.3 A basic procedural closure example . .6 4.3.1 Discussion . .6 4.3.2 Boogie output . .8 4.4 A counter factory example . 13 4.5 Delegation example . 17 5 Pure Closures 18 5.1 Pure Closure Type . 18 5.2 A recursive while . 19 6 Conclusions 21 6.1 Limitations . 21 6.2 Possible extensions . 21 6.3 Acknowledgments . 21 2 BACKGROUND 1 Introduction Closures represent a particularly useful language feature. They provide a means to keep the functionality linked together with state, providing a source of ex- pressiveness, conciseness and, when used correctly, give programmers a sense of freedom that few other language features do. Smalltalk's standard control structures, including branches (if/then/else) and loops (while and for) are very good examples of using closures, as closures de- lay evaluation; the state they capture may be used as a private communication channel between multiple closures closed over the same environment; closures may be used for handling User Interface events; the possibilities are endless. Although they have been used for decades, static verification has not yet tack- led the problems which appear when trying to reason modularly about closures. This project is a proof of concept of the methodology defined in [1], which presents a modular specification and partial correctness verification methodol- ogy for closures. The work described in this report is the result of a Computer Science Research Project at ETH Zurich, under the supervision of Dr. Ioannis Kassios and Prof. Dr. Peter M¨uller. 2 Background 2.1 Closures A closure is a first-class imperative procedure with free variables that are bound in its lexical environment. It is defined within the scope of its free variables, and the extent of those variables is at least as long as the lifetime of the closure itself. One way to think of a closure is as a package of two items: a pointer to a func- tion and a pointer to a state, a set of variables-values bindings. Closures can be typically handled as any other programming language object, they can be passed as parameters, they can be stored to variables, and so on. Figure 1, written in JavaScript, shows a simple example of an adder factory. The procedure createAdder will return a closure that will remember a, even after the execution has left the body of createAdder. More specifically, as long as a reference to x exists, a will also exist, and it will have the value with which the outer procedure was called. 1 2.2 Dafny 2 BACKGROUND function createAdder(a) { return function(b) { return a + b; } } x = createAdder(4); y = createAdder(-1); c = x(6); // c == 4 + 6 == 10 d = y(11); // d == -1 + 11 == 10 Figure 1: Example of JavaScript closures. What is happening in this specific example is that a new scope object containing all the local variables is being created every time createAdder is being executed and it is initialized with any variables passed in as procedure parameters. When createAdder finishes execution that scope is not destroyed because the returned procedure still holds an implicit pointer to it. This mechanism allows x to be called and it can run using a, even though exe- cution has left the body of createAdder. 2.2 Dafny Dafny[2] is an experimental language for dynamic-frames specification[3]. It explores the dynamic frames style specifications in an object-based sequential setting. Frame conditions define which allocated objects a method is allowed to change during its execution. They allow reasoning about the state of the heap after a method executes, as objects not listed in the modifies clause of the method remain unchanged. The granularity of the Dafny frame conditions is restricted to objects. For ex- ample, if an object o has two integer fields, o.i1 and o.i2, in order to change o.i1, a method must list o in its modifies clause, because integers are basic types and not objects. This means that even if the method only wants to change o.i1, after the method call, unless ensured through an ensures clause that o.i2 is unchanged, no assumptions can be made about the state of o.i2 relative to its state before the call. Dynamic Frames[3] allow to have framing without affecting data abstraction. In Dafny, this allows the programmer to list in its modifies clause a reference 2 3 GENERAL APPROACH to a set of objects, set which may change from call to call, depending on the overall state of the heap and the purposes the programmer has in mind. Figure 2 outlines the process of verifying a program written in Dafny. First, the Dafny program is parsed, an abstract syntax tree is built and resolved. Only if both the parsing and the resolving finished successfully, the Dafny abstract syntax tree is translated into a BoogiePL[4] abstract syntax tree. Then, if the resulting Boogie representation is correct, it is further translated into Math For- mulae which are passed on to a SMT Solver (such as Z3[5]). The results from the SMT Solver are collected and the possible error traces are translated back into Boogie tokens, which in turn are translated back into Dafny tokens, in order to present Dafny programmers with reasonable error messages. Dafny BoogiePL Math SMT Program Program Formulae Solver Result Figure 2: Dafny Verification Process 3 General approach The implementation of closures in Dafny follows closely the methodology de- scribed in [1]. Closures are separated in two distinct types: procedural closures and pure closures. The differences and similarities between these two types are almost the same as those between methods and functions in Dafny. Pure closures may be viewed as a special case of procedural closures which are guaranteed to have no side effects, which have a statically empty modifies clause. They can just evaluate an expression on a heap without actually making any 3 3 GENERAL APPROACH changes to it. Therefore, the only operation one can do with a pure closure is to evaluate it. Since they are closures and do capture their environment, we have decided to make pure closures parameter free. On the other hand, procedural closures may have side effects, meaning they may modify objects on the heap, and in general have a lot more properties than pure closures. They may have input parameters, output parameters, frame proper- ties and an abstraction. The abstraction is used as a means to argue about the captured state of the procedural closures. Like in [1], a number of closure specification functions have been introduced to the specification language. They are used to argue about closures and for a procedural closures they are: • pre(Heap,closure,inputs):boolean { evaluate if the precondition of a closure holds on a given heap with given inputs. • mod(closure,inputs):set<Object> { returns the static set of objects the closure may modify { its frame property. • abs(Heap,closure):AbstractionType { returns the abstraction of a closure. • post(preHeap,postHeap,closure,inputs,outputs):boolean { evaluate if the post- condition of a closure holds on a pre-heap and a post-heap, with the given inputs and outputs. • spec(preHeap,postHeap,closure,inputs,outputs):boolean { evaluate that the closure acts like a method { given a pre-heap and a post-heap, the inputs and the outputs evaluates: "if the precondition holds on the pre-heap and we call the closure, will the frame property and the postcondition hold on the post-heap?". For pure closures: • eval(Heap,closure):Type { evaluates the pure closure's expression and re- turns the result These functions, as defined above in BoogiePL are made available to the Dafny programmer. However, heaps may not be referred directly from Dafny, therefore the heap parameters to the functions will be filled in based on the context they are used from. The functions will be explained in greater depth in the following sections. Another newly introduced language construct is: • X frames Y, where X 's type is set<Object> and Y usually is pre(..) or abs(..) { it returns a boolean and evaluates: "Is it true that if I change anything but objects from X, Y will remain constant?". Another important change is in the semantics of the already existing Dafny lan- guage construct, fresh(X), which until now evaluated "Is it true that X was not 4 4 PROCEDURAL CLOSURES allocated in the pre-heap?" and now evaluates "Is it true that X is allocated in the post-heap and was not allocated in the pre-heap?". The only difference from [1] is around working with a closure's context (envi- ronment). Our approach is to statically mark methods which define closures and closure definitions which define inner closures and extract an environment class for each one. This environment class will contain all the scope variables of the method and all usage of the local variables inside the method's body are substituted with indirections through this environment class, where each local variable has a corresponding field. Another deviation is that the procedural closures bodies are being extracted and verified outside the method where the closure definitions statically occured, using again the environment class constructed for that method in order to in- direct all references of scope variables.