Muito Obrigado; Vielen Dank

Catherine Dubois

Laurent Fribourg

Alexandra E Luísa

Vitor Pereira

Donald Knuth

Tout d'abord, je tiens à remercier profondément mon directeur de thèse, Jean-Christophe Filliâtre. C'est une tâche trop difficile pour moi d'utiliser des mots pour exprimer toute ma gratitude vers toi, en fait beaucoup plus difficile que toutes les questions et défis scientifiques que tu m'as posés tout au long de ces dernières années. Tes énormes qualités en tant que scientifique, enseignant et programmateur nous les connaissons tous. Par contre, j'appartiens maintenant au groupe des gens privilégiés qui t'ont eu comme directeur de thèse et qui ont pu témoigner tes qualités humaines. Ton encadrement toujours très attentif, tes mots d'encouragement mais aussi tes questions pointeuses sur l'arithmétique des ordinateurs (que j'ai raté la plupart du temps), feront pour toujours partie des plus beaux souvenirs de ma thèse. Merci beaucoup, Jean-Christophe. J'espère que, dans les années à venir, je puisse être à la hauteur de toi, en tant que chercheur mais aussi bien en tant qu'encadrant, enseignant et collègue.

Je remercie très chaleureusement mes deux rapporteurs, Jorge Sousa Pinto et Wolfgang Ahrendt. J'espère que mon travail soit à la hauteur de vos plus grandes exigences scientifiques.

If I have seen further than others, it is by standing upon the shoulders of giants.

Isaac Newton

Préface

Quelle aventure ! Me voilà arrivé, trois ans et demi après, au bout de la plus grande aventure de ma vie. Maintenant que j'écris ces dernières lignes de mon manuscrit, un drôle de mélange de sentiments m'envahit : d'une part, terminer une thèse c'est un moment d'énorme joie, aucune autre réalisation académique peut nous porter un tel sentiment ; d'autre part, la fin d'une si belle étape de la vie me donne un secret envie de tout refaire pour une deuxième fois. Je me sens un peu comme le jour d'aujourd'hui. Le matin est arrivé nuageux sur Paris. De la fenêtre de ma chambre, à la Maison du Portugal, je vois le jardin de la maison qui touche au bord du périphérique parisien. Le contraste entre ce petit coin vert et l'intense trafic de la capitale française est assez difficile à imaginer pour ceux qui n'ont pas le vrai privilège d'habiter la Cité Universitaire de Paris. Mais, en fait, je me rends compte maintenant que la vue de ma chambre représente fidèlement un petit résumé de mes années en tant que thésard : la tranquillité de la campagne sur le plateau de Saclay, mais aussi la vie agitée de cette magnifique ville qui est Paris.

Si je peux garder un si bon souvenir de ces dernières trois années et demie, c'est surtout grâce aux gens que j'ai peux rencontrer au fil de ces années. J'ai vraiment monté sur les épaules des nombreux géants et c'est pour ça que j'ai pu voir beaucoup plus loin. À tous, je souhaite d'être capable de remercier comme vous le méritez.

Paris, le 26 novembre 2018

1
Introduction I am a programmer since the age of 15. This is one of those sentences that makes me feel truly happy. During these last years, I have experienced countless times the joys of writing computer programs. In particular, I keep a very sweet memory of the first program that I ever wrote: it was a Python script that printed to a terminal (how enthusiastic I felt in that moment using a computer terminal, such a mysterious object for me back then) a table containing integer distances from 1 to a certain n, expressed in the different metric units. But the most amazing part for me was that I could interact with this program. I could specify, as an input, how many lines of the table were to be printed (I was way far from knowing that I was just changing the upper bound of iteration). This little piece of code now makes me laugh, but back then I felt like I had just accomplished the most difficult task in the world.

In my first years as a programmer, I explored many different languages and programming approaches. After my first (very brief) experiences with Python, I spent some time programming in C and under the MatLab system. After that, I spent a long time around the new shiny feature of Microsoft's Visual Studio, the C# language. It is only during my first year as an undergraduate student at Universidade da Beira Interior that I discovered OCaml and, more generally, the functional paradigm. Now, when I look back, I am absolutely sure that this was a turning point in my life as a programmer. I felt like I was just rediscovering computer programming once again, but this time everything was so different. This was a much more elegant and attractive way of writing computer programs. Nowadays, I am using OCaml in a daily basis, either for work and personal projects.

Despite all the profound technical differences between programming languages, their syntax, their development environments, etc., one thing was not changing: my frustration in the presence of bugs in my code. Every time a program of mine did not work as expected (either it computed the wrong answer, crashed spectacularly with a segmentation fault error), I felt like I got lost in the middle of the desert. I was so disappointed every time I had the impression of having a bullet-proof code, just to discover that it failed again on the next input. I was struggling to understand that the computer was only doing what I was telling him to do, no more and no less. Back then, I wished for a method that I could use to convince myself, without any doubt, that the program I had spend so much time writing was, indeed, correct.

program actually conforms to the devised specification, which normally involves some mathematical interpretation of the program semantics. The complexity of the specification and associated program can turn this into a very difficult task. Within the field of formal methods, many different approaches have been developed to verify that a program conforms to a formal specification. We can mention abstract interpretation, model checking, type systems, and deductive program verification. The last two are of particular interest for the work of this thesis. A very important common aspect to all of these methods is that they are based on the static analysis discipline, i.e., there is never the need to execute a program in order to establish its relation to the specification.

Abstract interpretation [START_REF] Cousot | Methods and logics for proving programs[END_REF][START_REF] Cousot | Systematic design of program analysis frameworks[END_REF] is a technique used to establish the absence of run-time errors of a program, such as arithmetic overflows, memory violations, and out-of-bounds array accesses. This technique works by building an over-approximation of all the possible states of a program, using what is called an abstract domain. An abstract domain consists of a symbolic representation of a set of the program's states. For instance, an abstract domain can assign an integer interval to a program variable, representing the set of possible values that variable can contain at a given point of the program. This can be exploited to report on possible arithmetic overflows in the value of some variable. However, if the assigned intervals are too wide, the analyzes can trigger false alarms. A typical strike for abstract interpretation users is the balance between a fine-grained abstract domain and the computational cost associated with a very precise analyzes. An example of a working tool, with a successful application in industry, is the Astrée static analyzer [START_REF] Cousot | The ASTRÉE analyzer[END_REF] for C programs. It has been applied by the Airbus Company to establish that software running in planes is free of run-time problems.

Model checking [START_REF] Clarke | Automatic verification of finite-state concurrent systems using temporal logic specifications[END_REF] is a technique in which a program is considered as a state transition system and specification is represented in some variant of temporal logic. Finite automata are a typical representation for such systems. This method works by exhaustive state exploration and symbolic execution. A variant that limits the analyzes to the first k steps of the system execution is called bounded model checking [START_REF] Biere | Bounded model checking[END_REF]. This technique employs SAT solving to efficiently verify properties about the system. It solves some of the problems associated with the search space of traditional model checking, which requires large amounts of memory in order to analyze complex systems. Bounded model checking has been successfully used in industrial hardware verification, by companies such as IBM, Intel and Compaq.

Type systems. Type systems represent a vast domain of research. Here, we present only a very small sub-set of this field, with a focus on the aspects that are most relevant to our use of type systems throughout this work. For a broader presentation, we invite the reader to consult the two books in the series Types and Programming Languages by Benjamin Pierce [START_REF] Pierce | Types and Programming Languages[END_REF][START_REF]Advanced Topics in Types and Programming Languages[END_REF].

Type systems are the most widely used formal technique for static program analysis. The success of type systems is explained mainly by its light-weighted interaction with the programmer. Indeed, type systems have a significant impact since they can be used without a profound knowledge of the back-end machinery. We refer to languages equipped with some form of type system as statically typed. Examples of such languages include Java, C, or C++, which demand for a programmer to only specify the type of each program variable. Languages such as OCaml, Haskell, or Scala even feature a form of type inference mechanism, which means the system can automatically compute most of the needed typing information.

The main purpose of type systems is to rule out program expressions that would lead to execution-time errors. This is done by classifying program expressions according to the set of values they can produce. For instance, 42 is said to be of type int to express the fact that it is an integer constant. The same principle applies to the constant true of type bool. When it comes to assign a type to a function, we must make explicit the type of all its arguments, as well as the type of the returned value. If we consider, for instance, a function even that decides whether its argument is an even value, we must assign to this function the type int -> bool. According to this type, even can only be applied to integer arguments. The expression even 42 is thus of type bool, but expression even true has no computational meaning. These kind of expressions are immediately rejected by type systems at compile-time.

The research field of type systems is founded on the seminal work by Alonzo Church [START_REF] Church | A formulation of the simple theory of types[END_REF]. In this work, Church introduces the simple types discipline, with the purpose of giving a rigorous definition of the class of well-behaved terms of his λ-calculus [START_REF] Church | The Calculi of Lambda Conversion[END_REF]. Since then, type systems have been the object of extensive scientific study worldwide. A main line of research focus on the use of type systems in the design of programming languages. Given the increasing complexity of programming languages, there is also a need for more expressive type systems. Polymorphic type systems are an example of such increase in expressiveness. Using polymorphic types, one has the great flexibility to soundly re-use the same piece of code with arguments of different types. Such class of type systems are direct results of the works by J. Roger Hindley [START_REF] Hindley | The principal type scheme of an object in combinatory logic[END_REF] and Robin Milner [START_REF] Milner | A theory of type polymorphismn in programming[END_REF]. In 1982, Luis Damas and Robin Milner present the algorithm W [START_REF] Damas | Principal type-schemes for functional programs[END_REF], a type inference algorithm for polymorphic types. Polymorphic type systems and algorithm W form the basis of modern functional programming languages, such as OCaml and Haskell.

Type systems are also used as an artifact to mechanically encode mathematical proofs. In particular, such proofs can represent statements about the functional correctness of a program. With no surprise, such line of work requires very expressive type systems. An example of such is the work by Martin Löf on the theory of dependent types [107]. Very succinctly, the idea is to allow types to be parameterized by terms of a language. This takes a step forward with respect to the polymorphic theory of types, where types can only be parameterized by other types. Dependent types introduce the idea that reasoning about the behavior of programs can be seen as a typechecking problem. An example of a type system based on dependent types is the Calculus of Inductive Constructions [START_REF] Paulin-Mohring | Inductive definitions in the system COQ[END_REF], by Christine Paulin-Mohring. Such a type system forms the kernel of the Coq proof assistant [START_REF] Coq | The Coq Proof Assistant Reference Manual -Version V8.6[END_REF]. During the last few decades, Coq has been successfully applied in the formalization of mathematics, as well as in the verification of computer programs. Another example of a modern proof assistant based on the theory of dependent types is the Agda [START_REF] Coquand | [END_REF] proof assistant.

Deductive program verification.

The work of this thesis takes place in the research field of deductive program verification. Deductive program verification is the activity of turning the correctness of a program into a mathematical statement, and then proving it. Here, the word "deductive" stresses that the proof of such statements is done by applying deductive reasoning. Surveys of this research field are given by Jean-Christophe Filliâtre [START_REF] Filliâtre | Deductive software verification[END_REF] and by José Bacelar Almeida, Maria João Frade, Jorge Sousa Pinto, and Simão Melo de Sousa in their textbook Rigorous Software Development: An Introduction to Program Verification [START_REF] Bacelar Almeida | Rigorous Software Development: An Introduction to Program Verification[END_REF].

The idea of mathematically proving the correctness of a computer program is, almost, as old as the activity of programming. The very first known proof of program dates back to 1949, and is due to Alan Turing [START_REF] Mathison | Checking a large routine[END_REF]. In this paper "Checking a Large Routine", the author presents a program that computes the factorial of a number by repeated additions, together with a set of local assertions that, when verified correct individually, imply the correctness of the whole program.

The proof of Alan Turing already poses many of the questions that research in deductive program verification has been tackling over the course of years. One of the most important challenges in deductive verification is to understand how one connects a mathematical behavioral specification of a program with the program itself. Turing approaches this problem by associating each instruction with a logical assertion that must hold before execution, and a second one that must be respected after executing that instruction. The behavior of the program is thus specified via the whole set of intermediate assertions. The seminal works by Robert Floyd and Tony Hoare [START_REF] Floyd | Assigning meanings to programs[END_REF][START_REF] Hoare | An axiomatic basis for computer programming[END_REF] present the bases of the first working method for the deductive verification of programs, with respect to a logical representation of its behavior. Such method is normally referred to as the Floyd-Hoare logic, or simply Hoare Logic. The idea is to represent the specification of a program via two logical formulas. We use the following notation:

{P} C {Q}
The first is called the pre-condition, and states that a program C must execute in a state satisfying the conditions of P. The second one, which we refer to as the postcondition, states that the execution of C leads to a state that satisfies Q. This corresponds exactly to the notion of Hoare triples. A Hoare triple is said to be valid if for every state satisfying P in which we execute C, if execution of C terminates then the final state satisfies Q.

From a practical point of view, to entail the validity of a Hoare triple of a program, we must provide each individual instruction of the program with its pre-and postcondition. Hoare logic provides a set of inference rules that specify how to build valid Hoare triples. The validity of the program's Hoare triple follows from the composition of these rules. One of such rules is called the consequence rule and it is defined as follows:

P ⇒ P {P } C {Q } Q ⇒ Q {P} C {Q}
This rule states that we can strengthen a pre-condition and weaken the postcondition of a valid Hoare triple. This rule is useful when composing the triples of sub-expressions in which the logical conditions are syntactically equal. The two premises containing implications are called verification conditions. In 1971, Tony Hoare uses the described method to prove the correctness of FIND, a program that rearranges the elements of an array so that the k-th element ends up at position k, with smaller elements to the left and greater elements to the right [START_REF] Hoare | Proof of a program: Find[END_REF]. In this work, the author describes how the program behaves, together with a logical specification of such a behavior. The proof of correctness boils down to proving 18 verification conditions.

Building on the foundations of Hoare logic, many program logics have been proposed throughout the years, in order to tackle the verification of a specific class of programs. A successfully example is separation logic, introduced by John Reynolds in 2002 [START_REF] Reynolds | Separation logic: a logic for shared mutable data structures[END_REF] as an extension to traditional Hoare logic to facilitate reasoning about heap-manipulating programs. Separation logic extends Hoare triples with the notion of separation conjunction. A triple of the form {P 1 P 2 } C {Q 1 Q 2 } is valid if program C is executed in a state where P 1 and P 2 are valid conditions on separated portions of the memory, and the execution terminates in a state where Q 1 and Q 2 state valid conditions on separated portions of memory.

The way one uses Hoare logic to verify the correctness of a program makes it hard to scale to the proof of larger programs. To build a valid triple for a whole program, one must provide the intermediate triples for each and every individual expression. This is a fastidious and errorprone task. Moreover, the presence of the consequence rule makes it difficult to build a tool that automates the process. Fortunately, Edsger Dijkstra presents in 1975 his work on the calculus of weakest pre-conditions [START_REF] Edsger | Guarded commands, nondeterminacy and formal derivation of programs[END_REF]. This work introduces the foundations for a tremendous increase of automation in the process of deductive verification. Dijkstra observed that a programmer is only required to explicitly provide logical assertions in a few points of the program. The remaining intermediate assertions can be inferred automatically. From the practical point of view, Dijkstra proposes to take a program C and a user-supplied postcondition Q to compute the weakest precondition, noted wp(C, Q), over the initial state of execution such that it terminates in a state satisfying the postcondition Q. The following Hoare triple is, thus, valid:

{wp(C, Q)} C {Q}
In particular, the validity of a Hoare triple of the form {P} C {Q} is a consequence of the following verification condition: P ⇒ wp(C, Q)

Note that during the computation of wp(•) other verification conditions are also generated, which must also be proved valid. Several verification tools are based in the weakest pre-condition calculus approach. These tools are commonly referred to as verification condition generators. We can cite as examples Dafny [START_REF] Rustan | Dafny: An automatic program verifier for functional correctness[END_REF], KeY [START_REF]Deductive Software Verification -The KeY Book -From Theory to Practice[END_REF], VeriFast [START_REF] Jacobs | VeriFast: A powerful, sound, predictable, fast verifier for C and Java[END_REF], VCC [START_REF] Cohen | VCC: A practical system for verifying concurrent C[END_REF], Viper [START_REF] Müller | Viper: A verification infrastructure for permission-based reasoning[END_REF], SPARK2014 [START_REF] Mccormick | Building High Integrity Applications with SPARK[END_REF], and Why3 [START_REF] Bobot | Why3: Shepherd your herd of provers[END_REF][START_REF] Filliâtre | Why3 -where programs meet provers[END_REF]. The last one is the verification tool used as the working environment of this thesis.

We shall later present it in more detail.

Building the mathematical statement that entails the correction of a program, with respect to a supplied specification, is only one part of the deductive verification process. It now comes to the point where one must actually prove the validity of this statement. A possible solution is to use a pen and a paper to make such proof, similar to what Hoare did for his proof of the FIND algorithm. A much better approach amounts to using theorem provers, tools that can be used to write and/or check a formal proof. Interactive proof assistants, such as Agda, Coq, Isabelle [START_REF] Nipkow | Isabelle/HOL -A Proof Assistant for Higher-Order Logic[END_REF], PVS [START_REF] Owre | PVS: A prototype verification system[END_REF], are a possible solution. However, these demand the user to conduct manually most of the deductive reasoning. During the last decades, we have witnessed the rise of tools that can automatically search for the proof of a given mathematical formula. Even if constrained by the limits of decidability, automatic theorem provers have shown impressive results, and are now widely accepted as the tools of choice when it comes to discharge verification conditions. In particular, the family of SMT (Satisfability Modulo Theory) solvers are nowadays a crucial ingredient in automatic proof of programs. We can cite Alt-Ergo [START_REF] Bobot | The Alt-Ergo automated theorem prover[END_REF], CVC4 [START_REF] Barrett | CVC4[END_REF], and Z3 [START_REF] De | Z3, an efficient SMT solver[END_REF] as successfully examples of this family of tools.

A rather different approach to deductive program verification builds on the principle of refinement [START_REF] Back | Refinement Calculus: A Systematic Introduction[END_REF], originally introduced by Dijkstra [START_REF] Edsger | Notes on structured programming[END_REF]. The idea is to begin with a very high-level, abstract, non-executable description of a program serving as a specification, and successively derive more concrete versions of the same program until we get a fully executable program. Each refinement step produces verification conditions that state the equivalence between the original program and the one obtained by refinement. The chain of valid refinement steps ensures that the final result of refinement is a correct-by-construction program, equivalent to the initial specification. This technique is the basis of the B method [START_REF] Abrial | The B-Book, assigning programs to meaning[END_REF] and is implemented in the associated tool Atelier B.

Let us conclude with a small "hall-of-fame" of software projects built on top of deductive verification tools. The list we present here not only shows that it is nowadays feasible to apply deductive methods in the construction of realistic programs but, perhaps more importantly, it opens the perspective for a future where every piece of software can, in principle, be verified. CompCert [START_REF] Leroy | A formally verified compiler back-end[END_REF] is a realistic, optimizing, efficient compiler for the C language, whose functional correctness was verified using the Coq proof assistant. The dimension of this system makes it a true monument of deductive verification. Not only this verified compiler shows how the use of an interactive proof assistant can scale to the proof of an industrial-sized program, but it also opens the path for complete toolchains of verified software, from verified source programs all the way down to certified low-level representations of those programs. A recently published example of a system building on CompCert is the work of José Bacelar Almeida et al. in a verified software stack for secure function evaluation [START_REF] Almeida | A fast and verified software stack for secure function evaluation[END_REF]. This work also uses EasyCrypt [START_REF] Barthe | Computer-aided security proofs for the working cryptographer[END_REF], a tool for deductive reasoning on probabilistic computations with adversarial code. Another impressive verified development based on CompCert is the Verasco [START_REF] Jourdan | A formally-verified C static analyzer[END_REF] static analyzer. Verasco is completely specified and verified in Coq. It employs the technique of abstract interpretation to establish the absence of run-time errors in programs written in CompCert subset of ISO C 1999. Specified and developed using the HOL4 interactive theorem prover, CakeML [START_REF] Kumar | CakeML: A verified implementation of ML[END_REF] is a formally verified compiler for a subset of the Standard ML language. A major novelty of CakeML is that it is a bootstrapped development, meaning that all parts of the compiler are built inside the theorem prover. On a different domain of application, the Sel4 project [START_REF] Klein | seL4: Formal verification of an OS kernel[END_REF] presents the formal verification of a complete, general-purpose operating system kernel. The proof is conducted using the Isabelle/HOL theorem prover. Finally, and to cite a remarkable application of automated verification tools, the Verisoft XT project [START_REF] Beckert | Deductive verification of system software in the Verisoft XT project[END_REF] introduced a verified version of Microsoft's Hypervisor and its embedded operating system PikeOS. The proof is done using VCC and the SMT solver Z3. The Why3 tool. Why3 is a framework that proposes a set of tools that allow the user to implement, specify, prove programs, as well as extract correct-by-construction versions of those programs. The use of Why3 is oriented towards automatic proofs, as it supports many external automatic theorem provers. Why3 can also call interactive proof assistants, such as Coq, Isabelle, or PVS, when a proof obligation cannot be automatically discharged.

Why3 comes with a programming language, WhyML, a dialect of the ML family. This language offers some features commonly found in functional languages, like pattern-matching, algebraic types and polymorphism, but also imperative constructions, like records with mutable fields and exceptions. Programs written in WhyML can be annotated with contracts, that is, pre-and postconditions. The code itself can be annotated, for instance, with loop invariants or termination measures for loops and recursive functions. It is also possible to add intermediate assertions in the code to ease automatic proofs. The WhyML language allows the user to write ghost code [START_REF] Filliâtre | The spirit of ghost code[END_REF], which represents code with no computational interest, used for specification and proof purposes only. The system uses the annotations to generate verification conditions via its implementation of a weakest precondition calculus.

WhyML is also a specification language. It features an extension of first-order logic with rank-1 polymorphic types, algebraic types, (co-)inductive predicates and recursive definitions [START_REF] Filliâtre | One logic to use them all[END_REF], as well as a limited form of higher-order logic [START_REF] Clochard | Formalizing semantics with an automatic program verifier[END_REF]. This logic is used to write theories for the purpose of modeling the behavior of programs. Such theories are most of the time axiomatic. Why3 standard library is formed of many logic theories of this kind, in particular for integer and floating point arithmetic, sets, dictionaries, and finite sequences. The Why3 standard library is fully available on the project web site, http://why3.lri.fr.

Once a program is implemented and fully specified in WhyML, the user can interact with the system via its graphical user interface [START_REF] Dailler | Lightweight interactive proving inside an automatic program verifier[END_REF], which is invoked via the why3ide command. Fig. 1.1 presents an example of a typical user session of why3ide. From the graphical interface, the user can select a verification condition and apply logical transformations (tactics, in the interactive theorem provers' vocabulary), in order to make such formulas easier to discharge. This is a lightweight mechanism of interactive proof inside the Why3 system. To try proving the validity of a verification condition, the user can call her prover of choice, typically a SMT solver. The prover can give the following answers upon a proof attempt: it is able to discharge the selected verification condition, in which case the graphical interface adds a green button on the left part of window; it answers "don't know", which means that either the prover is not able to complete the proof (it falls out of the prover capacities), or it simply abandoned the proof by lacking information in the context; "timeout", which means the prover is not able to complete the proof in the given time; or finally, it is able to refute the selected verification condition.

The feedback of solvers changes the way we proceed with our proof. On one hand, a successful proof of a generated VC incites us to move on to the next one, without posing many questions. On the other hand, an unsuccessful attempt can be explained by several different reasons: the specification is insufficient, the generated VC falls out of the solver capacity, or, very simply, there is a bug in the given code 4 . This is the reason why the automated approach to program verification is many times a try-and-repair process: negative feedback on a proof attempt naturally leads to some reparation of the code, reinforcement of specification, or maybe both.

Once the verification task is complete, i.e., all the generated verification conditions are discharged, we can use the Why3 code extraction mechanism to generate a correct-by-construction executable code. The main task of the extraction phase is the removal of ghost code, without compromising the operational meaning of the verified WhyML program. Currently, Why3 supports extraction towards OCaml, CakeML, and the C language.

Why3 has been, until now, applied in the formal verification of several programs. The majority of theses examples is contained in the Why3 Gallery of Verified Programs, available online at http://toccata.lri.fr/gallery/why3.en.html. Despite its success, we feel Why3 still lacks application in a large-scale development. We believe that Why3 has reached a state of maturity that makes it feasible to be used in the development of a realistic verified software. Taking part in the development of a fully verified programming library is, perhaps, an interesting entry point for Why3 in the "revolution" that will lead us to a world of verified software.

The VOCaL project. VOCaL is an acronym for Verified OCaml Library. The name says it all: the great ambition of the VOCaL project 5 is to conceive a mechanically verified, general-purpose library for the OCaml language. We believe to build a "world of verified software" we must start by verifying libraries, the building blocks of any realistic software project. Even massively used and tested libraries can contain bugs, like the aforementioned cases of the mergesort and Timsort algorithms. We chose OCaml as our targeting language since this is the implementation language of systems used worldwide where stability, safety, and correctness are of utmost importance. Examples include the Coq proof assistant, the EasyCrypt proof assistant for cryptographic programs, the Astrée [START_REF] Cousot | The ASTRÉE analyzer[END_REF] and Frama-C [START_REF] Kirchner | Frama-c: A software analysis perspective[END_REF] static analyzers, the Cubicle model-checker [START_REF] Sylvain Conchon | Cubicle: A parallel SMT-based model checker for parameterized systems[END_REF], and the Alt-Ergo theorem prover. Moreover, the verification of programming libraries has been, just until recently, introduce a logically inconsistent specification, we might still get every generated verification condition discharged, even for a bugged program. 5 https://vocal.lri.fr/ barely explored by the formal methods community, which makes the VOCaL project an interesting ground for research work. Other than the library itself, another foreseen contribution of VOCaL is the formal verification of parts of verification tools themselves. One of the key ingredients of the VOCaL project is the design of a specification language for OCaml, independently of any verification tool. Another ingredient is the development of the verified library itself, using a combination of three tools: CFML [START_REF] Charguéraud | Characteristic formulae for the verification of imperative programs[END_REF], Coq, and Why3. These tools nicely complement each other: CFML implements a separation logic and targets pointer-based data structures; Coq is a tool of choice for purely applicative programs; and Why3 provides a high degree of automation using off-the-shelf SMT solvers. A consistent collaboration between these tools, keeping the benefits of each one, is one of the project's greatest challenges.

This thesis takes place right in the core of the VOCaL project. Over the course of this work, we have been developing the tools and techniques that allow Why3 to scale up to a verification framework that can successfully meet the goals of the VOCaL project. We detail on the contributions of this thesis in the following.

Contributions of this thesis.

A major contribution of this thesis, with a direct impact on the VOCaL project, is the new Why3 code extraction mechanism. Our implementation effort resulted in an enhanced extraction mechanism for Why3, featuring a modular translation, up to OCaml functors. This new extraction mechanism has been successfully used to generate correct-byconstruction implementations of several verified WhyML programs. In this thesis, we also present the mathematical formalization of a representative subset of the implemented extraction function. This formalization includes a proof that the extracted programs preserve the semantics behavior of the original source, either for convergent or divergent evaluations. We also show that the result of extracting a well-typed program is still a well-typed program. From a software architecture perspective, one key feature of the new extraction mechanism is that it clearly separates the code translation phase (removing logical annotations, erasing ghost code, optimizing superfluous let..in expressions) from code printing. Adding support for the extraction to a new language is now as simple as writing a printer from an intermediate representation to that language. The OCaml printer is also a contribution of ours.

Still in the context of the VOCaL project, we gave the first steps towards a specification language for OCaml. We have used this specification language to annotate several OCaml interface files. Such a specification is translated into the WhyML language via a Why3 plugin, whose development we also authored. Building on this translation tool and the Why3 extraction mechanism, we propose a toolchain/methodology to use Why3 as a platform to obtain verified OCaml programs. The translated specification links to a WhyML implementation via the Why3 refinement mechanism, which provides us with a proof that the verified implementation is a also a refinement of the specification written in the OCaml interface file. Using this toolchain, we were able to fed the VOCaL library with several correct-by-construction OCaml modules. These range from purelyapplicative functorial data-structures such as pairing heaps, to strongly imperative ones such as a union-find library.

During our experiments with Why3 and the verification of OCaml programs, we came across the challenge of verifying arbitrarily pointer-based data structures. WhyML type system poses some constraints when it comes to reason about recursively-defined mutable data types. To circumvent some of these, we propose an approach based on an explicit memory model that we named miniheaps. One distinctive aspect of this approach, contrarily to what is done in other tools that build memory models on top of Why3, is that we want to keep using WhyML types as much as possible, hence we only build a memory model for the parts of the program that cannot be encoded in the WhyML type system. Also, we do not introduce memory as a global mutable value of the whole program. We declare it as a private data type, and pass instances of the memory as a ghost argument to each function, with the extraction mechanism erasing it from the final OCaml program. The WhyML type system guarantees that different fragments of the memory are separated.

Finally, we propose a formal specification that can be used to specify iteration processes, independently of the paradigm and underlying implementation. Such a specification is based on the use of two logical predicates to characterize the possible finite prefix sequences of elements enumerated during iteration. Our proposition is general enough to cope with the specification of non-terminating and/or non-deterministic processes. We have applied this approach to the paradigms of cursors and higher-order iterators, specifying and verifying implementations of both iteration providers and client code. We observe that our specification naturally imposes an abstract barrier between the client and the implementation of the iteration process. A key aspect of our methodology is that it is completely independent of the chosen verification tool. We used Why3 to conduct our experimental validation, but any other deductive verification tool could be used.

Plan. In Chap. 2, we use the example of a library of permutations as a gentle introduction to program verification within the Why3 framework. We explain how to write a WhyML version of the library, which includes both logical specification and implementation of functions manipulating mathematical permutations. We conclude this chapter by showing how to use the Why3 extraction mechanism to turn the verified library into a correct-by-construction, executable OCaml code.

In Chap. 3, we introduce a programming language called KidML. This language features recursive definition, local variables binding, conditional expressions, exceptions, mutable state, and, most importantly, ghost code. We adopt a step-by-step presentation of the KidML language, its type system, and operational semantics. The chapter terminates with a proof of type soundness.

In Chap. 4, we present and formalize an extraction procedure for the KidML language. For the most intrinsic cases of our extraction function, we provide examples of small KidML programs and the result of their extraction. We prove that the extraction function preserves well-typedness and semantics behavior of KidML expressions. We conclude with an overview of the actual Why3 extraction mechanism implementation, as well as some differences with respect to the procedure that is formalized in this chapter.

In Chap. 5, we propose a methodology to produce verified OCaml programs. Using the proof of an union-find library as a running example, we give a complete and detailed presentation of how our methodology works in practice. We dedicate the remaining of the chapter to presenting interesting challenges that arise during the proof of OCaml programs. For each one, we give an illustrative case study. We conclude with a statistical summary on the experimental use of our methodology.

In Chap. 6, we introduce a modular approach to reason about iteration. We apply this approach in the specification of two different iteration paradigms: cursors and higher-order iterators. For each one, we present the proof of several case studies (both iteration providers and client code).

In Chap. 7, we summary the main contributions of this thesis and draw some conclusions from our work. We conclude by enumerating some possible lines of future work.

The most important property of a program is whether it accomplishes the intentions of its user.

Tony Hoare

2

The Art of Program Verification, with Why3

We use a small library of functions manipulating permutations to illustrate how program verification is conducted in Why3. Our road-map is, in some sense, very simple. First, we write a WhyML version of such library, together with its logical specification. Then, we start what is the actual verification task. Within Why3, this mostly corresponds to call its Verification Conditions Generator and employ SMT solvers to discharge the generated verification conditions. Finally, we can use the Why3 code extraction mechanism to produce a correct-by-construction version of the library that can be compiled down to an executable.

In this chapter, we chose OCaml as the target language of our extraction. We stress that our approach to verification is oriented towards the production of an executable program from a proof, rather than taking an existing code written in a compilable programming language and prove it. We give, at the end of the chapter, the OCaml code of the permutations library, for illustration purposes.

A Library of Permutations

Notation

We use standard mathematical notation to better illustrate some of our examples. We use π to denote some permutation, and we define a permutation over the set { 0, 1, . . . , n -1} as a bijection from this set to himself. A permutation can be represented using what is normally called the Cauchy's two-line notation [START_REF] Hunger | The genesis of the abstract group concept: A contribution to the history of the origin of abstract group theory[END_REF]. A permutation π is written as a two-line matrix where the first line are the elements x 0 , x 1 , . . . , x n-1 of the set and the second one the images π(x i ), for 0 ≤ i < n, as follows:

x 0 x 1 x 2 . . . x n-1 π(x 0 ) π(x 1 ) π(x 2 ) . . . π(x n-1 )
A permutation can also be represented using the cycle notation. For a permutation π, the cycle of x is the sequence (x π(x) π 2 (x) . . .) of repeated applications of π to x until it returns back to x. This sequence of values forms the orbit of x. We continue this enumeration of cycles from π until all the elements of the permutation are written in some cycle. Let us consider, for instance, the following permutation over elements from 0 to 5:

0 1 2 3 4 1 0 3 4 2
One possible cycle representation for it is (0 1)(3 4 2). This representation is often referred to as the decomposition into disjoint cycles. Finally, we use π to denote the number of elements in the permutation π.

Library Interface

We explain in this section the behavior of each function in the permutations library. The interface for this library is the following one:

OCaml type t = int array val id : int -> t val transposition : int -> int -> int -> t val compose : t -> t -> t val inverse : t -> t val cycle_length : t -> int -> int
We encode a permutation as an array of integer values, so the type t is just an alias for the type int array. Function id takes as an argument an integer n and returns the identity permutation from 0 to n -1. For instance, the array [|0; 1; 2; 3; 4|] is the result of applying id to 5.

The transposition function takes as arguments three integers n, i, and j and creates a permutation t of n elements where every element is mapped to itself, except for i and j, for which t(i) = j and t(j) = i. For instance, the call transposition 5 0 4 returns the permutation

(0 4)(1)(2)(3)
which is represented as the array [|4; 1; 2; 3; 0|].

Function compose computes the composition of two given permutations, i.e., given two permutations π 1 and π 2 such that π 1 = π 2 , this function returns the permutation π where π(i) = π 2 (π 1 (i)), for all 0 ≤ i < π 1 . For instance, applying compose to π 1 =[|1; 0; 3; 4; 2|] and π 2 =[|1; 4; 3; 0; 2|], we get the array [|4; 1; 0; 2; 3|].

Function inverse says it all in the name: it takes a permutation t and computes its inverse. The inverse permutation of π, noted π -1 , is a permutation such that π -1 (π(i)) = i, for all 0 ≤ i < π . Let us consider the following permutation:

0 1 2 3 4 1 4 3 0 2
Its inverse is the following permutation:

0 1 2 3 4 3 0 4 2 1
Finally, function cycle_length is, likely, the most interesting one of the permutations library. It takes as arguments a permutation t and an integer i, and computes the length of the orbit of i, i.e., the number k of elements we find, when traversing the orbit, before returning back to i. This is exactly how this function computes its result: it starts with t(i) and consecutively computes t 2 (i), t 3 (i), . . ., until t k (i) = i, for some 0 ≤ k ≤ t .

Program Specification and Proof

In this section we describe the task of writing a WhyML version of the permutation library, together with its formal specification. The complete WhyML code for this development is given in Appendix A.

The Type of Permutations

Let us present in detail the type t of permutations. We declare it as the following record type: WhyML type t = { a: array63; ghost inv: array63; ghost size: int; } Field a represents the permutation itself and the two ghost fields inv and size represent, respectively, the inverse permutation of a and the number of elements in the permutation (we shall comment on the type array63 later in this section). Field size is only introduced as a facility to write simpler code, as we can get the number of elements of an array in constant time. This is our first encounter with some form of ghost code, so let us take a moment to detail over the use of such an artifact. Ghost code represents a piece of arbitrary code (by arbitrary we mean that it can contain any feature from the WhyML language, such as assignments, loops, or function calls) that is introduced to ease the proof process. Having explicitly introduced field inv makes easier the reasoning about properties of permutations, increasing proof automation. This can be seen as a sort of constructive reasoning, since possessing the inverse permutation side-by-side with a avoids introducing some forms of existential quantification, as we will see in the forthcoming proofs. The interest of size is less obvious, since the length of an array is computed in constant time, but later in this section we will see how the value in this field actually helps SMT solvers to conclude some proofs, namely that of function inverse.

We refer to the dual of ghost code as regular code, i.e., that part of the code that we want to compile and run on some hardware. This operational point of view quickly leads us to the concept of code extraction. By code extraction we mean a mechanical way of translating a program written in a proof language, for instance WhyML, into a programming language for which there exists a compiler, for instance OCaml. The main task of extraction is to erase any proof-related element, including any manifestation of ghost code. Taking type t declaration as an example, extracting such type declaration would result in WhyML type t = { a: array63 } by erasing fields inv and size1 . Type Invariant. Each field is very precisely related to the other two, in some kind of logical connection. We want to be able to verify that each value of type t we built actually respects such logical connection. In WhyML, we can wrap up a type definition with a type invariant, a logical formula that every inhabitant of that type must always satisfy. For the case of type t, this is as follows:

WhyML type t = { a: array63; ghost inv: array63; ghost size: int; } invariant { length a = length inv = size } invariant { forall i. 0 <= i < size -> 0 <= a[i] < size } invariant { forall i. 0 <= i < size -> 0 <= inv[i] < size } invariant { forall i. 0 <= i < size -> a[inv[i]] = i } invariant { forall i. 0 <= i < size -> inv[a[i]] = i } by { a = make 1 0; inv = make 1 0; size = 1 }
We start by asserting that both a and inv are arrays of the same length, equal to the value of size. The next two lines of the invariant state that the elements of a and inv are integers between 0 and size. The last two lines of the invariant closely follow the laws that define an inverse permutation. For a permutation in a and its inverse in inv, we have that a[inv[i]] = i and inv[a[i]] = i, for all 0 ≤ i < size. Actually, the last line of the invariant can be deduced from the previous ones. This requires, however, a non-trivial auxiliary lemma. By including it as part of the invariant, we rest assured that such property holds for every value we manipulate of type t since Why3 will generate a proof obligation for the preservation of the type invariant. Finally, the line starting with by is used to introduce a witness that there is at least an inhabitant of type t that respects this invariant. Such witness is an actual value of type t, in this case we chose the one where both a and inv are the array [|0|]. By exhibiting a witness for a type invariant we avoid declaring a new type with an inconsistent invariant, which prevents us from introducing an inconsistency in our proof context.

Mathematical and Computer Arithmetic.

As users of WhyML, we are very quickly confronted with the decision on which types best match the purpose of some part of our program. A good example of such tension is to chose between unbounded mathematical integers or some form of machine arithmetic, both supported by WhyML. Mathematical integers are great to reason about, since they keep us closer to the logic of theorem provers, but prevent us from reasoning about integer overflows, which is crucial for any realistic software. On the other hand, using a fined-grain model of machine arithmetic makes it safe to translate integer values to native integers of some existing programming language, but makes it harder to reason about. It is, thus, very important to carefully chose the arithmetic type of each variable, as it can dramatically impact the outcome of our proof.

Fields of record type t are a perfect example of the duality between unbounded and machine integers. Let us start with field size: since it is introduced for specification purposes only, we are very pragmatic in our choice and declare it of type int, the WhyML predefined type of unbounded integers. As for fields a and inv, we chose the type array63, a WhyML model of arrays indexed by and containing 63-bits signed integers. The type array63 is declared in the Why3 standard library as follows: This type logically represents an array as a two two-fields records, where field elts is the finite mathematical sequence of integers that represents the elements of the array, and field size is the number of elements in the array. We note that field elts is declared as a mutable field, to account for possible modifications in the elements of the array. Whenever we want to change some element of the array, we need to create a new sequence and assign it to the elts field. This would imply to re-prove, at each time, that the length of the array does not change. We overcome such potential hamper by stating in the invariant the condition size = length elts which, since size is declared as an immutable field, ensures that the length of a value of type array63 is always the same.

The invariant of array63 states that the length of sequence elts is, at most, equal to the constant max_int of type int63. The type int63 is the WhyML type for 63-bits signed integers, declared as the following range type:

WhyML type int63 = < range -0x4000_0000_0000_0000 0x3fff_ffff_ffff_ffff >
The purpose of a range type is self-explanatory: it is a Why3 artifact to introduce a numerical type whose values are within a certain limit. It takes the form < range l u >, where l and u represent, respectively, the minimum and maximum value allowed for this type. For int63, l = -0x4000_0000_0000_0000 = -2 62 and u = 0x3fff_ffff_ffff_ffff = 2 62 -1. This corresponds exactly to OCaml's built-in int type on 64-bit architectures, a 64-bit word where a bit is used for the sign and another bit is reserved for the garbage collector to distinguish between pointers and integer values. The condition length elts <= max_int ensures that an array of type array63 can be safely indexed by a value of type int63.

The second line of type array63 invariant describes the possible values an array of this type can contain. We state that, for each i within the bounds of sequence elts, the element elts[i] respects the predicate in_bounds. This predicate asserts that its argument is a value between min_int and max_int. In fact, this condition of array63 invariant is what allows us to state it represents an array whose elements are 63-bit signed integers.

If we look carefully, it might seem that the invariant of array63 is ill-typed. Let us examine in detail the first line of this invariant: here, 0, size, and length elts are terms of type int, while max_int is of type int63. The operator <= takes two arguments of type int and returns a Boolean value. It seems that applying <= to max_int would result in a typing error. What happens in fact is that, after parsing this formula, Why3 is able to automatically apply a coercion from type int63 to type int. Such coercion is declared in the Int63 module, as follows:

WhyML function to_int (x : int63) : int = int63'int x meta coercion function to_int
Here, int63'int is an automatically generated logical function for the range type int63, which converts a value of type int63 to the corresponding int value. When reading length elts <= max_int, we must actually think as if the coercion would be explicitly written, resulting in the following well-typed formula:

WhyML invariant { 0 <= size = length elts <= to_int max_int }
Now, if we are even more watchful, we can spot other coercion applications in the invariant of type t of permutations. Indeed, the declaration of type array63 comes along with the following coercion:

WhyML meta coercion function elts

In Why3, record fields are treated as if they were regular functions, which is consistent with the concept of projections. There is no difference in declaring a symbol f as coercion, whether this symbol was introduced using the keyword function or as a record field. As we shall see in the following, the fact that Why3 treats record fields as regular functions allows for a certain degree of flexibility when accessing the values of nested records fields. Each application of the operator [] in the type invariant of t is, thus, an operation over sequences. Had we not used elts as a coercion, the invariant of type t would look like CHAPTER 2. THE ART OF PROGRAM VERIFICATION, WITH WHY3

WhyML invariant { length (elts a) = length (elts inv) = size } invariant { forall i. 0 <= i < size -> 0 <= (elts a)[i] < size } invariant { forall i. 0 <= i < size -> 0 <= (elts inv)[i] < size } invariant { forall i. 0 <= i < size -> (elts a)[(elts inv)[i]] = i } invariant { forall i. 0 <= i < size -> (elts inv)[(elts a)[i]] = i }
It would be quite painful to write, each time, the application of elts or to_int. We humans seem to make quite naturally these implicit conversions between types, thus a code without explicitly writing coercion functions is actually closer to what our brain expects.

Proving Library Functions

We focus now on the specification and proof of each function of the permutations library. For every function we present its functional specification, i.e., its pre-and postconditions, as well as proof elements like loop invariants and termination measures.

Function id. Applying function id to a non-negative argument n creates a new permutation over integers between 0 and n-1, where each element is mapped to itself. For instance, id 5 returns the array [|0; 1; 2; 3; 4|]. This behavior is described using the following specification:

WhyML let id (n: int63) : t requires { 0 <= n } ensures { result.size = n } ensures { forall i. 0 <= i < n -> result.a[i] = i }
Let us note that n is of type int63, which subsumes the application of the to_int coercion in the pre-and postconditions. The body of function id starts with the creation of a fresh array to store the permutation, as follows:

WhyML

= let a = make n 0 in let ghost inv = make n 0 in Array inv is used to store the inverse permutation. We use here syntax let ghost inv... to explicitly mark inv as ghost variable, which forbids us from using it in regular code. This is yet another way to introduce pieces of ghost code in our program, other than declaring ghost record fields. The remaining of this function consists of a loop filling a and inv with values from 0 to n -1. This is as simple as the following for loop:

WhyML for i = 0 to n -1 do invariant { forall j. 0 <= j < i -> a[j] = inv[j] = j } a[i] <-i; inv[i] <-i done;
The loop invariant is not a challenge neither for humans (to figure it out), neither for automated solvers (to prove its initialization and preservation), as it merely follows the code structure. Function id ends up naturally by returning the new value of type t:

WhyML { size = to_int n; a = a; inv = inv }
The verification condition generated by Why3 for function id is discharged in no time by SMT solvers. Although straightforwardly defined and easy to prove correct, function id is not without its subtleties. Let us consider the following alternative implementation for id:

WhyML let id (n: int63) : t requires { 0 <= n } ensures { result.size = n } ensures { forall i. 0 <= i < n -> result.a[i] = i } = let a = make n 0 in for i = 0 to n -1 do invariant { forall j. 0 <= j < i -> a[j] = j } a[i] <-i; done; { size = to_int n; a = a; inv = a }
Instead of manipulating the array inv, we assign directly the value of a to inv. From a programming point of view, this implementation is not satisfactory since it introduces a memory aliasing between two different fields of the same record. Each time we modify the value of a we would also modify the value of inv, which is surely not what we want. Nonetheless, such program is still accepted by the Why3's type system and the proof replays like a charm. A small test program, however, quickly reveals that this "does not mean what we think it means"2 :

WhyML let bad_assignment () = let t = id 42 in t.a[0] <-17; t.inv[0] <-17
This program is refused by Why3 with the following, slightly cryptic, error message:

Error: This expression makes a ghost modification in the non-ghost variable t

What is reported here as the problem, seems exactly what we want to do. We are aware that inv is a ghost field of t, and we want to modify it via a ghost modification. The reason why this piece of code is rejected lies deeply in the Why3 type system with effects [START_REF] Filliâtre | A pragmatic type system for deductive verification[END_REF]. Why3 features a type and effect system with singleton regions [START_REF] Tofte | Region-based memory management[END_REF] that allows one to statically track all aliasing occurring in some program. So, for the case of function id, the system recognizes the alias between fields a and inv of the created record, and propagates this information to the (internal) type of id. Why3 is now mapping fields a and inv to point to the same singleton region, which means that whenever we modify the contents of inv the system knows that we modify the contents of a, as well. Since a is a regular field, modifying it through inv would result in ghost code modifying regular data, preventing the extraction mechanism to erase this piece of ghost code. We name such a phenomenon an interference, and we shall use this designation throughout this thesis. In Sec. 3.1.2, we introduce typing rules that take into account aliasing between regular and ghost fields, preventing us from typing function bad_assignment. Finally, the given version of function id where field inv is assigned the array inv, does not introduce the aliasing problem, and so function bad_assignment would be accepted by the Why3's type system.

Functions transposition, compose, and inverse. The next functions in the permutations library are, perhaps, even simpler to define and prove than id. The first one takes as arguments integer values i, j, and n and returns the transposition permutation of set { 0, . . . , n -1} on i and j. It is specified and implemented as follows:

WhyML let transposition (i j n: int63) : t requires { 0 <= i < n /\ 0 <= j < n } ensures { result.size = n } ensures { result.a[i] = j /\ result.a[j] = i } ensures { forall k. 0 <= k < n -> k <> i -> k <> j -> result.a[k] = k } = let t = id n in swap t.a i j; swap t.inv i j; t
The pre-condition requires that both i and j are elements of the set { 0, . . . , n -1}. The postcondition ensures that i and j are interchanged in array t.a and that for any other element k the permutation behaves as the identity function. Function swap is part of the ArrayInt63 module and is straightforwardly defined. Functional correctness of function transposition, with respect to the given specification, is easily proved using SMT solvers.

The second one, compose, takes as arguments permutations p and q and computes the composition of these two permutations, i.e., the permutation that maps each element i to p(q(i)). Its code and specification are as follows:

WhyML let compose (p q: t) : t requires { p.size = q.size } ensures { result.size = p.size } ensures { forall i. 0 <= i < p.size -> result.a[i] = p.a[q.a[i]] } = let n = p.a.length in let res = make n 0 in let ghost ires = make n 0 in for i = 0 to n -1 do invariant { forall j. 0 <= j < i -> res[j] = p.a [q.a [j]] } invariant { forall j. 0 <= j < i -> ires[j] = q.inv[p.inv[j]] } res[i] <-p.a [q.a [i]]; ires[i] <-q.inv[p.inv[i]] done; { size = to_int n; a = res; inv = ires }
We state in the pre-condition that both p and q are permutations over the same set of elements. The postcondition establishes that the resulting permutation is also a permutation over the same set as the given permutations, where each element is mapped to the composition of p and q. The loop invariant straightforwardly follows the code structure. All the generated verification conditions generated for function compose are immediately proved by SMT solvers. Let us note that variable ires is declared as a ghost variable. This variable is only used inside ghost code, so it is of interest to erase it from the resulting extracted code. Had we not marked it with the keyword ghost, the extraction mechanism would still produce a correct code, but one containing variable ires. This would result in a useless computation, incurring some execution penalty for function compose. We can also observe a similar situation with variable inv in function id.

Finally, function inverse takes as argument a permutation t and computes its inverse. The specification of this function is easily deduced, as the inverse permutation is already stored in field inv. The complete code and specification is as follows:

WhyML let inverse (t: t) : t ensures { result.size = t.size } ensures { forall i. 0 <= i < t.size -> result.a[i] = t.inv[i] } = let n = t.a.length in let res = make n 0 in for i = 0 to n -1 do invariant { forall j. 0 <= j < i -> res[t.a[j]] = j } res[t.a[i]] <-i done;
{ size = to_int n; a = res; inv = copy t.a } All the verification conditions generated for inverse are automatically proved. It is worth pointing out that, even if we already possess the inverse permutation, we cannot directly copy it to field a, i.e., replace the last line of the inverse function by WhyML { size = to_int n; a = copy t.inv; inv = copy t.a } as the newly created record would be contaminated by ghost code. This means that the whole record would be considered as a ghost value, since the regular field a was assigned to a piece of ghost code. Function inverse would thus be considered a ghost function, getting erased at extraction time. On the other hand, copying the array a into field inv is not a problem, since assigning a regular value to a ghost field is not a contaminating operation. The typing rules we introduce in Sec. 3.1.2 account for both the contaminating and non-contaminating case.

Function cycle_length. Function cycle_length is the most challenging one in the permutations library. Let us consider the permutation (0 1)(3 4 2), schematically represented as follows:

0 1 2 3 4
The above diagrams show how to compute the length of a cycle in a permutation. Given an element i of the permutation, we follow the orbit of i until we get back to i. For the given example, the first orbit is of length 2 and the second one of length 3. A possible specification for cycle_length is the following one:

WhyML let cycle_length (t: t) (i: int63) : int63 requires { 0 <= i < t.size } ensures { exists s. 0 < length s = result /\ forall j. 0 < j < result -> s[j] = t.a[s[j -1]] /\ s[0] = i /\ t.a[s[result -1]] = i /\ distinct s }
The postcondition postulates the existence of a sequence s that represents, in fact, the orbit of i. This sequence has exactly the returned length of the cycle, denoted result in the postcondition, each element in the sequence is the image of its predecessor, its first element is i, the image of last element of s is also i, and finally the elements of s are pairwise distinct. The given specification is logically correct and complete, and we could very well prove that the implementation of cycle_length respects it. However, the existential quantification can create a bottleneck in the proof, since instantiating an existential quantification remains a challenge for automated solvers. In order to make the task of provers more amenable, we can make function cycle_length returning the witness sequence, together with the integer value computed for the length of the cycle. This is done in Why3 by declaring the function return type to be a tuple containing both regular and ghost components, as follows:

WhyML let cycle_length (t: t) (i: int63) : (n: int63, ghost s: seq int)

The specification changes accordingly:

WhyML requires { 0 <= i < t.size } ensures { 0 < length s = n } ensures { forall j. 0 < j < n -> s[j] = t.a[s[j -1]] } ensures { s[0] = i /\ t.a[s[n -1]] = i } ensures { distinct s }
This introduces the notion of partially ghost result: the regular part is the actual computed result; ghost results are there to aid the proof task, for instance removing existential quantification in postconditions.

Function cycle_length body begins as follows:

WhyML = let n = ref 1 in let ghost s = ref (singleton (to_int i)) in let x = ref t.a[i] in
WhyML uses the same vocabulary as other ML languages when it comes to references manipulation: expression ref v creates a fresh reference to v; expression r := v assigns v to reference r; finally, expression !r refers to the dereferencing operation, i.e., it returns the value stored in the memory location denoted by r. Reference n stores the value that shall represent the length of the cycle of i, and that will be returned as the regular result of cycle_length. Reference s is introduced as a ghost reference and stores a value that is used only for specification purposes, i.e., the sequence representing the orbit that is the ghost result of the function. Function singleton returns a fresh one-element sequence, which is here to_int i, the mathematical representation of the machine integer i. Finally, reference x stores the next element in the orbit, starting with the image of i in the permutation, t.a[i].

The rest of function cycle_length consists of a loop that repeatedly computes the next element in the orbit, until it comes back to i. The code is as follows:

WhyML while !x <> i do s := snoc !s (to_int !x); x := t.a[!x]; incr n; done; !n, !s
The snoc function appends to the end of the sequence, given as first argument, the element given as second argument. We note that the word snoc is the mirror of cons, a classical operation from the ML tradition that adds an element to the beginning of a sequence.

We give now, step-by-step, the loop invariants that allow us to deduce the postcondition of function cycle_length. The length of sequence !s is always positive and equal to the value of !n:

WhyML invariant { 0 < length !s = !n }
Reference x always contains a value belonging to the permutation, i.e., between 0 and t.size, so does each index j of sequence !s:

WhyML invariant { 0 <= !x < t.size } invariant { forall j. 0 <= j < !n -> 0 <= !s[j] < t.size }
The first element of !s is always i while the last one is always !x:

WhyML invariant { !s[0] = i } invariant { !x = t.a[!s[!n -1]] }
Sequence !s forms an iteration of the permutation:

WhyML invariant { forall j. 0 < j < !n -> !s[j] = t.a[!s[j -1]] }
Finally, we state that all elements of s are pairwise distinct:

WhyML invariant { distinct !s }
The verification conditions generated by Why3, regarding invariants preservation and initialization, are automatically discharged by a combination of SMT solvers. Also, this set of invariants allows us to deduce the given postcondition.

We focus now on verifying the safety of this code. The access t.a[!x] poses no problem since, by invariant, 0 <= !x < t.size holds, and so this is a valid access. The other potential source of safety violation is the line incr n. Because we are using here machine integers, the call to incr generates a verification condition of the form

WhyML to_int n < max_int63
where to_int n is the mathematical representation of the value !n before the call to incr. What this verification condition demands is a proof of arithmetic overflow absence, which in this case amounts to prove that the value of !n is strictly less than the constant max_int63. Intuitively, this is true because !n is the length of !s, which forms a sub-set of elements of the permutation t.a, so !n is bound by the length of t.a, another int63 value. This is an easy reasoning for a human, but not so easy for automated provers, which explains why we are not able to discharge this verification condition using any SMT solver. Let us use reductio ad absurdum to verify that this property actually holds. The following diagram illustrates the situation where, while traversing the orbit of i, we would return to an already visited element that is not i:

i . . . x . . . . . .
Such situation contradicts the hypotheses that the permutation introduces an injective function on elements { 0, . . . , t.size -1 }, as well as the invariant distinct !s. We explore this contradiction by employing the pigeonhole principle: if the length of !s would be greater than t.size, then clearly some elements of !s would be repeated. We state the pigeonhole principle through the lemma given in Fig. 2.1. This is defined as a lemma function: a ghost, effect-free, terminating program whose contract is automatically translated to a lemma. Lemma functions come with the great flexibility that proving the property expressed in its contract is as simple as writing a program that verifies that property. This is not different from what we do for any program. In fact, this almost feels like the Curry-Howard correspondence programs as proofs [START_REF] Howard | The formulae-as-types notion of construction[END_REF]. For the pigeonhole function, we state that for any function f whose domain ranges from 0 to n-1, the co-domain ranges from 0 to m-1, and for which m < n, then there exists two different elements i1 and i2 for which f i1 = f i2. The proof proceeds as follows: we search for an i, 0 ≤ i < n, such that f i = m -1. If we find such i, then we search for a j, i + 1 ≤ j < n, such that f j = m -1. If we find such j, then we are done. Otherwise, we remove m -1 from the co-domain of f, shrink the domain to values between 0 and n-2, and re-start our search. Finally, if no i is found such that f i = m -1, then we remove m-1 from the co-domain of f and re-start our search.

The pigeonhole lemma function is turned automatically into the following lemma:

WhyML lemma pigeonhole: forall n m: int, f: int -> int. 0 <= m < n -> forall i. 0 <= i < n -> 0 <= f i < m -> exists i1, i2. 0 <= i1 < i2 < n /\ f i1 = f i2
which is added to the proof context. However, even with such statement within our hypotheses, we are not able to prove the absence of overflow for the value !n. This lemma is actually difficult to instantiate by SMT solvers, mainly because they are unable to build the function to give as an argument to the lemma function. Since pigeonhole is treated by Why3 as any other program, we can instrument the code of cycle_length with an explicit call to pigeonhole. Passing to it the right arguments, we recover an instance of the postcondition that allows SMT solvers to discharge the remaining verification condition. We do this as follows:

WhyML while !x <> i do ... s := snoc !s (to_int !x); x := t.a[!x]; if to_int !n + 1 > t.
size then pigeonhole (length !s) t.size (get !s); incr n; done;

The partial application get !s returns a function of type int -> int which we use as the argument of pigeonhole. This if..then expression introduces a logical contradiction: if it would be the case that !n + 1 > t.size, then the postcondition of pigeonhole gives us that get !s i1 = get !s i2, for two different indexes i1 and i2 of !s, which contradicts the hypotheses that all elements in !s are distinct. This contradiction is explored by SMT solvers to conclude the proof that incr n does not overflow. Let us note that, since pigeonhole is a ghost function, the whole if..then expression is considered ghost, and so is erased during extraction. In Chap. 3 we introduce typing rules that ensure such behavior.

The reasoning we just performed, using the pigeonhole principle, actually leads to a nice termination measure for function cycle_length. The proof that !n does not overflow amounts to the proof that the length of !s is never greater than t.size. We have just found our termination measure for the while loop: t.size -length !s. This value is a valid measure since it is nonnegative, after the whole reasoning above, and it decreases at each iteration, as we always add a new element to !s. We add it as a loop variant, as follows:

WhyML while !x <> i do variant { t.size -length !s }
The newly generated verification conditions regarding loop termination are proved almost immediately by SMT solvers, concluding the proof of function cycle_length.

To conclude this section, let us summarize how we use existing Why3 modules to build our library of permutations. Assuming the library is encapsulated in a WhyML module named Permut, its dependencies graph is the following one:

WhyML let rec lemma pigeonhole (n m: int) (f: int -> int) requires { 0 <= m < n } requires { forall i. 0 <= i < n -> 0 <= f i < m } ensures { exists i1, i2. 0 <= i1 < i2 < n /\ f i1 = f i2 } variant { m } = for i = 0 to n -1 do invariant { forall k. 0 <= k < i -> f k < m -1 } if f i = m -1 then begin for j = i + 1 to n -1 do invariant { forall k. i < k < j -> f k < m -1 } if f j = m -1 then return done; let function g k = if k < i then f k else f (k + 1) in pigeonhole (n -1) (m -1) g; return end done; pigeonhole n (m -1) f Figure 2.1: Pigeonhole Principle.

Permut

ArrayInt63

Int63

Seq

Int

Even if our permutations library is a rather modest size development, it features nonetheless an interesting composition of Why3 modules. The Why3's module system leads to a very comfortable modular development, abstracting away implementation details of used data structures, which lets us focus only on the specification and implementation of our library. Another interesting aspect of our development is the use of data structures that combine the use of mathematical and machine integers. Once again, the use of Why3 modules makes it very easy to compose definitions and types introduced for each numerical representation. Finally, the aforementioned coercion mechanism is a most valuable feature when it comes to write clearer and more concise code.

Code Extraction

The last mile in our quest to build a verified permutations library is to get an actual executable implementation of the verified functions. In this section, we describe how we use the Why3 extraction mechanism to generate a correct-by-construction OCaml implementation out of the Why3 proof. The current Why3 code extraction machinery is a contribution of this thesis. This is further detailed in Chap. [START_REF] Bacelar Almeida | Rigorous Software Development: An Introduction to Program Verification[END_REF].

Compared to the proof effort, the step from a verified WhyML implementation to an executable OCaml code is actually much simpler. To compile WhyML down to some existing programming language, OCaml in our case, we use the extract tool from the Why3 platform. The command line used to extract the permutations library is:

Terminal > why3 extract -D ocaml64 --recursive -o permut.ml permut.mlw
We assume the permutations library to be contained in a Why3 file named permut.mlw. The output of the extract command is specified using the -o option. The --recursive option instructs the extract tool to perform a dependencies analysis on functions from file permut.mlw, recursively extracting any WhyML symbol (auxiliary function, type, or exception definition) on which the extracted code might depend on. For the case of the permutations library, transposition depends on the swap function from the Why3 standard library. The definition of swap is added to the file permut.ml only because we use the --recursive option.

The OCaml code obtained from running this command is given in Fig. 2.2. We argue that, even if this is an automatically extracted code, it remains readable and well-structured. A given WhyML code is translated into the Why3 internal AST using a variant of A-normal form [START_REF] Flanagan | The essence of compiling with continuations[END_REF], hence some let o = ... that remain in the OCaml code. Some extra parentheses are also added during extraction. This is explained by the fact that we are defensive regarding some substitutions introduced at extraction time, as we explain in the following.

One may wonder why other functions, for instance Array.make and Array.copy, are not recursively extracted. The reason lies in the argument of the -D option: ocaml64 is the name of the extraction driver, a file to rule the whole extraction process. A driver is a text file that establishes a set of rules regarding the translation of identifiers during extraction. Let us take a look at a fragment from the ocaml64 driver: Driver module mach.array.ArrayInt63 syntax type array63 "(int array)" syntax val length "Array.length %1" syntax val ([]) "%1.(%2)" syntax val ([]<-) "%1.(%2) <-%3" syntax val make "Array.make %1 %2" syntax val copy "Array.copy %1" end A driver consists of a set of declarations of the form module M ... end, where we specify a substitution rule for some of the identifiers defined in module M. Using this driver we assume a 64-bit architecture, hence the name ocaml64. Each line of the form Syntax syntax <decl_kind> <id> "<text_to_replace>" settles that any occurrence of <id> is to be replaced by <text_to_replace>, where <decl_kind> defines the kind of symbol we want to translate. This ranges over program functions (val keyword), type symbols (type keyword), or exception declarations (exception keyword). The example above establishes a substitution for identifiers introduced in module ArrayInt633 . Using an extraction driver we can directly map some elements of our formal development to their OCaml counterpart, whenever these have a matching semantics. For instance, the type array63 being a WhyML model for an array indexed by and containing 63-bits signed integers, we replace every occurrence of type array63 by the OCaml type int array. The next line in the driver establishes a substitution rule for function length, defined in the Why3 standard library as follows:

WhyML val length (a: array63) : int63 ensures { to_int result = a.length } This is a non-defined function whose behavior is completely characterized via its specification. Its semantics is exactly that of function length from the OCaml Array module, and so we add this rule in our driver. The syntax "Array.length %1" tells the code printer to take the first argument given to length in the WhyML code and apply it, as well, to Array.length. This illustrates an important use for extraction drivers: while WhyML allows to mix defined and undefined functions in the same context, the OCaml language does not4 . It is, thus, required to replace any occurrence of a non-defined symbol by an OCaml counterpart. This is also the case for function ([]), the direct access to the n-th element of an array5 , for which a substitution is defined in line

Driver syntax val ([]) "%1.(%2)"
The fact that a rule for such function is given in the driver raises an interesting discussion: while function length is specified using only a postcondition, function ([]) is declared in the Why3 standard library with a pre-condition to prevent out of bounds accesses, as follows:

WhyML val ([]) (a: array63) (i: int63) : int63 requires { 0 <= i < a.length } ensures { to_int result = a[i] }
Nothing forbids us from taking a WhyML code calling ([]) where its pre-condition is not proved, and still extract it to OCaml. In such a case, we could obtain a code that fails at run-time with an index out of bounds exception. Let us make a statement to prevent such situation: we only extract fully-proved WhyML code. In particular, safety must have been proved, meaning no access out of bounds, for instance, is expected to occur while executing the extracted code. We stick to this hypotheses throughout this thesis. The remaining of the ocaml64 driver has no significant difference with what we have already presented, so we do not detail it further. We point out that the driver actually belongs to the trusted computing base of the extraction process. The substitutions we define in a driver are textually performed, i.e., a symbol contained in the driver is directly replaced by the corresponding code written between quotes. A typo in the driver can lead to a faulty extracted code, even if the WhyML implementation has been completely verified.

Well-typed programs do not go wrong.

Robin Milner

3

KidML

In this chapter, we present and formalize KidML, a programming language in the tradition of the ML family. KidML features recursive functions, conditional expressions, top-level declarations of new data types, and ghost code. This is a stateful language, as the programmer can mutate the value of a record field, declare and manipulate exceptions, and write divergent programs. We equip KidML with an operational semantics, in the form of a big-step evaluation judgment. To describe divergent programs, we introduce a co-evaluation relation by taking a co-inductive interpretation of the set of inductive rules that form the evaluation judgment. We also design a type system in order to rule out some KidML expressions, which would make evaluation to get stuck in some erroneous state. We present, in an incrementally way, the KidML syntactic constructions, as well as its semantics and type system in Sec. 3.1. The combination of ghost code and stateful traits makes it a non-trivial task to conceive a proper type system for KidML. The KidML programs accepted by our type system must guarantee the property of non-interference between ghost and regular code, i.e., that we can safely erase ghost code from a program without jeopardizing its operational meaning. In Sec. 3.2, we provide a proof of type soundness with respect to the devised operational semantics.

The WhyML language is our main source of inspiration in the design of KidML. Many of the features we present here can already be found in the programming language of the Why3 verification framework. In fact, one can very well consider KidML as a sub-set of the WhyML language or, more precisely, a subset of the Why3 internal AST of a WhyML program. In Sec. 3.3, we report on the main differences between KidML and WhyML. Finally, let us mention that KidML is the input language of our code extraction algorithm presented in the next chapter.

The KidML Language, Step-by-Step

In this section we present the KidML language, step-by-step. We start with a simple core language containing few syntactic constructors and then gradually increment the language with new forms of expressions. Each time a new set of constructions is introduced, we give the corresponding semantics evaluation rules, as well as the type-checking rules. Representative examples of each new construction are also given.

Core Language

KidML core language is an effect-free deterministic language, with means to introduce ghost code. It is made up of the following syntactic constructions: Let us elaborate on each syntactic construction. Here, ā stands for a sequence of atomic expressions, where an atomic expression is either a variable or a value, as follows:

a ::= x | ν
We use the bar-notation to introduce a (possibly empty) sequence of elements. When convenient, we use a comma to separate the different components of a sequence, as this is close to the ML tradition. We use, as well, some familiar terms to describe particular sequences, for instance pair and triplet to refer, respectively, to two-and three-element sequences. A value is a constant, denoted by c: v ::= c

In KidML, constants range over integer numbers, i.e. 42, 73 1 , or 1729; the Boolean values true and false; and the value (), the sole inhabitant of the Unit type. Before further describing the syntax of our core language, let us introduce how we formalize its operational semantics and type system. We define the operational semantics of our language using a big-step style. Our evaluation judgment takes the form e ⇓ ν and asserts that an expression e evaluates (after a finite number of steps) to a sequence of values ν.

The following rule is the axiom of our judgment, and describes the evaluation of atomic expressions:

ν ⇓ ν (EvalVBar)
We only define the semantics of closed expressions. No free variables can thus appear while evaluating a sequence of atomic expressions. Our typing judgment is defined as a three-place predicate of the form Γ e : βτ asserting that, under a variable typing context Γ , an expression e is assigned the typing information βτ. We define Γ as a map from variable names to typing information of the form βτ. Here, βτ stands for a type τ annotated with a ghost status β, which we abbreviate to π in the following. The sequence β is called the mask of expression e. Type τ and ghost status β are defined as follows:

τ ::= α | T τ β ::= reg | ghost π ::= βτ
A type τ is either a type variable α or the application of a type constructor T to the sequence τ. We assume that T ranges, at least, over the type Int for integer constants, type Bool for Boolean constants, and Unit for value (). The ghost status of a type is introduced using either the keyword reg marking a regular type, or the keyword ghost marking a ghost type.

β 1 β 2 reg β β ghost β 1 = β 2 ∀i. β 1 i β 2 i β 1 β 2
The following describe the typing relation for atomic expressions and sequences of atomic expressions:

Typeof(c) = τ Γ c : reg τ (TConst) Γ (x) = βτ Γ x : βτ (TVar)
∀i. Γ a i :

β i τ i Γ ā : βτ (TABar)
The rule (TConst) uses the Typeof oracle to retrieve the type of a constant c. Note that we consider a constant to be regular code, as stated in the conclusion of this rule. In rule (TVar), we fetch the type of a variable x directly from environment Γ . The type of a variable is stored in Γ together with ghost status. Finally, we use the (TABar) rule to type a sequence of atomic expressions. Each premise of this rule is used to type each atomic expression a i , using either (TConst) or (TVar).

Let us return to the syntactic constructions of our language. The let βx = e 1 in e 2 expression is used to locally bind the result of evaluating e 1 in expression e 2 . The syntax βx accounts for multiple variables binding. Each variable is bound together with its ghost status. We write reg x to declare the variable x as regular code, while ghost x indicates that x can only be used within ghost code. This is a light-weight mechanism to introduce ghost code in our language. To evaluate a let .. in expression we introduce the following big-step semantics rule:

e 1 ⇓ ν e 2 [x → ν ] ⇓ ν let βx = e 1 in e 2 ⇓ ν (EvalLet)
The evaluation of a let .. in expression is rather intuitive: expression e 1 is first evaluated to a sequence of values ν ; second, e 2 [x → ν ] is evaluated to the sequence ν, which is the result of evaluating the whole let .. in expression. Let us recall that execution never takes into account the ghost status of variables. Indeed, from a semantics point of view, ghost and regular code are indistinguishable from each other. This will become even clearer once we give the evaluation rule for a ghost e expression.

The following rule is used to assign a type to a let .. in expression:

Γ e 1 : β 1 τ 1 β 1 β Γ + [x : βτ 1 ] e 2 : π 2 Γ let βx = e 1 in e 2 : π 2 (TLet)
We briefly comment on the first and the last premises of this rule, as the second one deserves more attention. Expression e 1 is assigned the type sequence τ 1 together with mask β 1 . Expression e 2 is typed with π 2 under the environment Γ + [x : βτ 1 ], the extension of Γ where each variable x i is assigned type β i τ 1 i . Type π 2 is the type assigned to the whole let .. in expression. Let us now focus on the second premise of (TLet). It uses operator to compare the ghost status β given for x with the mask β 1 inferred for expression e 1 . Comparing two masks simply amounts to comparing their components point-wise, as given in Fig. 3.1. The operator is used in the (TLet) rule to forbid binding a ghost expression to a variable that we might have declared as a regular variable.

Let us resort to the code from Chapter 2 to illustrate how to use the typing rules presented so far. In page 20, the body of function cycle_length is as follows: We explicitly omit the derivation trees for variables n and s, since these are of no particular interest, as well as the premises comparing ghost status via operator . Even though the second component of the pair !n, !s is ghost, the whole pair can only be seen as partially ghost, given the regular status of the first component. This is why, after extraction, this pair is converted simply into !n and the type of the function cycle_length becomes reg int63. How the extraction mechanism deals with partially ghost types and results, and why such an approach is sound, is the subject of the Chapter 4.

WhyML let n = ref
Besides locally binding a variable with a ghost status, another mean to introduce ghost code in KidML is by writing ghost e. In such a case, the whole expression is to be considered a ghost expression, independently of the status of its sub-expressions. The following typing rule precisely characterizes this behavior:

Γ e : π Γ ghost e : (π) (TGhost) Function (•), which we designate as type ghostification, converts the type π into a completely ghost type, as follows:

(βτ) ghost τ

From a semantics perspective, the ghost status of an expression has no impact over its execution. The evaluation of ghost e simply forgets about the ghost keyword and continues evaluating e. This is formalized via the following rule:

e ⇓ ν ghost e ⇓ ν (EvalGhost)
According to this rule, at execution time there is no need to distinguish between ghost and regular expressions.

We conclude the presentation of our core language with the conditional expression if..then..else. We note that the test of a conditional expression is necessarily an atomic expression. We use here a variant of A-normal form [START_REF] Flanagan | The essence of compiling with continuations[END_REF], which has the benefit of reducing the number of cases to consider when defining the semantics of KidML, without compromising its expressive power. Indeed, the evaluation of an if..then..else expression can be described using only the following two rules: If the test is a ghost expression, as described in the (TIfGhost) rule, then the whole expression is contaminated, and we ghostify its type. Before describing the (TIf ) rule, let us introduce the type union operator ∪, whose definition is given Fig. 3.3. This operation takes two typing information of the form π 1 and π 2 and performs what we refer to as mask union. The mask union operation is defined Fig. 3.2, and corresponds to the operation of merging two masks of the same length. During mask union, when doing β 1 β 2 , if either β 1 or β 2 is equal to ghost, the resulting mask is also ghost. This is of particular importance in the (TIf ) rule: since the test is a regular expression, the final ghost status of the if..then..else expression is computed in the type union operation. Consequently, if one of the branches is a completely ghost expression, then it contaminates the whole if..then..else expression. This is exactly the case in function cycle_length of page 20, for the following expression: WhyML if to_int !n + 1 > to_int t.size then pigeonhole (to_int t.size) !s Here, calling the ghost function pigeonhole contaminates the whole branch (contamination via function call is be discussed in Sec. 3.1.3), which in turns contaminates the whole if..then..else. The else can be omitted as it simply corresponds to the constant (), the unit value in the ML tradition. Given its ghost status, this expression is erased at extraction time, as shown in Fig. 2.2.

e 1 ⇓ ν if true
π 1 ∪ π 2 β 1 τ ∪ β 2 τ (β 1 β 2 )τ

Imperative Features

We extend our core language with common imperative constructions, namely records manipulation. We equip our language with the ability to define new data types via records, using the following syntax:

type

T α = { f : π }
The keyword type is followed by the name of the newly defined type together with a, possibly empty, vector of type variables to account for the definition of polymorphic data types. The definition of the composite type is enclosed within curly braces and consists of a sequence of pairs of the form f i : π i , a field name followed by a type with a ghost status. We constraint type definitions to non-recursive record definitions only. The type permutation of Sec. 2.2.1 is defined in KidML as follows:

KidML type t = { a: reg array63; inv: ghost array63; size: ghost int }

For readability purposes, we use a semicolon to separate each component of the record. We extend KidML grammar of expressions to account for record manipulations, as follows:

e ::= . . . | { f = a } | a.f | a.f ← a
As in the previous section, we detail over each new construction individually, giving for each one the corresponding typing and evaluation rules. Most of the material in this section is inspired by Chapter 13 of Benjamin Pierce's textbook Types and Programming Languages [START_REF] Pierce | Types and Programming Languages[END_REF]. The first new construction is used to create a new value of a composite type. Within curly braces, we give a sequence of pairs f i = a i , meaning the value of atomic expression a i is assigned to field f i . Note that every field of the record must be given during construction. From an operational point of view, creating a record corresponds to the allocation of a memory block in the store. Thus, an expression of the form { f = a } evaluates down to the memory location that points to the beginning of the allocated block. We extend the class of KidML values accordingly:

v ::= . . . | l
Using locations, we build a very simply model of stores as a partial function from locations to the memory representation of a record, as follows:

µ l → { f = ν }
We use meta-variable µ to range over stores. We extend our evaluation relation, as well, to account for the presence of stores. It becomes a four-place predicate of the form

µ • e ⇓ µ • ν
where µ and µ represent the state of the store before and after evaluating expression e, respectively. Returning µ allows us to propagate the modified store to future evaluations. For instance, evaluating an expression of the form { f = a } adds to the store a fresh location pointing to a new record. This is formally described via the following rule:

l ∈ dom(µ) µ = µ[l → { f = ν }] µ • { f = ν } ⇓ µ • l (EvalRecord)
The first premise ensures that l is a fresh location, where function dom(µ) returns the set of allocated location in µ. In order to type locations, we extend our typing judgment with a store typing context Σ, a function from locations to types, as follows:

Γ • Σ e : π
To assign a type to a location l, we introduce the following typing rule:

Σ(l) = T τ Γ • Σ l : reg T τ (TLoc)
This is analogous to the type assignment of variables via rule (TVar). The sequence τ instantiates the sequence α, the arguments of type T . There are no remaining type variables in α. We note that, like we do for constants, we type every location as a regular value. Assigning every KidML value a regular status conforms to the fact that our semantic relation does not distinguish ghost and regular data. On the other hand, an expression { f = a } can be assigned either a regular or ghost status. We distinguish the two possibilities by adding the following rules to our type system:

type T α = { f : β f τ f } ∀i. Γ • Σ a i : β i τ f i [α → τ] ∀i. β i β f i Γ • Σ { f = a } : reg T τ (TRecord) type T α = { f : β f τ f } ∀i. Γ • Σ a i : β i τ f i [α → τ] ∃i. β i β f i Γ • Σ { f = a } : ghost T τ (TRecordGhost)
Rules (TRecord) and (TRecordGhost) differ only in the third premise. For the latter rule, there exists at least one ghost expression a i assigned to a regular field f i . This contaminates the whole record creation expression. The former establishes that all a i have a smaller or equal ghost status than the one of field f i . Let us note the use of substitution [α → τ] in both rules. We use this type substitution to create an instance of the polymorphic type T α. As an illustrative example, we use rule (TRecord) to type the last line of function id (page 16): type t = { a : reg array63; inv : ghost array63; size :

ghost int } Γ (n) = reg int Γ (a) = reg array63 Γ (inv) = ghost array63 . . . Γ • ∅ { size = to_int n; a = a; inv = inv} : reg t
We build such typing derivation using an empty store typing, as there are no locations in scope. Environment Γ binds, at least, variables n, a, and inv. We do not give the premises concerning mask relation, as these are easily checked to be true. Nonetheless, we point out that n is a regular variable assigned to the ghost field size (function to_int returns a value with the same ghost status as its argument). Assigning a regular value to a ghost field respects the relation, hence the reg status assigned to the whole expression.

The next construction we describe is the access to the contents of a record field, written a.f. The operational semantics of this construction is defined by the following rule:

µ(l) = { . . . f i = ν i . . . } µ • l.f i ⇓ µ • ν i (TGet) type T α = {. . . , f : β f τ f , . . .} Γ • Σ a : ghost T τ Γ • Σ a.f : ghost τ f [α → τ] (TGetGhost)
We note that substitution [α → τ] appears in conclusion and the type T τ in premise, contrarily to rules (TRecord) and (TRecordGhost). The ghost status assigned in the conclusion is computed as follows: if a is a regular expression, expression a.f is given the status β f of field f; if a is a ghost expression, we do not even need to inspect the status of f, the whole expression a.f is a ghost expression. For instance, in the code of function transposition (page 17), both t.a and t.inv are typed using rule (TGet), where the first one is assigned a regular status (a is a regular field), while the second one is assigned a ghost status (inv is a ghost field).

We finally detail on the expression a.f ← a. The purpose of such an expression is to modify the value associated to a field of an allocated record. This means a direct modification of memory, marking our first encounter with mutability in KidML. Other than being a useful way to define new data types, records also offer mechanisms to build modifiable values. In languages like OCaml or WhyML itself, a value of a record type is immutable by default, i.e., it is not possible to directly modify the value of individual fields. Nonetheless, we can declare fields as mutable, which allows us to modify the value associated to that field via a side-effect. In fact, both in OCaml and in WhyML, mutable fields are the only means to introduce in-place modifiable values2 . In the design of KidML, we take on a different road: every record field is a mutable field. This choice simplifies our type system and semantics evaluation as there is no need to check for the mutability of fields.

In order to devise an evaluation rule for expression a.f ← a, we combine elements from both rules (EvalRecord) and (EvalGet). Let us consider an expression of the form l.f ← a. We start by fetching the binding of l in the store. Then, we build up a new store where l points to a new record, where the value of field f is replaced by the value of expression a. The following rule materializes this description:

µ(l) = { . . . f i = ν i . . . } µ = µ[l → { . . . f i = ν . . . }] µ • l.f i ← ν ⇓ µ • () (EvalAssign)
The resulting store µ contains exactly the same bindings as µ, except for l. The new store µ represents a functional update of µ. Modifying the value of a record field is done via a side-effect, thus no significant value is to be returned during the evaluation of l.f i ← a. We use, thus, constant (), the single inhabitant of type Unit, in the conclusion of (EvalAssign).

The presence of mutability and side-effects has a significant impact in our typing relation. We refine the type assigned to an expression to include information about the effects triggered during the evaluation of such an expression. We update our typing judgment to the following relation:

Γ • Σ e : (π, reg , ghost )
Keeping track of effects is what will allow us to use our system to show rich properties about well-typed programs, for instance, the absence of ghost code interference for well-typed programs. Our type system actually becomes a type and effects system [START_REF] Tofte | Region-based memory management[END_REF]. We keep the type and ghost information π and add reg and ghost which stand for, respectively, the regular and ghost effects of e. As we will see throughout this chapter, separating the effects depending on whether they are produced via regular or ghost code, is quite useful when designing and reasoning about our type system. In the current setup, by effects we mean the modifications made on the store, which we refer to as the writing effects of an expression. Later, we shall extend the notion of effect with primitives affecting the execution flow of a program. We keep track of the changes in the store by gathering the name of locations and the corresponding fields that may have been modified through the execution of a program. For instance, if we consider the expression x.i ← 42, where x is a bound regular variable of a type containing a regular integer field i, we want to assign this expression the type (Unit, ∅ + x.i, ∅). To do so, we add the following rule to our type system:

type T α = {. . . , f : reg τ f , . . .} Γ • Σ a l : (reg T τ, ∅, ∅) Γ • Σ a r : (reg τ f [α → τ], ∅, ∅) Γ • Σ a l .f ← a r : (Unit, ∅ + a l .f, ∅) (TAssign)
The first premise is used to ensure that field f is actually part of the definition of type T α. We represent the empty effect as ∅. Atomic expressions are pure expressions, i.e., they do not produce any kind of effect. The notation + a l .f stands for the extension of effect with the writing effect a l .f. For the given rule, ∅ + a l .f corresponds to the singleton effect containing a l .f. To illustrate how an expression can produce ghost effects, let us consider the very same expression x.i ← 42 but, this time, field f is declared of type ghost integer. Such an expression is typeable using the following rule:

type T α = {. . . , f : ghost τ f , . . .} Γ • Σ a l : (β l T τ, ∅, ∅) Γ • Σ a r : (β r τ f [α → τ], ∅, ∅) Γ • Σ a l .f ← a r : (Unit, ∅, ∅ + a l .f) (TAssignGhostField)
In this rule, we do not need to consider the ghost status of neither a l or a r . The ghost status of f immediately indicates that this expression produces a ghost effect. On the other hand, if f is declared as a regular field, we need to inspect the status of a l to type the expression a l .f ← a r . If a r is a regular expression, we can resort to rule (TAssign). Otherwise, we need the following new rule:

type T α = {. . . , f : reg τ f , . . .} Γ • Σ a l : (ghost T τ, ∅, ∅) Γ • Σ a r : (β r τ f [α → τ], ∅, ∅) Γ • Σ a l .f ← a r : (Unit, ∅, ∅ + a l .f) (TAssignGhost)
In KidML, we use the type Unit to represent an empty list of results. No particular ghost status is thus attached to this type.

Let us consider the eight different ways to combine the ghost status of a l , f, and a r in the expression a l .f ← a r . As soon as f is a ghost field, we use rule (TAssignGhostField) to conclude the typing derivation. Such rule is used independently of the ghost status of either a l and a r , covering four out of the eight possible combinations. If we consider in turn a l as a ghost expression and f a regular field, rule (TAssignGhost) is the one to be used. It applies both when a r is a regular or a ghost expression, which adds two other possible expressions covered by our type system. Finally, the sole possibility to use rule (TAssign) happens when every element of expression a l .f ← a r has a regular status. This adds up to seven possible typeable combinations under our typing rules. The only expression that we reject is the one where a r is a ghost expression, while both a l and f possess a regular status. An alternative type system, where, for instance, such an expression would be accepted as contaminated by the ghost status of a r , would not be sound. We want the extraction mechanism to erase this expression, while it modifies regular data through a regular effect that we must preserve at the extracted program. This is a manifestation of ghost code interference. The type system of Why3 also rejects such kind of expressions, aborting execution with the following error message:

Error: This expression makes a ghost modification in the non-ghost variable a l This is very similar to what we described for function bad_assignment in Sec. 2.2.2 (page 17). The error message is, in fact, the same on both cases. However, the reason why we must reject bad_assignment is more subtle. Let us take a step back and recall the definition of array63, the type assigned to fields a and inv of record type t (page 13). Type array63 is defined as the following record type: WhyML type array63 = private { mutable ghost elts: seq int; ghost size: int; } Given such a definition, we can conclude that both fields a and inv store values that are memory locations. The former field is associated with a regular location, while the latter points to a ghost location. Indeed, without further restriction, the line t.inv[0] <-17 in function bad_assign is typeable under rule (TAssignGhostField). As we described in Sec. 2.2.2, the source of the problem lies in the erroneous version of function id. In line { size = to_int n; a = a; inv = a }, we create an alias between fields a and inv, i.e., an alias between a regular and a ghost field. This alias must be rejected by our type system, as it would violate the aforementioned condition of noninterference in well-typed programs. To this end, we must restrict the effects that can be assigned to an expression. We introduce the class of admissible effects, a constraint on type-checking rules to only assign effects that cannot jeopardize the static guarantees yielded by our type system. The admissible effects of an expression are defined as follows:

adm(βτ, reg , ghost ) ¬reg writes( ghost )
Predicate adm holds if there are no regular writing effects in the set ghost , i.e., no regular location is mutated via ghost code. This condition is added as a global side-condition of our type system, i.e., we decorate each type-checking rule with the implicit premise that the type assigned in the conclusion must respect predicate adm. After this extension, function bad_assignment is no longer accepted by our type system.

To conclude this section, we show how to update the type-checking rules given so far to accommodate our type with effects setting. Let us establish some syntactic conventions that we shall use throughout the remaining of this thesis. For readability purposes, we denote the regular effects of an expression using meta-variable and the ghost effects with γ. We also use σ to abbreviate the type assigned to an expression, i.e., σ ::= (βτ, , γ)

The first modified rule we present is (TLet). It changes as follows:

Γ • Σ e 1 : (β 1 τ 1 , 1 , γ 1 ) β 1 β Γ + [x : βτ 1 ] • Σ e 2 : (π 2 , 2 , γ 2 ) Γ • Σ let βx = e 1 in e 2 : (π 2 , 1 ∪ 2 , γ 1 ∪ γ 2 ) (TLet)
Each individual expression e 1 and e 2 is now assigned an effect, respectively ( 1 , γ 1 ) and ( 2 , γ 2 ), which we combine to compute the effect of the whole let..in expression. Operation 1 ∪ 2 (resp. γ 1 ∪ γ 2 ) simply amounts to a set union operation. Next, (TGhost) is straightforwardly updated as follows:

Γ • Σ e : σ Γ • Σ ghost e : (σ) (TGhost)
Operation (•) is changed accordingly:

(βτ, ∅, γ) (ghost τ, ∅, γ)
It is worth pointing out that (•) can only be applied to a type σ containing no regular effect. Ghostifying the type of an expression containing regular effects would, once again, result in ghost code interference. We implicitly add this pre-condition of (•) to every type-checking rule using the ghostification operation in its conclusion. Finally, modified rules for if..then..else expressions feature both ghostification and type union:

Σ • Γ a : (ghost Bool, ∅, ∅) Σ • Γ e 1 : σ 1 Σ • Γ e 2 : σ 2 Σ • Γ if a then e 1 else e 2 : (σ 1 ∪ σ 2 ) (TIfGhost) Σ • Γ a : (reg Bool, ∅, ∅) Σ • Γ e 1 : σ 1 Σ • Γ e 2 : σ 2 Σ • Γ if a then e 1 else e 2 : σ 1 ∪ σ 2 (TIf)
The type union operation is straightforwardly refined as follows:

(β 1 τ, 1 , γ 1 ) ∪ (β 2 τ, 2 , γ 2 ) ((β 1 β 2 )τ, 1 ∪ 2 , γ 1 ∪ γ 2 )
As we can notice, rules (TIf ) and (TIfGhost) combine the effects of both branches and assign it as the resulting effect of the whole if..then..else expression. From a static point of view, we are not able to decide which of the two branches is to be executed, and thus we introduce an over-approximation of the entire expression effects. This means the effects our type system assigns to an expression are, in fact, an upper bound of the effects triggered during the evaluation of such expression. Rules (TConst), (TVar), (TABar), (TRecord), (TRecordGhost), (TGet), (TGetGhost) are not presented here as they are trivially updated. We systematically add the typing store Σ and two empty sets for the regular and ghost effects, since all of these rules type check pure expressions.

σ 1 σ 2 1 = 2 γ 1 = γ 2 β 1 β 2 (β 1 τ, 1 , γ 1 ) (β 2 τ, 2 , γ 2 )
Figure 3.4: Types Order Relation.

Function Definition and Function Call

In our quest to turn KidML into a more realist programming language, we turn our attention towards functions. In this section, we enrich our grammar of expressions with syntactic mechanisms to locally define and call functions, as follows:

e ::= . . . | fun f α (x : π) : σ = e in e | f τ (ā) | rec f α (x : π) : σ = e in e
Functions definition. Let us detail on the first construction. We introduce a binding for a function using the keyword fun, followed by the function signature: function's name, a (possibly empty) sequence α of type variables, the function arguments enclosed within parentheses, and finally the return type σ. The body of a function is written before the in keyword, and it is bound in the expression after in. We use variable f to range over the names of functions, clearly disjoint from the class of variables introduced via a let..in expression. Contrarily to OCaml and other functional languages, an expression in KidML cannot evaluate down to a function. The way KidML treat functions is, thus, much closer to the C language than to the functional paradigm. Regarding our evaluation relation, we introduce a map δ from a function name to its formal arguments and body, i.e.,

δ f → (x : π, e)
which we add to our semantics predicate as follows:

δ • µ • e ⇓ µ • ν
We shall refer to δ in the following as a procedure environment. Let us note that we do not return a new map δ as a result of evaluation. Such a map stores, at each point of evaluation, the functional symbols that are in the scope of expression e. After completely evaluate e, we can remove from scope locally bound functions. The following rule establishes the semantics for a fun..in expression:

δ[f → (x : π, e 1 )] • µ • e 2 ⇓ µ • ν δ • µ • fun f α (x : π) : σ = e 1 in e 2 ⇓ µ • ν (EvalFun)
In the above rule, notation δ[f → (x : π, e 1 )] stands for the functional update of δ with the new binding of f to (x : π, e 1 ). The semantics of a fun..in expression closely resembles that given in rule (EvalLet), except that we do not evaluate expression e 1 , the body of function f. We extend our typing relation with a functions typing context ∆ to account for the introduction of the procedure environment in the semantics side. Our typing judgment takes the following form:

∆ • Γ • Σ e : σ
This is akin to what we did when we added the store to the evaluation relation. A new environment in semantics means, normally, a new typing context in the type-checking rules. We use the extended judgment to type-check a fun..in expression as follows:

∆ • Γ + [x : π] • Σ e 1 : σ σ Γ +[x:π] σ ∆ + [f : ∀α.(x : π) → σ] • Γ • Σ e 2 : σ α # Γ α # ∆ ∆ • Γ • Σ fun f α (x : π) : σ = e 1 in e 2 : σ (TFun)
A fun..in expression represents a type generalization point in KidML. Our function types are polymorphic with respect to the sequence α. Contrarily to what is often the practice in the design of type-checking systems for ML-like languages, we do not extend our typing environments Γ to contain explicit declarations of type variables. Therefore, we add the two side conditions α # Γ and α # ∆, which constrains α to a sequence of type variables that do not appear in the co-domain of Γ and ∆. Let us note the use of operator to compare types σ and σ. This comparison lifts to operate over types, as defined in Fig. 3.4. Following such definition, for some types σ 1 and σ 2 , the relation σ 1 σ 2 holds only if both types exhibit the same set of effects. However, this may seem very restrictive, since we can produce some local effects in the body of a function, which are not visible from outside the function definition. If we consider, for instance, function transposition from Sec. 2.2.2 (page 17), from a caller point of view, this acts as an effect-free function and so we can only assign it the type (reg t, ∅, ∅)

However, the body of this function is an effectful computation of the following type:

(reg t, ∅ + t.a, ∅ + t.inv)
It is clear that the effects in the return type of a function can refer, only, to the function arguments or to variables in scope before the fun..in expression. In other words, and using the same symbols as in rule (TFun), the effects of σ are those we get by restricting σ to effects over variables declared in Γ + [x : π]. We take exactly such extended typing environment to compute σ Γ +[x:π] , i.e., the type obtained by intersecting the variables referred in the effect of σ with the variables in the domain of Γ + [x : π].

In rule (TFun), we can assign different masks to σ and to σ . The relation between these two masks is established via the first premise of operator . The mask of σ can be less ghost than the mask of σ. That fact that we explicitly write the return type of a function, allows us to declare a mask with more ghost components than the mask that is inferred for expression e 1 . This is convenient, for instance, when we wish to share some value in ghost and regular code. Only because we declare a component of the result of a function to be ghost, it does not forbid us to use that component in regular code, within the body of the function. In other words, the mask of the return type is not used to contaminate the body of the function. Let us illustrate using function cycle_length presented in Sec. 2.2.2 (page 20). We declare function cycle_length to return a value of type (reg int63, ghost seq int), whereas its body is an expression of the form

WhyML let n = ref 1 in let ghost s = ref (singleton (to_int i)) in ... !n, !s
In this case, expression !n, !s is of type ((reg int63, ghost seq int), ∅, ∅), the mask matching exactly the one given in the return type. On the other hand, had we written the body of cycle_length as

WhyML let n = ref 1 in let s = ref (singleton (to_int i)) in ... !n, !s
this would still be accepted by rule (TFun), with the mask (reg int63, ghost seq int). In such a case, we could use variable s inside regular code, and the let s = ... expression would stay in the extracted code. Nonetheless, the extraction mechanism still erases the second component of the pair !n, !s, in order to agree with the type of cycle_length after extraction, which becomes reg int63. In Chap. 4 we show how our extraction function takes into account the mask of the return type to produce a type-checking program.

Function application. When it comes to apply a function symbol to a sequence of arguments, we write in KidML an expression of the form f τ (ā). The type sequence τ is used to instantiate the polymorphic type of f, as we shall see in the forthcoming type-checking rules, while the sequence ā represents the effective arguments of f. Let us note that this expression is in A-normal form, as the effective arguments are limited to atomic expressions. The semantics of function application is as follows:

δ(f) = (x : π, e) x : π = ν δ • µ • e[x → ν] ⇓ µ • ν δ • µ • f τ (ν) ⇓ µ • ν (EvalApp)
First, we fetch the arguments and body of f from δ; second, we ensure the sequence of formal arguments and effective ones are of the same length (KidML does not support partial application); finally, we evaluate expression e[x → ν] down to ν , which becomes the result of the whole application expression. Assigning a type to an expression of the form f τ (ā) raises a number of interesting questions. First, assuming we have ∆(f) = ∀α.(x : βτ ) → σ, we need to instantiate the polymorphic type of f. When writing an application expression, we explicitly give the sequence τ, which we use to build the type substitution [α → τ]. Let us use θ to denote such a substitution. We apply θ to the return type of f to compute the type of the whole f τ (ā) expression, i.e. the typing derivation of an application must end up with a conclusion of the form

∆ • Γ • Σ f τ (ā) : σθ
The operation σθ lifts type substitution to the level of types with effects as follows:

(βτ, , γ)θ (βτθ, , γ)
We must also propagate substitution θ to the type derivation of the effective arguments, i.e., the typing derivation of an application must feature a premise of the form

∀i. ∆ • Γ • Σ a i : (β i τ i θ, ∅, ∅)
A second important point, when typing an application expression, is how to treat effects. The return type of a function might feature some effects, which we must instantiate with the effective arguments. We talk here about the operation of effects instantiation. This is not different from type instantiation: we introduce a substitution of the form [x → ā] (formal arguments to effective arguments), that we must apply to the inferred type of an application. We use ρ to denote such a substitution and we employ it as follows:

∆ • Γ • Σ f τ (ā) : σθρ
This updates the conclusion of an application typing derivation, where σθρ is defined as follows:

(βτ, , γ)θρ (βτθ, ρ, γρ)

We are now in position to define the type-checking rules for a function application. We introduce the following two rules, in order to type-check f τ (ā):

θ = [α → τ] ρ = [x → ā] α = τ x = ā ∆(f) = ∀α.(x : βτ ) → σ ∀i. ∆ • Γ • Σ a i : (β i τ i θ, ∅, ∅) ∀i. β i β i ∆ • Γ • Σ f τ (ā) : σθρ (TApp) θ = [α → τ] ρ = [x → ā] α = τ x = ā ∆(f) = ∀α.(x : βτ ) → σ ∀i.∆ • Γ • Σ a i : (β i τ i θ, ∅, ∅) ∃i. β i β i ∆ • Γ • Σ f τ (ā) : (σθρ) (TAppGhostArg)
Note that we impose the elements in substitutions θ and ρ to have the same length. In particular, this ensures that function f is applied to the correct number of effective arguments. It is worth pointing out that the effects in σθρ must respect the implicit conditions that we only assign admissible effects in our typing derivations. In other words, substitution ρ cannot introduce a set of effects violating predicate adm. These two rules differ only on the rightmost premise. Similar to what we did in rules (TRecord) and (TRecordGhost), we distinguish between the case where we must ghostify or preserve the ghost status of the type of a f τ (ā) expression. In rule (TApp), every effective argument is less or equally ghost as the corresponding formal argument. In rule (TAppGhostArg), at least one a i is a ghost expression, whereas the i-th argument of f is expected to be a regular value. We ghostify the type in the conclusion, and in such cases we say the expression is contaminated by this arguments.

A digression on the Why3 type system. An important aspect we must note is that the type inferred via the (TApp) rule can, very well, be an entirely ghost type. For instance, let us consider the following, very simple, KidML program:

KidML fun ff (x: reg int) = ghost x in ff (42)
We can build a typing derivation for this program using the (TFun) and (TApp) rules as follows:

(TGhost) . . . ∅ • [x : reg int] • ∅ ghost x : (reg int, ∅, ∅) ∆(ff) = (x : reg int) → (ghost int, ∅, ∅) ∆ • ∅ • ∅ 42 : (reg int, ∅, ∅) . . . ∆ • ∅ • ∅ ff (42) : (ghost int, ∅, ∅) (TApp) ∅ • ∅ • ∅ fun ff (x : reg int) = ghost x in ff (42) : (ghost int, ∅, ∅) (TFun)
In the above derivation, ∆ stands for the environment [ff : (x : reg int) → (ghost int, ∅, ∅)]. We omit the derivation from rule (TGhost), as well as the premise comparing masks of the formal and effective argument of ff in (TApp). Whenever an application becomes a ghost expression because of the ghost status of the function return type, we say the expression is contaminated by application. If we take a look to the Why3 type system, we find a very similar contamination mechanism for these situations. In fact, in the definition of cycle_length (Sec. 2.2.2, page 20) we can already find an example of contamination by application. In the body of this function, we make call to the pigeonhole lemma function in the following expression:

WhyML if to_int !n + 1 > t.size then pigeonhole (Seq.length !s) t.size (get !s)
In WhyML, we can directly declare a function as a ghost symbol, independently of the ghost status of its body, either by writing is, thus, contaminated by the ghost status of pigeonhole, and so is the whole if..then expression. Getting back to the KidML type system, if we use our type-checking rules to assign a type to the pigeonhole application, we end up with the derivation . . .

∆ • Γ • ∅ pigeonhole (Seq.length !s) t.size (get !s) : (Unit, ∅, ∅) (TApp)
where ∆ binds, at least, a type to functions pigeonhole, Seq.length, get, and (!), and the typing context Γ stores a binding, for, at least, variable s. We consider the Unit type to refer to an empty sequence of results, with no particular ghost attached. The call to pigeonhole, and consequently the whole if..then..else expression, does not return any result neither performs side-effects. Such expression can, thus, be erased at extraction time.

Recursive definitions. We extend the KidML language with local definition of recursive functions. The syntax rec f α (x : π) : σ = e in e is very similar to the fun..in construction, except for the use of the rec keyword to indicate the recursive nature of the definition. The semantics of a recursive function is given by the following rule:

δ[f → (x : π, e 1 )] • µ • e 2 ⇓ µ • ν δ • µ • rec f(x : π) : σ = e 1 in e 2 ⇓ µ • ν (EvalRec)
This is the exact same semantics as that of a fun..in expression.

Our evaluation relation asserts that δ[f → (x : π, e 1 )] • µ • e 2 ⇓ µ • ν holds if e 2 evaluates down to ν in a finite number of steps. Such an hypotheses makes it meaningless to use our semantics to deal with divergent computations. To account for possible divergent evaluations, we extend the possible outcome of an evaluation: either it terminates on a sequence of values ν or it diverges. This leads us to the notion of semantic results, which we define as follows:

r ::= ν | div
Divergence is explicitly represented using the newly introduced constant div. We update our evaluation predicate accordingly:

δ • µ • e ⇓ µ • r
A divergent KidML expression can be seen as an expression inducing an infinite evaluation derivation. In order to reason about such infinite derivation trees, we introduce a coevaluation judgment of the form

δ • µ • e ⇓ co µ • r
which defines a coinductive interpretation of the evaluation rules, where δ • µ • e ⇓ co µ • r is the conclusion of a finite or infinite derivation tree. We write δ • µ • e ⇓ co div to state that e diverges during execution. For each evaluation rule, we introduce a coevaluation rule of the same form. For instance, the following is the updated (EvalRec) rule together with its coinductive counterpart:

δ[f → (x : π, e 1 )] • µ • e 2 ⇓ µ • r δ • µ • rec f(x : π) : σ = e 1 in e 2 ⇓ µ • r (EvalRec) δ[f → (x : π, e 1 )] • µ • e 2 ⇓ co µ • r δ • µ • rec f(x : π) : σ = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalRec)
We represent coevaluation rules using a double bar.

Before presenting the type-checking rule for recursive functions, let us take a step back and look at the evaluation of a let..in expression. First, we (straightforwardly) update the (EvalLet) rule to cope with the changes on the evaluation predicate:

δ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ µ • r δ • µ • let βx = e 1 in e 2 ⇓ µ • r (EvalLet)
Following the above rule, we observe that the semantic result r in the conclusion is taken from the evaluation of e 2 [x → ν]. This only makes sense when the first premise holds, i.e., when there is a finite evaluation for expression e 1 down to ν. If the execution of e 1 diverges, we must propagate divergence as the result of evaluating the whole let..in expression. The following premise asserts the divergence of e 1 :

δ • µ • e 1 ⇓ co µ • div
Our treatment of divergence as a constant and the use of a coinductive judgment builds on the works of Arthur Charguéraud [START_REF] Charguéraud | Pretty-big-step semantics[END_REF], and Xavier Leroy and Hervé Grall [START_REF] Leroy | Coinductive big-step operational semantics[END_REF][START_REF] Leroy | Coinductive big-step operational semantics[END_REF]. Anticipating the extensions from next section, let us give a generalized presentation for this premise. We introduce a predicate abort to describe semantic results that break the normal execution flow of a program. Since constant div is, for now, the only mean to interfere in the execution of an expression, predicate abort is simply defined as follows:

abort div (AbortDiv)
Using this definition, the inductive and co-inductive semantic rules for a divergent let..in expression are as follows:

δ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ co µ • r δ • µ • let βx = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLet) δ • µ • e 1 ⇓ co µ • r abort r δ • µ • let βx = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLetAbort)
It is interesting to note that our definitions allow us to write judgments of the form δ

•µ•e ⇓ µ •div or δ • µ • e ⇓ co µ • ν, even if we are only interested in δ • µ • e ⇓ µ • ν and δ • µ • e ⇓ co µ • div.
In next section, we prove a number of properties about the behavior of our inductive and co-inductive judgments, which rule out some unnatural behaviors. We finally describe the type assignment rule for recursive definitions. First, we must clarify the role of divergence in our type and effects context. In KidML, we follow the same direction as in WhyML and treat divergence as an effect. We extend our definition of effects accordingly:

, γ (ϕ, div)
The meta-variable ϕ stands for the set of writing effects and div is used as a Boolean mark to indicate whether the expression may diverge. Using the extended definition of effects, we devise the following type-checking rule for recursive functions:

∆ + [f : ∀α.(x : π) → σ] • Γ + [x : π] • Σ e 1 : σ σ Γ +[x:π] σ σ = (βτ, + div, γ) ∆ + [f : ∀α.(x : π) → σ] • Γ • Σ e 2 : σ α # Γ α # ∆ ∆ • Γ • Σ rec f α (x : π) : σ = e 1 in e 2 : σ (TRecDiv)
Let us note that, by default, we assign every recursive function a divergent status (leftmost premise). In Sec. 3.1.5, we extend our type-checking system with proof-related elements, namely oracle functions that state the termination or divergence of evaluation. In the case of provable terminating definitions, we shall be able to remove divergence from the function effects. Besides divergence, the other difference between rule (TRecDiv) and (TFun) is the extension of ∆ with the type of function f when type-checking e 1 .

To conclude this section, we detail on how to treat divergence as an admissible effect. This is, actually, very straightforward: ghost expressions cannot diverge and so we must forbid divergence to appear in the ghost effects assigned to an expression. We update our definition of predicate adm as follows:

adm(βτ, , γ) ¬obs writes(γ) ∧ ¬div(γ)

It is worth pointing out that, for now, we can only type-check non-ghost recursive functions. We automatically add the divergence effect in the (TRecDiv) rule, making it impossible to build a typing derivation for recursive ghost functions, as this would violate the global condition that every assigned effect is a valid effect. As aforementioned, in Sec. 3.1.5 we shall introduce means to decide upon the termination of a recursive definition, which we will use to relax the constraints of rule (TRecDiv).

Exceptions

In this section, we continue extending KidML with new language features, namely exception declaration, raising and handling. We add to our language a top-level construction to declare exceptions, as follows: exception E : π

The keyword exception is followed by E, the name of a new exception. The π sequence represents the arguments of the exception, i.e., a sequence of types together with a ghost status. For instance, the declaration exception Return (reg Int, ghost Bool)

introduces the exception Return whose first argument is a regular integer and the second one is a ghost Boolean value. We change our grammar of expressions as follows:

e ::= . . . | raise Eā | try e with Ex ⇒ e

An expression of the form raise Eā represents the classical exception raising constructor. From a semantic point of view, a raise expression cannot be further evaluated, which we represent by adding the following rule to our evaluation judgment definition:

δ • µ • raise Eν ⇓ µ • raise Eν (EvalRaise)
We change our definition of semantic results accordingly:

r ::= . . . | raise Eν
Exceptions are control-flow changing constructions, which make them amenable to be included in our predicate abort: abort div

(AbortDiv)
abort raise

E (AbortExn)
We treat exceptions in KidML as an effect, following the same approach as in the WhyML language.

In the context of our type and effects system, we update the effects assigned to an expression to a triple of the form , γ (ϕ, div, X )

where X is the set of raised exceptions. Using this extended definition of effects, we give the following type-checking rules for a raise expression:

exception E : β 1 τ 1 . . . β n τ n ∀i. ∆ • Γ • Σ a i : (β i τ i , ∅, ∅) ∀i. β i β i ∆ • Γ • Σ raise Eā : (reg τ , ∅ + raises E, ∅) (TRaiseReg) exception E : β 1 τ 1 . . . β n τ n ∀i. ∆ • Γ • Σ a i : (β i τ i , ∅, ∅) ∃i. β i β i ∆ • Γ • Σ raise Eā : (ghost τ , ∅, ∅ + raises E) (TRaiseGhost)
We distinguish between regular and ghost exceptions using the ghost status of arguments in exception declaration and the status of each component in sequence ā. This is a similar approach to what we did in rules for record creation and function application. The leftmost premise of (TRaiseReg) and (TRaiseGhost) rules stipulates that an exception named E was declared with arguments β 1 τ 1 . . . β n τ n .

The second syntactic construction we add to KidML is the exception handler. The semantics of a try e 0 with Ex ⇒ e expression is the following. First, we evaluate expression e 0 . If it evaluates down to a semantic result r that is not a raise expression, either a sequence of values or divergence, the whole try..with expression evaluates down to r. On the other hand, if e 0 evaluates down to raise E ν, two different outcomes are possible: E is exactly exception E i from sequence Ex ⇒ e, in which case the result of evaluating expression e i is the result of the whole try..with expression; or, if E does not match any exception in Ex, we propagate such an exception as the result of the whole evaluation. The following big-step rules systematize this informal explanation:

δ • µ • e 0 ⇓ µ • ν δ • µ • try e 0 with Ex ⇒ e ⇓ µ • ν (EvalTry) δ • µ • e ⇓ µ • r abort r ∀i. E i = r δ • µ • try e 0 with Ex ⇒ e ⇓ µ • r (EvalTryAbort) δ • µ • e 0 ⇓ µ • raise E ν E = E i ∀j. j < i → E = E j δ • µ • e i [x i → ν] ⇓ µ • r δ • µ • try e 0 with Ex ⇒ e ⇓ µ • r (EvalTryExn)
Rule (EvalTryAbort) describes, at the same time, the case where we propagate divergence out of the evaluation of e 0 , or an exception that is not listed by the handler.

We now focus on type-checking try..with expressions. The following are the three premises used to assign types to the sub-expressions of an exception handler:

∆ • Γ • Σ e 0 : (π, , γ) ∀i. exception E i : π i ∆ • Γ + [x : π i ] • Σ e i : σ i
We detail on each of these three conditions. The first is rather straightforward and is used to assign the body e 0 with some type (π, , γ). Next, we verify that every exception E i listed in the handler is defined with arguments π i . Finally, we type every expression e i with the type σ i , under a typing context extended with the bindings x : π. Using these three premises, we devise the following type-checking rule:

∆ • Γ • Σ e 0 : (π, , γ) ∀i. exception E i : π i ∆ • Γ + [x : π i ] • Σ e i : σ i ∆ • Γ • Σ try e 0 with Ex ⇒ e : (π, -raises E, γ -raises E) ∪ i σ i (TTry)
An exception raised in e 0 and caught by the handler do not interfere with the execution of the whole try..with expression, hence we remove it from the effect assigned in the conclusion. Notation raises E i (resp. γ) stands for the regular (resp. ghost) effect (resp. γ) from which we remove the sequence E of exceptions listed in the handler. Nonetheless, the final effect of a try..with expression does depend on the effects of each individual expression e i . We add the effect of every type σ i via the union operation on the right-hand side of the conclusion. Such an operation also changes the expression mask: we take the typing information π from the body of the handler e 0 and combine its mask with the mask of every type σ i . This means, that, even if e 0 is a regular expression, the whole try..with expression gets contaminated if there is a ghost expression e i .

Before giving an example of a typing derivation using rules (TRaise) and (TTry), we extend our notion of admissible effects to accommodate exceptions. Contrarily to divergence, both regular and ghost expressions can raise exceptions. Nonetheless, a ghost exception propagates a ghost effect up until the first handler that catches it. In other words, raising an exception within ghost code is a source of ghost contamination, and so it can only be done if the enclosing expression is free of regular effects. We add such a condition to our predicate adm, as follows:

adm(βτ, , γ) ¬reg writes(γ) ∧ ¬div(γ) ∧ raises(γ) ⇒ = ∅ ∧ β = ghost
The fact that a ghost exception implies a completely ghost mask β is also deeply connected to how the ghostification operation (•) treats exceptions. We relax the condition of a completely empty set of regular effects (page 37) and give the following updated definition for (•):

(βτ, (∅, ⊥, X ), γ) (ghost τ, ∅, γ ∪ X )
We allow the ghostification of terminating expressions (we use ⊥ to represent the absence of divergence), containing no regular writing effects, but possibly raising regular exceptions. Such regular exceptions then become ghost exceptions, as noted by γ ∪ X .

In order to illustrate the type-checking of raising and handling exceptions, let us consider the following KidML program: We begin by declaring the two zero-argument exceptions A and B. Next, the main part of this program is function foo. It takes a regular integer argument and returns a regular integer. The body of this function is a try..with expression, catching only exception A. Let us focus, first, on the body of the handler. This is an if..then..else expression, where the then branch simply raises exception A and the else branch returns 89 (as a matter of convenience, we do not respect here the A-normal form convention, writing the test of the if..then..else as a compound expression). We can build the following typing derivation for the body of the handler:

. . .

(TRaise)

. . .

∆ • Γ • Σ raise A : (reg int, ∅ + raises A, ∅) . . . ∆ • Γ • Σ 89 : (reg int, ∅, ∅) (TConst) ∆ • Γ • Σ if x > 42 then raise A else 89 : (reg int, ∅ + raises A, ∅) ∪ (reg int, ∅, ∅) (TIf)
We omit the typing trees for the test expression, as well as for the premises in rules (TRaise) and (TConst) as these are straightforward. The (TRaise) rule allows to type the raise A expression with type reg int, which allows then to match the same type as for the else branch and complete the derivation with rule (TIf ). The type assigned to this if..then..else expression, after type union, is

(reg int, ∅ + raises A, ∅)
We focus now on the typing derivation for the expression after the with A clause. We use the (TGhost) rule as follows:

∆ • Γ • Σ raise B : (reg int, ∅ + raises B, ∅) ∆ • Γ • Σ ghost raise B : (reg int, ∅ + raises B, ∅) (TGhost)
The only regular effect is the rise of exception B, hence it is legal to apply the ghostification operation, which results in the type

(ghost int, ∅, ∅ + raises B)
This type respects the conditions of the adm predicate, making the above derivation fit our global condition of admissible effects. Finally, we combine the two given typing derivations to type-check the whole try..with expression, as follows:

∆ • Γ • Σ if . . then . . else : (reg int, ∅ + raises A, ∅) exception A ∆ • Γ • Σ ghost raise B : (ghost int, ∅, ∅ + raises B) ∆ • Γ • Σ try . . with . . : (reg int, ∅ + raises A -raises A, ∅) ∪ (ghost int, ∅, ∅ + raises B) (TTry)
Since we remove exception A from the regular effects of the type at the left-hand side of the union operation, the final type computed is

(ghost int, ∅, ∅ + raises B)
which fits in our definition of admissible effects. Using this typing derivation, we observe that, indeed, the ghost status and ghost effect of the ghost raise B expression is propagated to the whole try..with expression, which turns the foo function into a ghost function. The application foo ( 73) is, consequently, a ghost expression which we could easily verify using the (TFun) rule.

Loop structure. We conclude this section by adding to KidML a very simple loop construction, as follows:

e ::= . . . | loop e

An expression of the form loop e stands for the infinite evaluation of expression e, as formalized by the following evaluation rule:

δ • µ • let reg _ = e in loop e ⇓ µ • r δ • µ • loop e ⇓ µ • r (EvalLoop)
We introduce the loop construction after having introduced exceptions, simply because exceptions offer a mean to escape the body of an infinite loop. In fact, from our evaluation relation point of view, we are only interested in judgments of the form δ • µ • loop e ⇓ µ • raise Eν, for some exception E. The type-checking rule for a loop e expression is straightforwardly defined as follows:

∆ • Γ • Σ e : (Unit, , γ) ∆ • Γ • Σ loop e : (reg τ, + div, γ) (TLoopDiv)
The typing information assigned in the conclusion is actually a design choice for the KidML language. Even if the body e must be typed as a Unit expression, the whole loop is assigned a generic type reg τ. As for the rule (TRaise), this means we can assign any type to a loop e expression, depending on the context in which we type-check such an expression. Perhaps, the most obvious way would be to type-check loop e also as a Unit expression. Let us use an example written in KidML to show how the second solution would create a practical pitfall. The following KidML program computes the integer square root of a non-negative integer, using the Newton-Raphson method:

KidML exception Sqrt : reg int fun isqrt (x: reg int) : (reg int, ∅, ∅) = if x = 0 then 0 else if x <= 3 then 1 else let reg y = ref x in let reg z = ref ((1 + x) / 2) in try loop if !z >= !y then raise Sqrt !y; y := !z; z := (x / !z + !z) / 2 with Sqrt y -> y in isqrt 17
Function isqrt computes a non-negative result such that isqrt(x) = √ x . We use the loop structure to iterate until the stopping condition !z >= !y is reached. In that case, we raise the Sqrt exception applied to the value of reference y, which stores the integer square root at the end of the computation. For the example above, the expression isqrt 17 evaluates down to 4, as expected. Using the (TLoopDiv) rule, we can type-check the body of the try..with expression with type (reg int, ∅ + raises Sqrt, ∅)

and so the whole try..with expression is assigned the type

(reg int, ∅, ∅)
which exactly matches the type of the isqrt function. Had we chosen to assign the Unit type in the conclusion of the (TLoopDiv) rule and the given implementation of the isqrt function would be rejected. In that case, we would have to write a dummy integer value after the loop expression, for instance KidML fun isqrt (x: reg int) : (reg int, ∅, ∅) = ... try loop ...; 1729 with Sqrt y -> y in ... which is rather unpleasant. Moreover, we know that 1729 is an unreachable point of execution, which makes it even more regrettable to include in the code. We could replace the constant 1729 by some primitive marking an unreachable point by execution. In OCaml we would write assert false after the loop structure: We extend the KidML language with such an absurd primitive in the next section. Nonetheless, we keep our design choice for the (TLoopDiv) rule, since our language only features infinitely executing loops. This is not the case with the more general while..do loops from OCaml and WhyML, even if a simple static analysis could easily detect that a while true do expression stands for an infinite loop.

Proof-related Elements

We finally come to an end in the development of the KidML language, its operational semantics, and type system. In this section, we extend KidML with some proof-related elements, namely a syntactic primitive to mark unreachable points in the code and means to prove termination.

Unreachable points. In order to mark unreachable points in the code, we follow the same approach as WhyML and extend KidML with the absurd keyword, as follows:

e ::= . . . | absurd From a semantics point of view, an absurd represents a completely evaluated expression, and so we add the following axiom to our (co-)evaluation judgment:

δ • µ • absurd ⇓ µ • absurd (EvalAbsurd)
We also extend the set of possible semantic results to include absurd, as follows:

r ::= . . . | absurd
We complete, as well, our definition of predicate abort: abort absurd

The absurd result is not to be confused with an execution error. An expression that evaluates down to absurd is not a stuck expression. Indeed, this is a well-typed expression, which we type-check via the following rule:

∆ • Γ • Σ absurd : (reg τ, ∅, ∅) (TAbsurd)
We note that, similarly to what happens with raise expressions, the (TAbsurd) rule assigns the contextual type reg τ in the conclusion. However, we choose to treat an absurd expression differently from an exception, hence the new semantic result.

The purpose of the absurd primitive is to be used together with a proof system, in order to formally demonstrate that some point in the program is never reached. We implicitly assume that every KidML program are formally verified, in particular all of the absurd are proved to be placed in unreachable execution points. In other words, a KidML program shall never evaluate down to an absurd result. This might be a little puzzling as it seems that, after all, there is no good reason to include the absurd keyword in the KidML syntax. In fact, the absurd expression triggers some particular type-checking situations when used in an if..then..else expression. These are described using the following rules:

∆ • Γ • Σ a : (βBool, ∅, ∅) ∆ • Γ • Σ e : σ ∆ • Γ • Σ if a then absurd else e : σ (TIfAbsurd1) ∆ • Γ • Σ a : (βBool, ∅, ∅) ∆ • Γ • Σ e : σ ∆ • Γ • Σ if a then e else absurd : σ (TIfAbsurd2)
For an expression of the form if a then e 1 else e 2 , where either e 1 or e 2 is an absurd, we can forget about the ghost status of the test expression a when type-checking the whole if..then..else expression. For instance, the expression KidML if ghost true then absurd else 42 is assigned the regular type (reg int, ∅, ∅), despite the ghost status of the test expression.

The particular role of absurd when type-checking if..then..else expressions is better understood by taking an operational perspective, i.e., if we think of such expressions in terms of code extraction. As we assume that every single absurd is proved to be unreachable, for expressions of the form KidML if a then absurd else e or KidML if a then e else absurd we can bypass the conditional structure and drop the branch corresponding to the absurd case. The type of the whole if..then..else must follow the type assigned to the e expression. In next chapter, we present our extraction function, for which we devise a particular extraction scheme to deal with the combination of if..then..else and absurd expressions.

Termination. When it comes to recursion and iteration, we have only considered the possibility of divergent evaluations. To overcome such a limitation, we extend the KidML type system with a proof oracle to state the termination of a given expression. We use the CheckTermination(•) function to implement such an oracle, where CheckTermination(e) states the evaluation of expression e always terminates. There are a few different approaches to implement the CheckTermination function: in WhyML, we annotate functions with termination measures, commonly called variants, from which the Why3 system generates sufficient verification conditions to prove the termination of the annotated expression; rich type theories encode directly in their types algebra conditions that check the termination of recursive definitions [START_REF] Barthe | Type-based termination of recursive definitions[END_REF]. In the KidML type-with-effects system we abstract away from a particular materialization of the CheckTermination oracle. We take here exactly the same approach as in the work of Jean-Christophe Filliâtre, Léon Gondelman and Andrei Paskevich [START_REF] Filliâtre | The spirit of ghost code[END_REF]. We only care to know that some recursive or iterative definitions are provably terminating. We can benefit from such information to avoid assigning systematically a divergence effect in the conclusion of our type-checking rules. For instance, we can separate the type-checking of recursive functions in two different rules, depending on the result of the CheckTermination function, as follows:

∆ + [f : ∀α.(x : π) → σ] • Γ + [x : π] • Σ e 1 : σ σ σ ¬CheckTermination(rec f α (x : π) : σ = e 1 ) σ = (βτ, + div, γ) ∆ + [f : ∀α.(x : π) → σ] • Γ • Σ e 2 : σ α # Γ α # ∆ ∆ • Γ • Σ rec f α (x : π) : σ = e 1 in e 2 : σ (TRecDiv) ∆ + [f : ∀α.(x : π) → σ] • Γ + [x : π] • Σ e 1 : σ σ σ CheckTermination(rec f α (x : π) : σ = e 1 ) σ = (βτ, , γ) ∆ + [f : ∀α.(x : π) → σ] • Γ • Σ e 2 : σ α # Γ α # ∆ ∆ • Γ • Σ rec f α (x : π) : σ = e 1 in e 2 : σ (TRec)
The (TRecDiv) rule is a simple update from the previously shown (TRecDiv) rule, where we add the ¬CheckTermination(rec f α (x :

π) : σ = e 1 )
premise, i.e., we cannot prove the termination of every call to the recursive function f. On the other hand, rule (TRec) features the premise

CheckTermination(rec f α (x : π) : σ = e 1 )
to indicate that every call to function f always terminates, which avoids us from assigning the div effect in the return type σ, leftmost premise. From a programmatic point of view, if we can type-check a recursive function without the divergence effect, then we can use such function in a ghost context. For instance, the following KidML program

KidML rec fact (n: reg int) : (reg int, ∅, ∅) = if n <= 0 then 1 else n * fact (n -1) in ghost fact 42
is now accepted by our type-system: one can prove that function fact always terminates (e.g., using the value of argument n as a termination measure), and so we use the (TRec) to type-check the definition of fact with an empty effect; rule (TGhost) assigns expression ghost fact [START_REF] Coquand | [END_REF] the type (reg int, ∅, ∅) which respects our predicate adm. Without the CheckTermination oracle, we would have to include the divergence effect in the return type of fact, ending up with the type

(reg int, ∅ + div, ∅)
for the ghost fact (42) expression, which clearly violates the definition of admissible effects.

Oracle CheckTermination can also be used to type-check expressions featuring the loop construction. This is no different from what we did with recursive definitions, as shown by the following rules:

CheckTermination(loop e) ∆ • Γ • Σ e : (Unit, , γ) ∆ • Γ • Σ loop e : (reg τ, , γ) (TLoop) ¬CheckTermination(loop e) ∆ • Γ • Σ e : (Unit, , γ) ∆ • Γ • Σ loop e : (reg τ, + div, γ) (TLoopDiv)
Following the result of CheckTermination, we either add or not the div effect in the conclusion of the rule. The latter corresponds to the case of rule (TLoop), the former to (TLoopDiv). The purpose of this section is prove that one can safely write programs in the KidML language. We want to prove that the static guarantees issued from our type system are sound, with respect to the introduced operational semantics relation. This means to prove that if a KidML expression is considered a well-typed expression, then it is safe to evaluate this expression or, in other words, no run-time errors will ever occur 3 . This formalization effort follows exactly the famous slogan by Robin Milner, "well-typed programs cannot go wrong" [START_REF] Milner | A theory of type polymorphismn in programming[END_REF].

KidML Formalization

Semantics

Throughout the previous section, we introduced the operational semantics rules for the KidML language. The syntax of the language is resumed in Fig. 3.5, while the complete set of rules of the evaluation judgment are given in Fig. 3.6. The (EvalErr) rule is explained later in this section. For completeness purposes, we also present the rules of our coinductive judgment, Fig. 3.7. The relation δ • µ • e ⇓ co µ • r holds if and only if this is the conclusion of a finite or infinite derivation built from these rules. In fact, to express the special case δ • µ • e ⇓ co div we do here an abuse of notation, since it does not make sense to state divergence together with a resulting store µ .

Our complete definition of semantics results is as follows:

r ::= ν | raise Eν | absurd | div
Lemma 3.2.1. If δ • µ • e ⇓ µ • r then r = div. Proof. Straightforward induction on δ • µ • e ⇓ µ • r.
In the following, we only describe the evaluation of divergent expressions via the coinductive judgment. We state, as well, that the coinductive judgment contains the inductive one:

Lemma 3.2.2. If δ • µ • e ⇓ µ • r then δ • µ • e ⇓ co µ • r. Proof. Straightforward induction on δ • µ • e ⇓ µ • r.
Next, we show that if δ • µ • e ⇓ co µ • r holds then either the evaluation of e diverges, or terminates into some result r. This corresponds to the following lemma:

Lemma 3.2.3. If δ • µ • e ⇓ co µ • r then either δ • µ • e ⇓ co div or δ • µ • e ⇓ µ • r.
Proof. By coinduction and case analysis on the rule used at the bottom of the derivation δ•µ•e ⇓ co µ • r. We detail only the cases of co-evaluation of let..in expressions.

case (CoEvalLetAbort). Co-evaluation ends up with

δ • µ • e 1 ⇓ co µ • r abort r δ • µ • let βx = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = By co-induction hypotheses either δ • µ • e 1 ⇓ co div or δ • µ • e 1 ⇓ µ • r r = div
If it is the first case, we complete the proof with the following derivation:

δ • µ • e 1 ⇓ co div abort div δ • µ • let βx = e 1 in e 2 ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLetAbort)
If it is the second case, we complete the proof with the following derivation:

δ • µ • e 1 ⇓ µ • r abort r δ • µ • let βx = e 1 in e 2 ⇓ µ • r (EvalLetAbort) δ • µ • e ⇓ µ • r δ • µ • ν ⇓ µ • ν (EvalVBar) δ • µ • raise Eν ⇓ µ • raise Eν (EvalRaise) δ • µ • e ⇓ µ • r δ • µ • ghost e ⇓ µ • r (EvalGhost) δ • µ • let reg_ = e in loop e ⇓ µ • r δ • µ • loop e ⇓ µ • r (EvalLoop) δ • µ • absurd ⇓ µ • absurd (EvalAbsurd) δ • µ • e 1 ⇓ µ • r abort r δ • µ • let βx = e 1 in e 2 ⇓ µ • r (EvalLetAbort) δ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ µ • r δ • µ • let βx = e 1 in e 2 ⇓ µ • r (EvalLet) δ(f) = (x : π, e) x : π = ν δ • µ • e[x → ν] ⇓ µ • r δ • µ • f τ (ν) ⇓ µ • r (EvalApp) δ[f → (x : π, e 1 )] • µ • e 2 ⇓ µ • r δ • µ • fun f α (x : π) : σ = e 1 in e 2 ⇓ µ • r (EvalFun) δ[f → (x : π, e 1 )] • µ • e 2 ⇓ µ • r δ • µ • rec f α (x : π) : σ = e 1 in e 2 ⇓ µ • r (EvalRec) δ • µ • e 2 ⇓ µ • r δ • µ • if false then e 1 else e 2 ⇓ µ • r (EvalIfFalse) δ • µ • e 1 ⇓ µ • r δ • µ • if true then e 1 else e 2 ⇓ µ • r (EvalIfTrue) δ • µ • e 0 ⇓ µ • ν δ • µ • try e 0 with Ex ⇒ e ⇓ µ • ν (EvalTry) δ • µ • e 0 ⇓ µ • r abort r ∀i. E i = r δ • µ • try e 0 with Ex ⇒ e ⇓ µ • r (EvalTryAbort) (EvalTryExn) δ • µ • e 0 ⇓ µ • raise E ν E = E i ∀j. j < i → E = E j δ • µ • e i [x → ν] ⇓ µ • r δ • µ • try e 0 with Ex ⇒ e ⇓ µ • r l ∈ dom(µ) µ = µ[l → { f = ν }] δ • µ • { f = ν } ⇓ µ • l (EvalRecord) µ(l) = { . . . f i = ν i . . . } δ • µ • l.f i ⇓ µ • ν i (EvalGet) µ(l) = { . . . f i = ν i . . . } µ = µ[l → { . . . f i = ν . . . }] δ • µ • l.f i ← ν ⇓ µ • () (EvalAssign) ¬(e ↓) δ • µ • e ⇓ err (EvalErr) Figure 3.6: Inductive Evaluation Rules. δ • µ • e ⇓ co µ • r δ • µ • ν ⇓ co µ • ν = = = = = = = = = = = = = = = = (CoEvalVBar) δ • µ • raise Eν ⇓ co µ • raise Eν = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalRaise) δ • µ • e ⇓ co µ • r δ • µ • ghost e ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = (CoEvalGhost) δ • µ • absurd ⇓ µ • absurd = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalAbsurd) δ • µ • let reg_ = e in loop e ⇓ co µ • r δ • µ • loop e ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLoop) δ • µ • e 1 ⇓ co µ • r abort r δ • µ • let βx = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLetAbort) δ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ co µ • r δ • µ • let βx = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLet) δ(f) = (x : π, e) x : π = ν δ • µ • e[x → ν] ⇓ co µ • r δ • µ • f τ (ν)
δ[f → (x : π, e 1 )] • µ • e 2 ⇓ co µ • r δ • µ • rec f α (x : π) : σ = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalRec) δ • µ • e 2 ⇓ co µ • r δ • µ • if false then e 1 else e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalIfFalse) δ • µ • e 1 ⇓ co µ • r δ • µ • if true then e 1 else e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalIfTrue) δ • µ • e 0 ⇓ co µ • ν δ • µ •
δ • µ • e ⇓ co µ • r (CoEvalTryExn) δ • µ • e 0 ⇓ µ • raise E ν E = E i ∀j. j < i → E = E j δ • µ • e i [x → ν] ⇓ co µ • r δ • µ •
= = = = l ∈ dom(µ) µ = µ[l → { f = ν }] µ • { f = ν } ⇓ µ • l = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalRecord) µ(l) = { . . . f i = ν i . . . } µ • l.f i ⇓ µ • ν i = = = = = = = = = = = = = = = = = = = = = = (CoEvalGet) (CoEvalAssign) µ(l) = { . . . f i = ν i . . . } µ = µ[l → { . . . f i = ν . . . }] µ • l.f i ← ν ⇓ µ • () = = =

case (CoEvalLet). Co-evaluation ends up with

δ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ co µ • r δ • µ • let βx = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = By co-induction hypotheses δ • µ • e 2 [x → ν] ⇓ co div or δ • µ • e 2 [x → ν] ⇓ µ • r r = div
If it is the first case, we complete the proof with the following derivation:

(CoEvalLet) δ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ co div δ • µ • let βx = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
If it is the second case, we complete the proof with the following derivation:

δ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ µ • r δ • µ • let βx = e 1 in e 2 ⇓ µ • r (EvalLet)
Finally, the coinductive relation is deterministic for terminating evaluations: Generic error rule. To describe expressions whose evaluation "goes wrong", we add to our definition of semantics results the err constant, as follows: r ::= . . . | err When the evaluation of an expression gets stuck, we want to completely abort the current evaluation. This last sentence says it all: to propagate errors to top level, we must state that err satisfies the abort predicate. This completes our definition of abort, as follows: abort raise Eν abort absurd abort div abort err

Lemma 3.2.4. If δ • µ • e ⇓ µ • r and δ • µ • e ⇓ co µ • r then r = r . Proof. By induction on δ • µ • e ⇓ µ • r and case analysis on δ • µ • e ⇓ co µ • r . A corollary of this result is that if δ • µ • e ⇓ µ •
The introduction of the err constant in our language follows a classical approach when it comes to prove type soundness using a big-step operational semantics. A major drawback of such an approach is that we must augment our evaluation judgment with extra rules to describe terms that get stuck, which increases the size of the semantics and, worse, it can compromise the the type soundness result in case we miss some error rule. In order to circumvent this pitfall, we follow the approach proposed by Arthur Charguéraud [START_REF] Charguéraud | Pretty-big-step semantics[END_REF] and equip our semantics judgment with a generic error rule. This captures very smoothly the idea that an expression gets stuck if no evaluation rule can be applied. We define the generic error rule in terms of the progress judgment, given Fig. 3.9. Relation e ↓ holds if there is at least one evaluation rule whose conclusion matches expression e. We introduce the following generic error rule:

¬(e ↓) δ • µ • e ⇓ µ • err
which asserts that if an evaluation cannot progress, then e must evaluate down to err for any procedures environment δ and store µ.

Type Soundness

In this section, we prove the soundness of our type system with respect to the introduced semantics evaluation. We follow a syntactic approach, as proposed by Andrew Wright and Matthias Felleisen [START_REF] Wright | A syntactic approach to type soundness[END_REF]. Using such an approach, type soundness is derived from two lemmas: the first, the progress lemma, states that closed well-typed expression can be evaluated down to a semantics result; the second lemma, the preservation property, states that a well-typed expression evaluates down to a well-typed result, not necessarily with the same ghost status. Our typing rules are summarized in Fig. 3.10 and 3.11. In order to state the progress lemma, we need the following two definitions: Definition 3.2.1 (Well-typed store). A store µ is said to be well typed with respect to a functions typing context ∆, a variables typing context Γ , and a store typing context Σ, written Σ µ, if dom(µ) = dom(Σ) and ∆ • Γ • Σ l : (reg Σ(l), ∅, ∅), for every l ∈ dom(µ). Definition 3.2.2 (Well-typed procedure environment). A procedure environment δ is said to be well typed with respect to a functions typing context ∆, a variables typing context Γ , and a store typing context Σ, written ∆ δ, if dom(δ) = dom(∆) and

∆ • Γ + [x : π] • Σ e : (reg Σ(l), ∅, ∅), for every f ∈ dom(δ) such that δ(f) = (x : π, e).
We can now state the progress lemma. This is as follows:

Lemma 3.2.5 (Progress). If ∆ • ∅ • Σ e : σ then e ↓.

Proof. By case analysis on

∆ • ∅ • Σ e : σ.
The proof of this lemma is almost trivial, thanks to the use of judgment e ↓. At each case, we show that there is at least one semantics evaluation rule that can be applied. case (TVar). Impossible. cases (TABar), (TIfAbsurd1), (TIfAbsurd2), (TIfGhost), (TIf ), (TRaiseReg), (TApp), (TRaiseGhost), (TAppGhostArg), (TRecord), (TRecordGhost), (TGet), (TGetGhost), (TAssignGhostField), (TAssignGhost), and (TAssign). For each of these cases, every occurrence of an atomic sub-expression must be fully evaluated, since variables are not typeable in the empty environment. We complete the proof of each case by applying the corresponding (↓) rule.

cases (TConst) and (TLoc) Particular cases of (TABar).

cases (TGhost), (TLoop), (TAbsurd), (TFun), (TRec), (TLet), and (TTry). For each case, we apply the corresponding (↓) rule.

We now move to the preservation lemma. Before stating and proving such a result, we introduce a substitution lemma, a classic result in the formalization of type systems. This auxiliary result states that performing variable substitution over a well-typed expression results in a welltyped expression. This is later used in the preservation proof to show that, whenever a semantics evaluation involves substitution, the semantic result obtained is well-typed.

∆ • Γ • Σ e : σ Typeof(c) = τ ∆ • Γ • Σ c : (reg τ, ∅, ∅) (TConst) Σ(l) = T τ ∆ • Γ • Σ l : (reg T τ, ∅, ∅) (TLoc) Γ (x) = π ∆ • Γ • Σ x : (π, ∅, ∅) (TVar) ∀i. ∆ • Γ • Σ a i : (π i , ∅) ∆ • Γ • Σ ā : (π, ∅, ∅) (TABar) ∆ • Γ • Σ e : σ ∆ • Γ • Σ ghost e : (σ) (TGhost) ∆ • Γ • Σ absurd : (reg τ, ∅, ∅) (TAbsurd) CheckTermination(loop e) ∆ • Γ • Σ e : (Unit, , γ) ∆ • Γ • Σ loop e : (reg τ, , γ) (TLoop) ¬CheckTermination(loop e) ∆ • Γ • Σ e : (Unit, , γ) ∆ • Γ • Σ loop e : (reg τ, + div, γ) (TLoopDiv) ∆ • Γ + [x : π] • Σ e 1 : σ σ Γ +[x:π] σ ∆ + [f : ∀α.(x : π) → σ] • Γ • Σ e 2 : σ α#Γ α ∈ ∆ ∆ • Γ • Σ fun f α (x : π) : σ = e 1 in e 2 : σ (TFun) ∆ + [f : ∀α.(x : π) → σ] • Γ + [x : π] • Σ e 1 : σ σ Γ +[x:π] σ ¬CheckTermination(rec f α (x : π) : σ = e 1 ) σ = (βτ, + div, γ) ∆ + [f : ∀α.(x : π) → σ] • Γ • Σ e 2 : σ α ∈ Γ α ∈ ∆ ∆ • Γ • Σ rec f α (x : π) : σ = e 1 in e 2 : σ (TRecDiv) ∆ + [f : ∀α.(x : π) → σ] • Γ + [x : π] • Σ e 1 : σ σ Γ +[x:π] σ CheckTermination(rec f α (x : π) : σ = e 1 ) σ = (βτ, , γ) ∆ + [f : ∀α.(x : π) → σ] • Γ • Σ e 2 : σ α ∈ Γ α ∈ ∆ ∆ • Γ • Σ rec f α (x : π) : σ = e 1 in e 2 : σ (TRec) ∆ • Γ • Σ a : (βBool, ∅, ∅) ∆ • Γ • Σ e 2 : σ ∆ • Γ • Σ if a then absurd else e 2 : σ (TIfAbsurd1) ∆ • Γ • Σ a : (βBool, ∅, ∅) ∆ • Γ • Σ e 1 : σ ∆ • Γ • Σ if a then e 1 else absurd : σ (TIfAbsurd2) ∆ • Γ • Σ a : (ghost Bool, ∅, ∅) ∆ • Γ • Σ e 1 : σ 1 ∆ • Γ • Σ e 2 : σ 2 ∆ • Γ • Σ if a then e 1 else e 2 : (σ 1 ∪ σ 2 ) (TIfGhost) ∆ • Γ • Σ a : (reg Bool, ∅, ∅) ∆ • Γ • Σ e 1 : σ 1 ∆ • Γ • Σ e 2 : σ 2 ∆ • Γ • Σ if a then e 1 else e 2 : σ 1 ∪ σ 2 (TIf) ∆ • Γ • Σ e 1 : (β 1 τ 1 , 1 , γ 1 ) β 1 β ∆ • Γ + [x : βτ 1 ] • Σ e 2 : (π 2 , 2 , γ 2 ) ∆ • Γ • Σ let βx = e 1 in e 2 : (π 2 , 1 ∪ 2 , γ 1 ∪ γ 2 ) (TLet)
Figure 3.10: Typing rules for expressions (1/2). The interesting cases happen when expression e is a variable or an assignment. The other cases are either immediate or can be deduced from the induction hypotheses. In the cases of fun..in, rec..in, let..in, and try..with expressions we additionally need to use standard weakening and permutations lemmas [START_REF] Pierce | Types and Programming Languages[END_REF], which pose no particular difficulty, and so we do not detail them here.

∆ • Γ • Σ e : σ θ = [α → τ] ρ = [x → ā] α = τ x = ā ∆(f) = ∀α.(x : βτ ) → σ ∀i. ∆ • Γ • Σ a i : (β i τ i θ, ∅, ∅) ∀i. β i β i ∆ • Γ • Σ f τ (ā) : σθρ (TApp) θ = [α → τ] ρ = [x → ā] α = τ x = ā ∆(f) = (x : βτ ) → σ ∀i.∆ • Γ • Σ a i : (β i τ i θ, ∅, ∅) ∃i. β i β i ∆ • Γ • Σ f τ (ā) : (σθρ) (TAppGhostArg) exception E : (β 1 τ 1 . . . β n τ n ) ∀i. ∆ • Γ • Σ a i : (β i τ i , ∅, ∅) ∀i. β i β i ∆ • Γ • Σ raise Eā : (reg τ, raises E, ∅) (TRaiseReg) exception E : (β 1 τ 1 . . . β n τ n ) ∀i. ∆ • Γ • Σ a i : (β i τ i , ∅, ∅) ∃i. β i β i ∆ • Γ • Σ raise Eā : (ghost τ, ∅, raises E) (TRaiseGhost) ∆ • Γ • Σ e 0 : (π, , γ) ∀i. E i : π i ∀i. ∆ • Γ + [x : π i ] • Σ e i : σ i ∆ • Γ • Σ try e 0 with Ex ⇒ e : (π, -raises E i , γ -raises E i ) ∪ i σ i (TTry) type T α = {f : β f τ f } ∀i. ∆ • Γ • Σ a i : (β i τ f i [α → τ], ∅, ∅) ∀i. β i β f i ∆ • Γ • Σ {f = a} : (reg T τ, ∅, ∅) (TRecord) type T α = {f : β f τ f } ∀i. ∆ • Γ • Σ a i : (β i τ f i [α → τ], ∅, ∅) ∃i. β f i β i ∆ • Γ • Σ {f = a} : (ghost T τ, ∅, ∅) (TRecordGhost) type T α = {. . . , f : β f τ f , . . .} ∆ • Γ • Σ a : (reg T τ, ∅, ∅) ∆ • Γ • Σ a.f : (β f τ f [α → τ], ∅, ∅) (TGet) type T α = {. . . , f : β f τ f , . . .} ∆ • Γ • Σ a : (ghost T τ, ∅, ∅) ∆ • Γ • Σ a.f : (ghost τ f [α → τ], ∅, ∅) (TGetGhost) type T α = {. . . , f : ghost τ f , . . .} ∆ • Γ • Σ a l : (β l T τ, ∅, ∅) ∆ • Γ • Σ a r : (β r τ f [α → τ], ∅, ∅) ∆ • Γ • Σ a l .f ← a r : (Unit, ∅, ∅ + writes a l .f) (TAssignGhostField) type T α = {. . . , f : reg τ f , . . .} ∆ • Γ • Σ a l : (ghost T τ, ∅, ∅) ∆ • Γ • Σ a r : (β r τ f [α → τ], ∅, ∅) ∆ • Γ • Σ a l .f ← a r : (Unit, ∅, ∅ + writes a l .f) (TAssignGhost) type T α = {. . . , f : reg τ f , . . .} ∆ • Γ • Σ a l : (regT τ, ∅, ∅) ∆ • Γ • Σ a r : (regτ f [α → τ], ∅, ∅) ∆ • Γ • Σ a l .f ← a r : (Unit, ∅ + writes a l .f, ∅) (TAssign)

case (TVar). The typing derivation ends up with

Γ (y) = π ∆ • Γ + [x : βτ] • Σ y : (π, ∅, ∅)
If variable y does not occur in x, the result follows immediately. On the other hand, if y is equal to some x i , then we have

y[x → ā] ≡ a i
The following holds by the hypotheses of the lemma

∆ • Γ • Σ a i : β τ i , ∅, ∅
which concludes this case.

case (TAssign). The typing derivation ends up with

type T α = {. . . , f : reg τ f , . . .} ∆ • Γ + [x : βτ] • Σ a l : (regT τ, ∅, ∅) ∆ • Γ + [x : βτ] • Σ a r : (reg τ f [α → τ], ∅, ∅) ∆ • Γ + [x : βτ] • Σ a l .f ← a r : (Unit, ∅ + writes a l .f, ∅)
Since e is an effectful expression, we have β β, i.e., the atoms a l and a r can only be replaced by regular expressions. This prevents us from substituting regular locations by ghost ones, which would introduce a new ghost effect and eliminate an existing regular effect. By IH

∆ • Γ • Σ a l [x → ā] : (regT τ, ∅, ∅) ∆ • Γ • Σ a r [x → ā] : (reg τ f [α → τ], ∅, ∅)
We can build the following derivation:

type T α = {. . . , f : reg τ f , . . .} ∆ • Γ • Σ a l [x → ā] : (regT τ, ∅, ∅) ∆ • Γ • Σ a r [x → ā] : (reg τ f [α → τ], ∅, ∅) ∆ • Γ • Σ a l [x → ā].f ← a r [x → ā] : (Unit, ∅ + writes a l .f, ∅)[x → ā]
which completes this case.

case (TAssignGhost). The typing derivation ends up with

type T α = {. . . , f : reg τ f , . . .} ∆ • Γ + [x : βτ] • Σ a l : (ghost T τ, ∅, ∅) ∆ • Γ + [x : βτ] • Σ a r : (β r τ f [α → τ], ∅, ∅) ∆ • Γ + [x : βτ] • Σ a l .f ← a r : (Unit, ∅, ∅ + writes a l .f)
We can only replace a l by another atomic ghost expression a i . Otherwise, we would be introducing a regular effect via a ghost assignment (aliasing), which conflicts with our global condition of admissible effects. By IH

∆ • Γ • Σ a l [x → ā] : (ghostT τ, ∅, ∅) ∆ • Γ • Σ a r [x → ā] : (β r τ f [α → τ], ∅, ∅)
We can build the following derivation:

type T α = {. . . , f : reg τ f , . . .} ∆ • Γ • Σ a l [x → ā] : (ghost T τ, ∅, ∅) ∆ • Γ • Σ a r [x → ā] : (β r τ f [α → τ], ∅, ∅) ∆ • Γ • Σ a l .f[x → ā] ← a r [x → ā] : (Unit, ∅, ∅ + writes a l .f)[x → ā]
which completes this case.

case (TAssignGhostField). The typing derivation ends up with

type T α = {. . . , f : ghost τ f , . . .} ∆ • Γ + [x → ā] • Σ a l : (β l T τ, ∅, ∅) ∆ • Γ + [x → ā] • Σ a r : (β r τ f [α → τ], ∅, ∅) ∆ • Γ + [x → ā] • Σ a l .f ← a r : (Unit, ∅, ∅ + writes a l .f) By IH ∆ • Γ • Σ a l [x → ā] : (β l T τ, ∅, ∅) ∆ • Γ • Σ a r [x → ā] : (β r T τ, ∅, ∅)
We can build the following derivation:

type T α = {. . . , f : ghost τ f , . . .} ∆ • Γ • Σ a l [x → ā] : (β l T τ, ∅, ∅) ∆ • Γ • Σ a r [x → ā] : (β r τ f [α → τ], ∅, ∅) ∆ • Γ • Σ a l [x → ā].f ← a r [x → ā] : (Unit, ∅, ∅ + writes a l .f)[x → ā]
which completes this case.

A last step before stating the preservation property is to show that if an expression e is typeable under a certain store typing Σ, then we can extend Σ with fresh bindings and e is still typeable.

Lemma 3.2.7 (Extension of Store Typing Context

). If ∆ • Γ • Σ e : σ then ∆ • Γ • Σ e : σ, where Σ ⊆ Σ , i.e., dom(Σ) ⊆ dom(Σ ) and ∀ l ∈ dom(Σ). Σ (l) = Σ(l). Proof. Straightforward induction on ∆ • Γ • Σ e : σ.
We can finally state the preservation lemma, as follows:

case (EvalGhost). Semantics evaluation ends up with

δ • µ • e ⇓ µ • r δ • µ • ghost e ⇓ µ • r
The typing derivation ends up with

∆ • ∅ • Σ e : (βτ, , γ) ∆ • ∅ • Σ ghost e : (βτ, , γ) By IH ∆ • ∅ • Γ r : (β τ, , γ )
where β β, ⊆ , γ ⊆ γ, Σ ⊆ Σ , and Σ µ .

case (EvalLoop). Semantics evaluation ends up with

δ • µ • let reg _ = e in loop e ⇓ µ • r δ • µ • loop e ⇓ µ • r
We distinguish two cases depending on which rule was used at the conclusion of the type derivation.

• case (TLoop): typing derivation ends up with

CheckTermination(loop e) ∆ • ∅ • Σ e : (Unit, , γ) ∆ • ∅ • Σ loop e : (reg τ, , γ)
The following judgment is valid:

∆ • ∅ • Σ e : (Unit, , γ) ∆ • [_ : reg Unit] • Σ loop e : (reg τ, , γ) ∆ • ∅ • Σ let reg _ = e in loop e : (reg τ, , γ) By IH ∆ • ∅ • Σ r : (reg τ, , γ )
where ⊆ , γ ⊆ γ, Σ ⊆ Σ , and Σ µ .

• case (TLoopDiv): similar to the previous case.

case (EvalLetAbort). Semantics evaluation ends up with

δ • µ • e 1 ⇓ µ • r abort r δ • µ • let βx = e 1 in e 2 ⇓ µ • r
We can type an abort response with any type so we trivially have that

∆ • ∅ • Σ r : (reg τ 2 , ∅, ∅)
case (EvalLet). Semantics evaluation ends up with

δ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ µ • r δ • µ • let βx = e 1 in e 2 ⇓ µ • r
Typing derivation ends up with

∆ • ∅ • Σ e 1 : (β 1 τ 1 , 1 , γ 1 ) β 1 β ∆ • [x : βτ 1 ] • Σ e 2 : (β 2 τ 2 , 2 , γ 2 ) ∆ • ∅ • Σ let βx = e 1 in e 2 : (β 2 τ 2 , 1 ∪ 2 , γ 1 ∪ γ 2 ) By IH ∆ • ∅ • Σ ν : (β 1 τ 1 , 1 , γ 1 )
where

β 1 β 1 , 1 ⊆ 1 , γ 1 ⊆ γ 1 , Σ ⊆ Σ ,
∆ • ∅ • Σ e 2 [x → ν] : (β 2 τ 2 , 2 , γ 2 )
where

β 2 β 2 , since β 1 β 1 . By IH ∆ • ∅ • Σ r : (β 2 τ 2 , 2 , γ 2 )
where

β 2 β 2 (by transitivity), 2 ⊆ 2 ⊆ 1 ∪ 2 , γ 2 ⊆ γ 2 ⊆ γ 1 ∪ γ 2 , Σ ⊆ Σ , and Σ µ .
case (EvalApp). Semantics evaluation ends up with

δ(f) = (x : βτ, e) x : π = ν δ • µ • e[x → ν] ⇓ µ • r δ • µ • f(ν) ⇓ µ • r
We distinguish two cases depending on which rule was used at the conclusion of the type derivation.

• case (TApp): typing derivation ends up with where β e β e , e ⊆ e , γ e ⊆ γ e , Σ ⊆ Σ , and Σ µ .

θ = [α → τ] ρ = [x → ā] ∆(f) = ∀α.(x : βτ) → (β e τ e , e , γ e ) ∀i. ∆ • ∅ • Σ ν i : (β i τ i θ, ∅, ∅) β i β i ∆ • ∅ • Σ f (ν) : (β e τ e ,
• case (TAppGhostArg): similar to the previous case. The only difference is that the mask of r obtained after applying the substitution lemma and the induction hypotheses is trivially less ghost than the mask of (σθρ).

cases (EvalFun), (EvalRec), (EvalIfTrue), (EvalIfFalse), and (EvalTry).

Straightforward induction over the sub-expressions.

case (EvalTryAbort). Semantics evaluation ends up

δ • µ • e ⇓ µ • r abort r ∀i. E i = r δ • µ • try e 0 with Ex ⇒ e ⇓ µ • r
We can type an abort response with any type so we trivially have that

∆ • ∅ • Σ r : (π, ∅, ∅)
case (EvalTryExn). Semantics evaluation ends up with

δ • µ • e 0 ⇓ µ • raise E ν E = E i ∀j. j < i → E = E j δ • µ • e i [x → ν] ⇓ µ • r δ • µ • try e 0 with Ex ⇒ e ⇓ µ • r
The typing derivation ends up with

∆ • Γ • Σ e 0 : (π, , γ) ∀i. E i : π i ∀i. ∆ • Γ + [x : π i ] • Σ e i : σ i ∆ • Γ • Σ try e 0 with Ex ⇒ e : (π, -raises E i , γ -raises E i ) ∪ i σ i
We have trivially that

∆ • ∅ • Σ raise E ν : (π, ∅, ∅) where Σ ⊆ Σ ,
∆ • ∅ • Σ e i [x → ν] : (β i τ i , i , γ i )
where β i is a less ghost mask than the mask of σ i , since the sequence ν is typed with a completely regular mask. By IH

∆ • ∅ • Σ r : (β i τ i , ∅, ∅)
where β i β i , Σ ⊆ Σ , and Σ µ .

cases (EvalGet) and (EvalAssign). Trivial.

case (EvalErr). Impossible.

Using the progress and preservation results, we can now prove the desired type soundness theorem. We state that a well-typed closed expression cannot evaluate down to an error. This is strongly inspired by Theorem 2 of the paper Pretty-Big Step Semantics, by Arthur Charguéraud [START_REF] Charguéraud | Pretty-big-step semantics[END_REF].

Theorem 3.2.9 (Type Soundness). If ∆ • ∅ • Σ e : σ then 1. ¬(δ • µ • e ⇓ µ • err) 2. ¬(δ • µ • e ⇓ co µ • err) Proof.
1. By induction on ∆ • ∅ • Σ e : σ, we apply the progress lemma to establish that a well-typed sub-expression can be (co-)evaluated. Since err is not a well-typed expression, it is never the case that err is evaluated. If a well-typed sub-expression evaluates down to a semantic result, then we use preservation to show that it evaluates down to a well-typed result. Since err is not a well-typed expression, it cannot be the result of an evaluation.

By lemma 3.2.3, either

δ • µ • e ⇓ µ • r or δ • µ • e ⇓ co div.
If it is the latter, there is nothing to prove. In the former case, the proof follows by the previous point.

Discussion and Related Work

A language with ghost code. In this chapter, we presented and formalized KidML, a programming language of the ML family whose most distinguish feature is the interaction between ghost code and stateful traits, such as assignments and divergence. The presence of ghost code makes KidML suitable to be used as the programming language of a deductive verification framework.

In the activity of applying deductive techniques to the verification of programs, we often need to introduce auxiliary elements, e.g., functions or variables, with the purpose to make the proof effort much easier and sometimes, namely in an automated proof setting, to make it possible. This is exactly the role of ghost code. Ghost elements are removed by a code extraction mechanism, which generates a correct-by-construction, executable program. The notion of ghost code dates back to the origins of deductive verification itself. The first acknowledged use of auxiliary variables, the most primitive form of ghost code, is due to Lucas in 1968 [START_REF] Lucas | Two constructive realizations of the block concept and their equivalence[END_REF]. From that moment on, ghost code evolved into a standard component of deductive verification tools. We can cite VCC [START_REF] Cohen | VCC: A practical system for verifying concurrent C[END_REF], Dafny [START_REF] Rustan | Dafny: An automatic program verifier for functional correctness[END_REF], Viper [START_REF] Müller | Viper: A verification infrastructure for permission-based reasoning[END_REF], and Why3 itself as verification platforms that allow users to augment a program with some form of ghost code. The crucial property that all these tools statically guarantee is that ghost code does not interfere with regular data. This is also the case with KidML, with our type system featuring a clear separation between regular and ghost effects, as well as the global condition of admissible effects. In particular, we forbid assignments over regular locations when these are aliased with ghost ones.

A language with effects. KidML is a stateful language. It features memory assignment, raise and catch of exceptions, and divergence. The simultaneous use of imperative and ghost code is what makes the design of KidML type system a challenging task. In our design of such a type system, we chose to treat effects via oracle functions. This is mostly evident in two situations: when it comes to prove the termination of a certain expression using the CheckTermination predicate (we recall that the evaluation of some piece of ghost code must always terminates, i.e., a ghost expression can never be assigned the divergence effect); in the characterization of admissible effects via the adm predicate, where we state that no assignment on a regular location is allowed when this is aliased with a ghost one, without giving a precise definition of what it means for two memory locations to be aliased.

A comprehensive treatment of effects in the WhyML language is given by Jean-Christophe Filliâtre, Léon Gondelman [START_REF] Gondelman | A Pragmatic Type System for Deductive Software Verification[END_REF]Chap.3] and Andrei Paskevich [START_REF] Filliâtre | A pragmatic type system for deductive verification[END_REF]. The authors present a type and effect system with regions, an artifact that is used to statically track all the memory aliases occurring in a given program. The developed type system uses singleton regions and gives strong guarantees about the separation of function arguments and return value. Each type definition is annotated with a set of regions, one for each mutable component of such a type. In order to statically know all the existing aliases in the program, this approach cannot be applied to arbitrary pointer-based data structures, such as an union-find implementation 4 , since the set of involved regions is not statically bound. In the design of KidML, we abstract away many of the details of this precise characterization of effects and memory aliases, since these are orthogonal to our intended use of the language. It would be interesting to study if the proposed approach can be applied to a language of the size of KidML.

Differences with respect to WhyML. The KidML language draws inspiration from WhyML. Features like ghost code, recursive function definitions, stateful computations, top-level definition of record types, and exceptions treatment can be found in both languages. In fact, KidML is very close to the internal representation of a WhyML program. For instance, our separation in different syntactic categories of local variable binding and local (recursive) functions definitions can also be found in the internal representation of WhyML. There are, nevertheless, some WhyML interesting features that we did not include in the design of KidML. We detail some of main differences between KidML and WhyML in the following.

A key aspect in the practicality of functional programming languages is the presence of pattern matching [START_REF] Le | Optimizing pattern-matching[END_REF][START_REF] Maranget | Compiling pattern matching to good decision trees[END_REF]. This is a mostly appreciated feature when it comes to manipulate recursivelydefined data types, e.g., algebraic data types. WhyML features top-level definition of algebraic data types, as well as a match..with construction to destruct values of such types [START_REF] Paskevich | Algebraic types and pattern matching in the logical language of the Why verification platform (version 2)[END_REF]. We do not include pattern matching in KidML, as we believe that this would unnecessarily complicate the design and formalization of the language, without adding expressiveness power.

One can rely on the WhyML module system to break the program components into smaller individual units. These units can then be linked together using the Why3 data refinement mechanism. In Chap. 6, we provide several examples of programs implemented and proved using Why3, where a key component is the use of WhyML modules to separate a client code from the implementation details of a provided module. The KidML language does not feature any kind of separation on the program components, other than the local definition of functions. Equipping KidML with a module system and a module refinement mechanism, in the style of WhyML, would certainly approach KidML to a more realistic programming language.

Finally, an important aspect of WhyML is that it features a lightweight form of higher-order programming. One can pass functions as arguments of other functions, return functions as the result of some computation, and define anonymous functions, as long as all of those remain stateless functions. Additionally, as it is commonly the case in a higher-order setting, WhyML allows the user to partially apply some functional symbol, which generates a new function that can be manipulated, e.g., to be passed as an argument to a higher-order traversal procedure. In KidML, every function application is total, i.e., the programmer must always supply the exact number of attended arguments. To extend KidML with some form of higher-order programming, we would need to add functions as a possible result of evaluation, and we most certainly would have to reconsider the design of our type with effects system, in order to limit the interaction between higher-order programming and stateful code.

The finger pointing at the moon is not the moon.

Buddhist saying 4 Extraction

In this chapter, we design a code extraction procedure for KidML programs. The main task of extraction is the removal of any ghost element from the source code. Extraction is guided by the KidML type system, which guarantees that we only apply our extraction function to regular expressions, i.e., expressions that are meaningful from an operational point of view. We present our extraction function in Sec. 4.1. One novelty of our extraction function, with respect to other existing similar presentations [START_REF] Filliâtre | The spirit of ghost code[END_REF], is that we erase as much as possible, i.e., without compromising the soundness of the extracted code, any trace of ghost code. This adds some difficulty in our presentation and formalization, but we believe this is worth the effort in order to obtain a more efficient extracted code.

An important aspect of an extracted program is that it preserves the behavior of the original source program. This holds both from a typing, as well as from a semantics point of view. The former means that a well-typed regular expression is extracted into a well-typed expression. In particular, the extracted expression is typed with the same regular effects and with an empty set of ghost effects. The latter means that if the original source code diverges, then the extracted code diverges as well; and if the original source code evaluates down to a semantics result, then the extracted expression evaluates down to the extraction of that result. In Sec. 4.2 and 4.3, we present the proofs of typing and semantics preservation, respectively.

The extraction procedure we present in this chapter is a representative part of the Why3 extraction mechanism, which we have implemented in the course of this thesis. In Sec. 4.4, we describe the implemented extraction machinery, with an highlight on the adopted architecture, which clearly separates code translation from the printing phase, and the use of the new Why3 extract command. Sec. 4.5 concludes this chapter with some related work and discussion. We detail on some of the main differences between the extracted function defined in this chapter and the one we actually implemented.

Extraction Function

In this section, we define our extraction function over KidML types, top-level declarations (exceptions and record-types), and finally over well-typed regular expressions. In the following, we denote our extraction function by E β (•) , meaning extraction is parameterized by a mask β.

Extraction of Types

We begin by defining what it means to extract a type π, i.e., a type of the form β τ. The idea is to remove some of the type components, according to the mask passed as an argument to the extraction function. Definition 4.1.1 (Extraction of π). We define the extraction of type π by induction over its structure, as follows:

β β E β (β τ) E β (β τ) Unit if ∀i. β i = ghost β τ β otherwise
The pre-condition on the left ensures that we only apply extraction with a mask β of same length and that is at least as ghost as mask β . The notation β τ β means that we filter the elements of sequence β τ with respect to the regular elements of mask β. In other words, we keep the β i τ i pairs such that β i = reg and all the ghost components of type β τ are removed. For instance, the result of E (ghost, reg) (reg Int, reg Bool) is the type reg Bool. As we shall see later, this is useful when extracting a function body against the type declared in the function signature. We lift function E β (•) to the level of σ types, as follows:

Definition 4.1.2 (Extraction of σ). β β E β (β τ, , γ) E β (β τ, , γ) (E β (β τ), , ∅)
The most important point about this definition is that the extracted type contains no ghost effects. This conforms to the fact that extracted expressions cannot contain any such effect, since every piece of ghost code is removed. We present, as well, the following auxiliary definition: Definition 4.1.3 (Ghost status of σ). We define the ghost status of a type σ as follows:

G(σ) G(βτ, , γ) ghost if ∀i. β i = ghost ∧ = ∅ reg otherwise
When defining extraction for KidML expressions, we will be using G(•) to decide which subexpressions we must extract. Following this definition, an extractable expression is one that is typed with a type σ such that G(σ) = reg. An expression is thus extractable if its mask is not entirely ghost, or it is a stateful expression. Note that, for instance, an expression to which we assign type (Unit, ∅, ∅) is considered a ghost expression, and so it can be completely removed.

Extraction of Top-level Declarations

KidML top-level declarations include the declaration of an exception name and arguments, as well as the declaration of new record types. We define the extraction of such KidML symbols, as follows:

Extraction of Expressions

We define now our procedure to extract KidML expressions. We separate this definition into two distinct functions. First, we define the extraction of semantic results that satisfy the abort predicate, as follows: Definition 4.1.5 (Extraction of abort results). Let r be a semantic result such that abort r. We define the extraction of r as follows:

abort r E β (e) E β (absurd) absurd E β (div) div E β (raise Eν) raise Eν M(E)
The interesting aspect about the extraction of such expressions is that it completely ignores the argument mask. For the first two cases, the extraction function actually acts like the identity function. For the extraction of raise expressions, we filter the sequence ν of arguments using M(E), the mask of exception E. Such a mask corresponds to the sequence of ghost status assigned to the arguments in the exception declaration. For instance, if we return to the previous example of the Return exception, result of extracting the KidML program We can observe here the coherence in the number and type of arguments of the extracted exception declaration, and the extracted raise expression. The extraction of an exception applied to a sequence ā of atomic variables follows the exact same approach:

E β (raise Eā) raise Eā M(E)
We now move to the definition of the extraction function over KidML expressions. The complete definition of this function is given in Fig. 4.1. The conditions on the top-left corner of the definition specify that this function can only be applied to well-typed regular KidML expressions. Moreover, the mask β given as an argument of extraction, and the mask β assign by the KidML type system to the expression being extracted, must verify the property β β. Throughout the definition of E β (e), we use notation E(e) to denote the extraction of expression e under its own mask. In the following, we elaborate on the extraction of each syntactic category.

Extraction of sequence of atomic expressions. Extracting a sequence ā of atoms amounts to a filter of this sequence according to mask β. Given the pre-condition of this function, β is at least as ghost as the mask of ā, hence every ghost variable of ā does not occur in ā β . If we consider, for instance, the extraction of expression KidML (x, 42, x) where x is a ghost variable, only two masks can be used: either β = ghost, reg, ghost, in which case the result of extraction is the constant 42; or β = ghost, i.e., β is a completely ghost mask, in which case the extracted expression corresponds to the unit value ().

Extraction of function definitions. When it comes to extract an expression of the form

fun f α (x : π) : σ = e 1 in e 2
if f is a ghost function, the result of extraction is simply the extraction of expression e 2 under mask β. On the other hand, if f is regular, we recursively extract the body e 1 with the mask given in type σ. We assume σ to be equal to (β τ, , γ). We also extract the arguments and the return type σ. The following KidML program Every application of f inside expression e is typed as a ghost expression, by rule (TApp). Consequently, there remain no occurrences of f in E β (e). Extraction of recursive definitions follows the exact same pattern.

∆ • Γ • Σ e : (β τ, , γ), G(β , ) = reg, β β E β (e) E β (ā) ā β E β fun f α (x : π) : σ = e 1 in e 2        E β (e 2 ) if G(σ) = ghost fun f α (E(x : π)) : E(β τ, , γ) = E β (e 1 ) in E β (e 2
) otherwise

E β rec f α (x : π) : σ = e 1 in e 2        E β (e 2 ) if G(σ) = ghost rec f α (E(x : π)) : E(β τ, , γ) = E β (e 1 ) in E β (e 2 ) otherwise E β (let β x = e 1 in e 2 )                              E β (e 2 ) if G(e 1 ) = ghost E ghost (e 1 ) if G(e 2 ) = ghost let reg _ = E ghost (e 1 ) in E β (e 2 ) if β = ghost ∧ 1 = ∅ E β (e 2 ) if x / ∈ FV(E β (e 2 )) ∧ 1 = ∅ let reg x β = E β (e 1 ) in E β (e 2 ) otherwise E β (f τ (ā)) let reg x = f τ (ā β ) in x β if ∆(f) = (x : β τ) → σ E β (
E reg (a.f) a.f E ghost ({f = a}) () E reg ({f = a}) {f = a} M(f) E β (a l .f ← a r ) a l .f ← a r E β (raise Eā) raise Eā M(E)
E β (try e 0 with Ex ⇒ e) try E β (e 0 ) with E β (Ex ⇒ e) The next case is triggered when e 2 is a ghost expression. We know that e 1 is a stateful expression, otherwise the whole let..in would be typed as ghost expression. In this case, the result of e 1 is meaningless, and so we extract this expression under a completely ghost mask. If we consider the following KidML program KidML let reg _ = a.c <-42 in ghost 0 where c is a regular field of some record type, it extracts to a.c <-42.

The third case corresponds to the extraction of a let..in expression where e 2 is a regular expression and e 1 is a stateful expression, even if it is assigned a completely ghost mask. In such a case, we erase the results of e 1 , by extracting it with a completely ghost mask, and then extract e 2 as expected. As an example, the following KidML program This highlights a subtle point of the KidML language and your extraction procedure: even if we declare x as ghost variable, it does not necessarily mean that such a variable is bound to a ghost expression. This differs from the following expression KidML let ghost x = ghost e in the sense that now the whole expression e is typed as a ghost expression. In particular, there can be no regular effects in e and so the program The final case is, perhaps, the one that happens most commonly in practice. In this case, both e 1 and e 2 are regular expressions. We proceed by extracting e 2 with the argument mask of extraction, and e 1 with the mask assigned in the return type of f. The sequence of variables x is also filtered according to this mask. As an example of such case, let us consider the following KidML program: When it comes to extract the definition of g, we need to extract the application f (z, 42) under the mask assigned in the return type of g. In order to take this mask into account, we locally bind the regular results of the application, and then filter the sequence of bound variables using the mask of the function return type. The extraction of function g is as follows: The extraction of a loop e expression is straightforward. We simply recursively extract e, the body of the loop, with its own mask. Since e is always typed as a Unit expression, this is actually the empty mask.

Extraction of conditional expressions. The extraction of if..then..else expressions follows a very simple approach, where sub-expressions are recursively extracted using the mask that is initially given as an argument. The interesting aspect of this rule is the case when one of the branches is absurd and the test is a ghost expression. In that case, and according to typing rules (TIfAbsurd1) and (TIfAbsurd2), we do not need to keep the conditional structure in the extracted expression.

Extraction of access, creation, and assignment of record fields. The extraction of recordrelated expressions is straightforward. In the case of access and creation expressions, we can only a apply either a completely (singleton) ghost mask or a regular one. In the first case, both expressions are translated into the unit value (). On the other hand, extraction function just behaves as the identity function for access, and filters the pairs f i = a i according to the mask M(f). This mask stands for the mask that is assigned to the fields in the declaration of the associated record type.

For instance, the following KidML program As we did for exceptions, we insist on the coherence between the extracted declaration of type t and the expression that creates a value of such type.

Given the pre-condition on the top-left corner of Fig. 4.1, we know we can only extract regular expressions. When it comes to extract assignment expressions, we know this can only be a regular assignment, which we must keep in the extracted program. Hence, the extraction function behaves as the identity function for assignments.

Extraction of exception raising. The extraction of a raise expression, where the exception is applied to a sequence ā of atomic variables, is similar to the extraction of a semantics result of the form raise Eν. It ignores mask β and filters the sequence ā according to the mask of exception E.

Extraction of exception handling. The last case in our definition of the extraction function concerns the extraction of try..with expressions. We first recursively extract the body of the handler, expression e 0 , with mask β. Next, we must extract each branch of the handler individually. Naively, we could simply apply the extraction function over each expression e i . However, some of these might very well be ghost expressions. Let us take a moment in order to clarify such a possibility. Consider, for instance, the following KidML program: Even if the only branch of this handler is a ghost expressions, rule (TTry) assigns a regular type to the whole try..with expression. This is due to the assignment r.c <-42, a regular effect that we must preserve in the extracted program. To extract each branch of an exception handler, we proceed as follows. If the branch is a completely ghost expression, we replace it with the unit value,

G(e i ) = ghost E β (Ex ⇒ e i ) Ex M(E) ⇒ ()
where G(e i ) = ghost is a notation shortcut which we use to state expression e i is a ghost expression. Let us note that, even if e i is a ghost expression, we must preserve the branch structure to catch exception E i . Otherwise, if expression e 0 raises E i , the extracted code would be introducing a regular effect that did not existed in the source code.

If e i is a regular expression, we straightforwardly extract it using mask β, as follows:

G(e i ) = reg E β (Ex ⇒ e i ) Ex M(E) ⇒ E β (e i )
The result of extracting the above KidML program is as follows:

∆ • Γ + [x : π] • Σ e 1 : (β 1 τ, 1 , γ 1 ) (β 1 τ, 1 , γ 1 ) Γ +[x:π] (β 0 τ, 1 , γ 1 ) ∆ + [f : ∀α.(x : π) → (β 0 τ, 1 , γ 1 )] • Γ • Σ e 2 : (β τ, , γ) α#Γ α#∆ ∆ • Γ • Σ fun f α (x : π) : (β 0 τ, 1 , γ 1 ) = e 1 in e 2 : (β τ, , γ)
We distinguish two sub-cases:

• sub-case G(β 0 , 1 ) = ghost: by hypotheses we have G(β , ) = reg and β β, so the recursive call E β (e 2 ) respects the pre-conditions.

• sub-case G(β 0 , 1 ) = reg: as for the previous case, it is easy to conclude that the recursive call to E β 0 (e 1 ) respects the pre-conditions.

We have (β 1 τ, 1 , γ 1 ) (β 0 τ, 1 , γ 1 ), in particular β 1 β 0 . We can thus conclude that the recursive call E β 0 (e 1 ) respects the pre-conditions.

case (e ≡ rec f(x : π) : (β τ, , γ) = e 1 in e 2 ). Similar to the previous case.

case (e ≡ let β x = e 1 in e 2 ). The typing derivation ends up with

∆ • Γ • Σ e 1 : (β 1 τ 1 , 1 , γ 1 ) β 1 β ∆ • Γ + [x : β τ 1 ] • Σ e 2 : (π 2 , 2 , γ 2 ) ∆ • Γ • Σ let β x = e 1 in e 2 : (π 2 , 1 ∪ 2 , γ 1 ∪ γ 2 )
We distinguish five sub-cases:

• sub-case G(β 1 , 1 ) = ghost: by hypotheses, we have G(π 2 , 2 ) = reg and so the call to E β (e 2 ) respects the pre-conditions.

• sub-case G(π 2 , 2 ) = ghost: expression e 1 must be an effectful expression, G(β 1 , 1 ) = reg.

We trivially have that β 1 ghost, so the call E ghost (e 1 ) respects the pre-conditions.

• sub-case β = ghost and obs( 1 ): by hypotheses we have G(π 2 , 2 ) = reg. Expression e 1 is an effectful expression, thus the call E ghost (e 1 ) respects the pre-conditions. Expression e 2 is a regular expression, and so the call E β (e 2 ) respects the pre-conditions.

• sub-case x / ∈ FV(E β (e 2 )) and ¬obs(e 1 ): similar to the case G(β 1 , 1 ) = ghost.

• otherwise, we have G(β 1 , 1 ) = reg and G(π 2 , 2 ) = reg. Since β 1 β , we conclude that the call E β (e 1 ) respects the pre-conditions. We can also easily conclude that the call E β (e 2 ) respects the pre-conditions.

case (e ≡ loop e ) We distinguish two sub-cases according to the typing rule used at the bottom of the derivation:

• case (TLoop): the typing derivation ends up with

CheckTermination(loop e) ∆ • Γ • Σ e : (Unit, , γ) ∆ • Γ • Σ loop e : (reg τ, , γ)
The call E(e ) trivially respects the pre-conditions, since e is a unit expression and we extract e with its own mask.

• case (TLoopDiv): similar to the previous case.

case (e ≡ if a then absurd else e 2 ). We distinguish two sub-cases according to the typing rule used at the bottom of the derivation:

• case (TIfAbsurd1): the typing derivation ends up with

∆ • Γ • Σ a : (β Bool, ∅, ∅) ∆ • Γ • Σ e 2 : (β 2 τ, 2 , γ 2 ) ∆ • Γ • Σ if a then absurd else e 2 : (β 2 τ, 2 , γ 2 )
where β 2 β and G(β 2 , 2 ) = reg. The recursive call E β (e 2 ) thus respects the pre-conditions.

• case (TIf ): typing derivation ends up with

∆ • Γ • Σ a : (reg Bool, ∅, ∅) ∆ • Γ • Σ absurd : (regτ, ∅, ∅) ∆ • Γ • Σ e 2 : (β 2 τ 2 , 2 , γ 2 ) ∆ • Γ • Σ if a then absurd else e 2 : (β 2 τ 2 , 2 , γ 2 )
The call E(absurd) trivially respects the pre-conditions. By hypotheses β 2 β and G(β 2 , 2 ) = reg so the call E β (e 2 ) respects the pre-conditions.

case (e ≡ if a then e 1 else absurd). Similar to the previous one.

case (e ≡ if a then e 1 else e 2 ). The typing derivation ends up with

∆ • Γ • Σ a : (reg Bool, ∅, ∅) ∆ • Γ • Σ e 1 : (β 1 τ, 1 , γ 1 ) ∆ • Γ • Σ e 2 : (β 2 τ, 2 , γ 2 ) ∆ • Γ • Σ if a then e 1 else e 2 : ((β 1 β 2 )τ, 1 ∪ 2 , γ 1 ∪ γ 2 )
where G(β 1 β 2 , 1 ∪ 2 ) = reg and β 1 β 2 β. In particular,

G(β 1 , 1 ) = reg G(β 2 , 2 ) = reg β 1 β β 2 β
The recursive calls E β (e 1 ) and E β (e 2 ) thus respect the pre-conditions. The call E(a) also respects the pre-conditions since a is a regular expression and we use its own mask to extract it.

case (e ≡ try e 0 with Ex ⇒ e ). The typing derivation ends up with

∆ • Γ • Σ e 0 : (π, , γ) ∀i. E i : π i ∀i. ∆ • Γ + [x : π i ] • Σ e i : σ i ∆ • Γ • Σ try e 0 with Ex ⇒ e : (π, -raises E i , γ -raises E i ) ∪ i σ i
where G(β 0 , ) = reg, β 0 β, and ∀i.β i β. It is easy to conclude that the call E β (e 0 ) respects the pre-conditions. The call E β (Ex ⇒ e) performs an individual extraction of the branches. For each ghost branch, we do not call the extraction function, so there is no violation of the preconditions. On the other hand, for each regular branch e i , we have G(β i , 1 ) = reg and so the call E β (e i ) respects the pre-conditions.

Typing Preservation under Extraction

In this section, we prove that our extraction function produces only well-typed, regular KidML expressions. Before demonstrating such a theorem, let us introduce some auxiliary properties about the masks comparison operator and the masks union operator . First, we show that defines a preorder, as established by the following property: Property 4.2.1 (Relation is a preorder).

1. β β.

2. if β 1 β 2 and β 2 β 3 , then β 1 β 3 .

Proof.

1. by the definition of .

2. by case analysis on β 3 .

We prove, as well, that is an Associative, Commutative, and Idempotent operator:

Property 4.2.2 (Operator is ACI). 1. (β 1 β 2 ) β 3 = β 1 (β 2 β 3 ). -sub-case β 1 = reg: since β β 1 β 2 then β 1 1 β 1 and β 1 2 β 1 . This gives β 1 1 = β 1 2 = β 1 = reg. The result of E β 1 (β 1 1 β 1
2 ) is then reg and using the IH we build the expected result:

regE β 2 ...βn (E β 2 ...βn (β 2 1 . . . β n 1 ) E β 2 ...βn (β 2 2 . . . β n 2 ))
To state the preservation theorem, we need the following auxiliary definitions:

Definition 4.2.1 (Typing context extraction). E(Γ ) E(∅) ∅ E(Γ + [x : ghost τ]) E(Γ ) E(Γ + [x : reg τ]) E(Γ ) + [x : reg τ] Definition 4.2.

(Typing functions context extraction)

.

E(∆) E(∅) ∅ E(∆ + [f : ∀α.(x : π) → σ]) E(∆) if G(σ) = ghost E(∆) + [f : ∀α.(E(x : π)) → E(σ)] otherwise
We do not need to define an extraction for a typing store context Σ. Since every location is assigned a regular type by our typing rules, such a function would just behave like the identity function. We finally state type preservation theorem as follows:

Theorem 4.2.6 (Type preservation under extraction). If ∆•Γ •Σ e : (β τ, , γ), where G(β τ, ) = reg, then for all β such that β β, E(∆) • E(Γ ) • Σ E β (e) : E β (β τ, , γ) holds.
Proof. Induction on the derivation ∆ • Γ • Σ e : (βτ , , γ). Throughout this proof we shall write

E(∆ • Γ • Σ) instead of E(∆) • E(Γ ) • Σ. case (TAbar). By IH ∀i. E(∆ • Γ • Σ) E β i (a i ) : (β i τ i , ∅, ∅)
for each a i s.t. G(a i ) = reg where β i β i . In particular, any ghost variable or any ghost location in ā is also removed in E(Γ ) and E(Σ), respectively. By taking only the judgments for which β i = reg we get exactly the subset ā β . The following judgment is then a valid judgment:

∀i. E(∆ • Γ • Σ) a i : (reg τ i , ∅, ∅) E(∆ • Γ • Σ) ā β : E β (reg τ, ∅, ∅)
case (TFun). The typing derivation ends up with

∆ • Γ + [x : β τ] • Σ e 1 : σ σ Γ +[x:pi] σ ∆ + [f : ∀α.(x : β τ) → σ] • Γ • Σ e 2 : σ α ∈ Γ α ∈ ∆ ∆ • Γ • Σ fun f α (x : β τ) : σ = e 1 in e 2 : σ
We distinguish two sub-cases:

• sub-case G(σ) = ghost: by the definition of E β (•), we have

E β (fun f α (x : β τ) : σ = e 1 in e 2 ) E β (e 2 )
By IH

E(∆ + [f : (x : β τ, σ)]) • E(Γ ) • E(Σ) E β (e 2 ) : E β (σ ) which simplifies to E(∆ • Γ • Σ) E β (e 2 ) : E β (σ )
• sub-case G(σ) = reg: by the definition of E β (•) we have

E β (fun f α (x : β τ) : (β τ, ) = e 1 ine 2 ) fun f α E(x : β τ) : E(β τ, ) = E β (e 1 )inE β (e 2 )
By IH

E(∆) • E(Γ + [x : β τ]) • E(Σ) E β (e 1 ) : E β (σ ) E(∆ + [f : ∀α.(x : β τ) → (β τ, , γ)]) • E(Γ ) • E(Σ) E β (e 2 ) : E β (σ )
By auxiliary lemma 4.2.4 E β (σ ) E β (σ), so the following judgment is valid:

E(∆) • E(Γ ) + [E(x : β τ)] • E(Σ) E β (e 1 ) : E β (σ ) E(∆) + [f : ∀α.E(x : β τ) → E(β τ, , γ)] • E(Γ ) • E(Σ) E β (e 2 ) : E β (σ ) E β (σ ) E(β τ, ) α ∈ E(Γ ) α ∈ E(∆) E(∆ • Γ • Σ) fun f α E(x : β τ) : E(β τ, ) = E β (e 1 ) in E β (e 2 ) : E β (σ )
cases (TRec) and (TRecDiv). Similar to the (TFun) case.

case (TApp).

The typing derivation ends up with

θ = [α → τ] ρ = [x → ā] ∆(f) = ∀α.(x : β τ) → (β σ τ σ , , γ) ∀i. ∆ • Γ • Σ a i : (β i τ i θ, ∅, ∅) ∀i. β i β i ∆ • Γ • Σ f τ (ā) : (β σ τ σ , , γ)θρ
By the definition of E β (•) we have

E β (f τ (ā)) let reg x = f τ (ā β ) in x β
In order to prove this case, we must build the following judgment:

E(∆ • Γ • Σ) f τ (ā β ) : E(β σ , τ σ , , γ)θρ θ = [α → τ] ρ = [x → ā] E(∆) • E(Γ ) + [x β : E β (β σ τ σ )] • E(Σ) x β : E β (β σ τ σ , ∅, ∅) E(∆ • Γ • Σ) let reg x = f τ (ā β ) in x β : E β (β σ τ σ , , γ)θρ (4.1) First, by IH ∀i. E(∆ • Γ • Σ) E β i (a i ) : E β i (β i τ i , ∅, ∅)
for each a i s.t. G(β i ) = reg. By taking only the judgments for which β i = reg, we get exactly the sequence ā β . Since β i β i , this a legal mask to extract each a i . Given that G(f) = reg, we have

E(∆)(f) = ∀α.E(x : β τ) → E(β σ τ σ , , γ)
so the following judgment is valid:

θ = [α → τ] ρ = [x → ā] E(∆)(f) = ∀α.E(x : β τ) → (E(β σ τ σ ), , ∅) ∀i. E(∆ • Γ • Σ) E β i (a i ) : E β i (β i τ i θ, ∅, ∅) E(∆ • Γ • Σ) f τ (ā β ) : (E(β σ , τ σ ), , ∅)
The premises E(β i ) E(β i ) are trivially true, since

β i = β i = reg.
Second, the judgment

E(∆) • E(Γ ) + [x β : E β (β σ τ σ )] • E(Σ) x β : (E β (β σ τ σ ), ∅, ∅)θρ
is also valid, with β begin a legal mask since β σ β. We can finally build the judgment (4.1), which concludes this case.

case (TIfAbsurd1). The typing derivation ends up with

∆ • Γ • Σ a : (βBool, ∅, ∅) ∆ • Γ • Σ e 2 : σ ∆ • Γ • Σ if a then absurd else e 2 : σ
We distinguish two sub-cases:

• β = ghost: by the definition of E β (•), we have E β (if a then e 1 else e 2 ) E β (e 2 ). By IH E(∆ • Γ • Σ) E β (e 2 ) : E β (σ)
which concludes this case.

• β = reg: by the definition of E β (•), we have

E β (if a then e 1 else e 2 ) if a then absurd else E β (e 2 ) By IH E(∆ • Γ • Σ) E β (e 2 ) : E β (σ)
so the following derivation is valid:

E(∆ • Γ • Σ) a : (reg Bool, ∅, ∅) E(∆ • Γ • Σ) e 2 : E β (σ) E(∆ • Γ • Σ) if a then absurd else E β (e 2 ) : E β (σ)
case (TIfAbsurd2). Similar to the previous one.

case (TIf )

. By the definition of E β (•), we have

E β (if a then e 1 else e 2 ) if E(a) then E β (e 1 ) else E β (e 2 )
By IH

E(∆ • Γ • Σ) E(a) : (reg Bool, ∅, ∅) E(∆ • Γ • Σ) E β (e 1 ) : E β (σ 1 ) E(∆ • Γ • Σ) E β (e 2 ) : E β (σ 2 )
By corollary 4.2.5.

1 E β (σ 1 ∪ σ 2 ) = E β (σ 1 ) ∪ E β (σ 2 ).
The following judgment is thus valid:

E(∆ • Γ • Σ) E(a) : (reg Bool, ∅, ∅) E(∆ • Γ • Σ) E β (e 1 ) : E β (σ 1 ) E(∆ • Γ • Σ) E β (e 2 ) : E β (σ 2 ) E(∆ • Γ • Σ) if E(a) then E β (e 1 ) else E β (e 2 ) : E β (σ 1 ) ∪ E β (σ 2 )
case (TLet). We distinguish five different sub-cases:

• sub-case G(e 1 ) = ghost: by the definition of E β (•) we have E β (let

β x = e 1 in e 2 ) E β (e 2 ).
By IH

E(∆ • Γ • Σ) E β (e 2 ) : E β (β 2 τ 2 , 2 , γ 2 ) since E(Γ + [x : ghost τ 1 ]) = E(Γ ).
There are no observable effects in 1 , i.e. 1 = ∅, and the ghost effects are erased. Given that

E β (β 2 τ 2 , 1 ∪ 2 , γ 1 ∪ γ 2 ) = (E β (β 2 τ 2 ), 1 ∪ 2 , ∅) = (E β (β 2 τ 2 )
, 2 , ∅), we conclude this case.

• sub-case G(e 2 ) = ghost: by the definition of E β (•) we have E β (let β x = e 1 in e 2 ) E ghost (e 1 ).

There are no observable effects in 2 and the ghost effects are erased. Since G(β 2 ) = ghost, the only admissible value for β is ghost, so E β (β 2 τ 2 , 2 , γ 2 ) = (Unit, ∅, ∅).

By IH E(∆ • Γ • Σ) E ghost (e 1 ) : E ghost (β 1 τ 1 , 1 , γ 1 ) which simplifies to E(∆ • Γ • Σ) E ghost (e 1 ) : (Unit, 1 , ∅).
Given that

E β (β 2 τ 2 , 1 ∪ 2 , γ 1 ∪ γ 2 ) = (E β (β 2 τ 2 ), 1 ∪ 2 , ∅) = (Unit, 1 , ∅)
, we conclude this case.

• sub-case G(β ) = ghost: by the definition of E β (•) we have

E β (let β x = e 1 in e 2 ) let reg _ = E ghost (e 1 ) in E β (e 2 )
By IH

E(∆ • Γ • Σ) E ghost (e 1 ) : (Unit, 1 , ∅) E(∆ • Γ • Σ) E β (e 2 ) : E β (β 2 τ 2 , 2 , γ 2 )
where the last judgment is valid since the identifier _ does not appear free in e 2 so it is not bound in Γ . The following judgment is valid:

E(∆ • Γ • Σ) E ghost (e 1 ) : (Unit, 1 , ∅) E(∆ • Γ • Σ) E β (e 2 ) : (E β (β 2 τ 2 ), 2 , ∅) E(∆ • Γ • Σ) let reg _ = E ghost (e 1 ) in E β (e 2 ) : (E β (β 2 τ 2 ), 1 ∪ 2 , ∅)
• sub-case x ∈ FV(E β (e 2 )): similar to the case G(e 1 ) = ghost.

• finally, the last case in the definition of extraction for a let..in expression: by the definition of E β (•) we have

E β (let β x = e 1 in e 2 ) let reg x β = E β (e 1 ) in E β (e 2 )
By the typing relation we have

∆ • Γ • Σ e 1 : (β 1 τ 1 , 1 , γ 1 ) ∆ • Γ + [x : β τ 1 ] • Σ e 2 : (β 2 τ 2 , 2 , γ 2 )
By IH

E(∆ • Γ • Σ) E β (e 1 ) : E β (β 1 τ 1 , 1 , γ 1 ) E(∆) • E(Γ + [x : β τ 1 ]) • E(Σ) E β (e 2 ) : E β (β 2 τ 2 , 2 , γ 2 )
where the last judgment simplifies to

E(∆) • E(Γ ) + [x β : E β (β 1 τ 1 )] • E(Σ) E β (e 2 ) : E β (β 2 τ 2 , 2 , γ 2 )
The following judgment is thus valid:

E(∆ • Γ • Σ) E β (e 1 ) : (E β (β 1 τ 1 ), 1 , ∅) E(∆) • E(Γ ) + [x β : E β (β 1 τ 1 )] • E(Σ) E β (e 2 ) : (E β (β 2 τ 2 ), 2 , ∅) E(∆ • Γ • Σ) let reg x β = E(e 1 ) in E β (e 2 ) : (E β (β 2 τ 2 ), 1 ∪ 2 , ∅)
case (TRaiseReg). By the typing relation we know

E : (β 1 τ 1 . . . β n τ n ) ∀i. ∆ • Γ • Σ a i : (β i τ i , ∅, ∅) ∀i. β i β i
By the definition of E β (•) we have E β (raise Eā) raise Eā M(E) , where

M(E) = β . By IH ∀i. E(∆ • Γ • Σ) E β i (a i ) : E β i (β i τ i , ∅, ∅)
considering only the extraction of a i for which β i = reg. By taking only the judgments where β i = reg we get exactly the sequence ā β . The following judgment is then valid:

E : E(β i τ 1 . . . β n τ n ) ∀i. E(∆ • Γ • Σ) E β i (a i ) : E β i (β i τ i , ∅, ∅) reg β i E(∆ • Γ • Σ) raise Eā β : (reg τ, ∅ + raises E, ∅)
case (TLoop). The typing derivation ends up with

∆ • Γ • Σ e : (Unit, , γ) ∆ • Γ • Σ loop e : (reg τ, + div, γ) By IH E(∆ • Γ • Σ) E reg (e) : (Unit, , ∅)
The following judgment is valid:

E(∆ • Γ • Σ) E reg (e) : (Unit, , ∅) E(∆ • Γ • Σ) loop E reg (e) : (reg τ, + div, ∅)
where loop E reg (e) = E β (loop e), for every β. case (TLoopDiv). Similar to the previous case.

case (TTry). The typing derivation ends up with

∆ • Γ • Σ e 0 : (π, , γ) ∀i. E i : (π i ) ∆ • Γ + [x : π i ] • Σ e i : σ i ∆ • Γ • Σ try e 0 with Ex ⇒ e : (π, -raises E i , γ -raises E i ) ∪ i σ i
where e and e i are real expressions. By IH

E(∆ • Γ • Σ) E β (e 0 ) : E β (π, , γ) E(∆) • E(Γ + [x : π i ]) • E(Σ) E β (e i ) : E β (σ i )
The last judgments simplify to

E(∆) • E(Γ ) + E([x : π i ]) • E β (e i ) E(Σ) : E β (σ i )
that is, the extraction of Γ , for each e i keeps only the x j s.t. β i j = reg, which corresponds exactly to real arguments when extracting the exceptions E i :

E(E i : (β i 1 τ i 1 . . . β in i τ in i )) E i : E(β i 1 τ i 1 . . . β in i τ in i )
The following judgment is valid:

E(∆ • Γ • Σ) E β (e 0 ) : (E β (π), , ∅) E(∆) • E(Γ ) + E([x : π i ]) • E β (e i ) E(Σ) : E β (σ i ) E(∆ • Γ • Σ) try E β (e) with Ex M(E) ⇒ e : (E β (π), -raises E i , γ -raises E i ) ∪ i E β (σ i )
which concludes the case.

case (TRecord).

We distinguish two sub-cases:

• sub-case β = ghost: by the definition of E β (•) we have E ghost ({f = a}) (), which can be typed under any environment.

• sub-case β = reg: by the definition of E β (•) we have E reg ({f = a}) {f = a} M(T ) . The following judgment is valid:

type T α = E({f : β f τ f }) ∀i. E(∆ • Γ • Σ) a i : (β f τ f i [α → τ], ∅, ∅) ∀i. β i β f i E(∆ • Γ • Σ) {f = a} : (reg T τ, ∅, ∅)
by considering only the judgments

E(∆ • Γ • Σ) a i : (β f τ f i [α → τ], ∅, ∅) for which β f i = reg. Atoms a i are not erased in E(∆ • Γ • Σ).
case (TGet). We distinguish two sub-cases:

• sub-case β = ghost: by the definition of E β (•) we have E ghost (a.f) (), which can be typed under any environment.

• sub-case β = reg: by the definition of E β (•) we have E reg (a.f) a.f. The following judgment is valid:

type T α = E({. . . , f : reg τ f , . . .}) E(∆ • Γ • Σ) a : (reg T τ, ∅, ∅) E(∆ • Γ • Σ) a.f : (β f τ f [α → τ], ∅, ∅)
as atoms a l and a r are not erased in E(∆ • Γ • Σ) and the field f is not erased in E({. . . , f :

reg τ f , . . .}). δ • µ • e µ • r E β (δ • µ • e) E β (µ • r) E β E β

case (TAssign). By the definition of E

β (•) we have E β (a l .f i ← a r ) a l .f i ← a r . Since G(a l ) = reg, G(f i ) = reg,
and G(a r ) = reg the following judgment is valid:

type T α = {. . . , f i : reg τ f , . . .} E(∆ • Γ • Σ) a l : (reg T τ, ∅, ∅) E(∆ • Γ • Σ) a r : (reg τ i [α → τ], ∅, ∅) E(∆ • Γ • Σ) a l .f i ← a r : (Unit, ∅ + writes a r , ∅)
as atoms a l and a r are not erased in E(∆ • Γ • Σ).

Semantics Preservation

In this section, we prove that extracted expressions preserve the semantics behavior of the original KidML program. Since we can only extract regular expressions, we are only interested in the regular behavior, i.e., regular results and regular effects. We prove this preservation property both for convergent and divergent evaluations.

Preservation of Convergent Evaluation

In this section, we prove that if the evaluation of a KidML program converges to a semantic result r, then the evaluation of the extracted program converges into the result of extracting r. To show this result, we use the technique of forward simulation, as depicted in Fig. 4.2, where black arrows correspond to hypotheses, while the red ones stand for conclusions. Before stating this main result, we need the following auxiliary property:

Lemma 4.3.1 (Extraction preserves substitution). If ∆ • Γ + [x : β τ 1 ] • Σ e : (β τ, , γ) and ∆ • Γ • Σ ν : (reg τ 1 , ∅, ∅), then E β (e 2 )[x → ν] = E β (e 2 [x → ν]), for all β such that β β.
Proof. By straightforward induction on the structure of expression e.

We also introduce the following auxiliary definitions: Definition 4.3.1 (Extraction of stores).

E(µ) E(∅) ∅ E(µ[l → {f = ν}]) E(µ)[l → {f = ν} M(f) ] Definition 4.3.2 (Extraction of procedures environment). ∆ δ E(δ) E(∅) ∅ E(δ[f → (x : π, e)])      E(δ) if G(f) = ghost E(δ)[f → (E(x : π), E β (e))] otherwise, where ∆(f) = ∀α. (x : π) → (β τ, , γ)
In the definition of E(δ), we use notation G(f) = ghost as a shortcut to state that f is a ghost function. Since we only extract well-typed programs, we only consider the evaluation of extracted programs under a well-typed procedures environment. In particular, we use the information stored for a function f in δ, in order to retrieve the mask of the returning type and extract the body e 1 . The preservation theorem is stated as follows:

Theorem 4.3.2 (Convergent behavior preservation). If ∆ • Γ • Σ e : (βτ , , γ) and δ • µ • e ⇓ µ • r, such that r = absurd, Σ µ, and ∆ δ, then E(δ) • E(µ) • E β (e) ⇓ E(µ ) • E β (r), for all β such that β β.
Proof. By induction on the semantics evaluation δ • µ • e ⇓ µ • r. We rule out absurd since we suppose that we only extract verified KidML programs. This means that all absurd points in the code are proved to be unreachable, hence a KidML expression cannot evaluate down to such a semantics result.

cases (EvalVBar) and (EvalRaise).

Trivial.

cases (EvalDiv), (EvalGhost), and (EvallAbsurd). Impossible case (EvalLoop). The semantics evaluation ends up with

δ • µ • let reg_ = e in loop e ⇓ µ • r δ • µ • loop e ⇓ µ • r
We distinguish two sub-cases:

• case (EvalLetAbort): we can build the following derivation:

δ • µ • e ⇓ µ • r abort r δ • µ • let reg_ = e in loop e ⇓ µ • r EvalLetAbort δ • µ • loop e ⇓ µ • r

EvalLoop

We distinguish two sub-sub-cases, according to the typing rule used at the bottom of the derivation:

case (TLoop): the typing derivation ends up with

CheckTermination(loop e) ∆ • Γ • Σ e : (Unit, , γ) ∆ • Γ • Σ loop e : (reg τ, , γ) By IH E(δ) • E(µ) • E(e) ⇓ E(µ ) • E(r)
We can build the following derivation:

δ • µ • E(e) ⇓ µ • E(r) abort E(r) δ • µ • let reg_ = E(e) in loop E(e) ⇓ µ • E(r) EvalLetAbort δ • µ • loop E(e) ⇓ µ • E(r)
EvalLoop which completes the case.

case (TLoopDiv): similar to the previous case. We may have an expression whose evaluation terminates, but for which oracle CheckTermination is not able to prove termination. Nonetheless, the semantics derivation is exactly the same as in the (TLoop) case, since our evaluation judgment does not care about the divergence effect assigned by the typing relation.

• case (EvalLet): we can build the following derivation:

δ • µ • e ⇓ µ • () δ • µ • loop e[_ → ()] ⇓ µ • r δ • µ • let reg_ = e in loop e ⇓ µ • r EvalLet δ • µ • loop e ⇓ µ • r

EvalLoop

Let us note that expression e can only evaluate down to the unit value (), since it is a well-typed expression. By IH

E(δ) • E(µ ) • E(e) ⇓ E(µ ) • () E(δ) • E(µ ) • E β (loop e) ⇓ E(µ ) • E β (r)
We can build the following derivation:

E(δ) • E(µ ) • E(e) ⇓ E(µ ) • () E(δ) • E(µ ) • E β (loop e) ⇓ E(µ ) • E β (r) E(δ) • E(µ) • let reg_ = E(e) in E β (loop e) ⇓ E(µ ) • E β (r) EvalLet E(δ) • E(µ) • loop E(e) ⇓ E(µ ) • E β (r)
EvalLoop where E β (loop e) = loop E(e). This completes this case.

case (EvalLetAbort). The semantics evaluation ends up with

δ • µ • e 1 ⇓ µ • r abort r δ • µ • let β x = e 1 in e 2 ⇓ µ • r
The typing derivation ends up with

∆ • Γ • Σ e 1 : (β 1 τ 1 , 1 , γ 1 ) β 1 β ∆ • Γ + [x : β τ 1 ] • Σ e 2 : (π 2 , 2 , γ 2 ) ∆ • Γ • Σ let β x = e 1 in e 2 : (π 2 , 1 ∪ 2 , γ 1 ∪ γ 2 )
Since δ • µ • e 1 ⇓ µ • r such that abort r, where r = absurd, then we know e 1 is always an effectful regular expression, i.e., it cannot be the case that e 1 is a completely ghost expression. We can thus apply the IH and get

E(δ) • E(µ) • E β (e 1 ) ⇓ E(µ ) • E β (r) (4.2)
for all β, such that β 1 β. The prove proceeds by case analysis on the definition of the extraction function:

• case G(e 1 ) = ghost: impossible;

• case G(e 2 ) = ghost: by instantiating β with ghost in equation (4.2) we complete this case, since

E β 2 (let β x = e 1 in e 2 ) E ghost (e 1 )
for all β 2 such that π 2 β 2 ;

• case β = ghost and 1 = ∅: by the definition of E(•)

E β 2 (let β x = e 1 in e 2 ) let reg _ = E ghost (e 1 ) in E β 2 (e 2 )
We can build the following derivation:

E(δ) • E(µ) • E ghost (e 1 ) ⇓ E(µ ) • E ghost (r) abort E ghost (r) E(δ) • E(µ) • let reg _ = E ghost (e 1 ) in E β 2 (e 2 ) ⇓ E(µ ) • E ghost (r)
which completes this case;

• case 1 = ∅: impossible;

• finally, the last case is the one where

E β 2 (let β x = e 1 in e 2 ) let reg x β = E β (e 1 ) in E β 2 (e 2 )
By instantiating β with β in equation ( 4.2), we can build the following judgment:

E(δ) • E(µ) • E β (E(µ ) • e 1 ) ⇓ E β (r) abort E β (r) E(δ) • E(µ) • let reg x β = E β (e 1 ) in E β 2 (e 2 ) ⇓ E(µ ) • E β (r)
which completes this case.

case (EvalLet).

The semantics evaluation ends up with

δ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ µ • r δ • µ • let β x = e 1 in e 2 ⇓ µ • r
The typing derivation ends up with

∆ • Γ • Σ e 1 : (β 1 τ 1 , 1 , γ 1 ) β 1 β ∆ • Γ + [x : β τ 1 ] • Σ e 2 : (π 2 , 2 , γ 2 ) ∆ • Γ • Σ let β x = e 1 in e 2 : (π 2 , 1 ∪ 2 , γ 1 ∪ γ 2 )
The proof proceeds by case analysis on the definition of the extraction function:

• if G(e 1 ) = ghost: by the substitution lemma

∆ • Γ • Σ e 2 [x → ν] : (π 2 , 2 , γ 2 )[x → ν] By IH E(δ) • E(µ) • E β (e 2 [x → ν]) ⇓ E(µ ) • E β (r)
By the auxiliary lemma 4.3.1

E(δ) • E(µ) • E β (e 2 )[x → ν] ⇓ E(µ ) • E β (r)
which completes this case, since E β (let β x = e 1 in e 2 ) E β (e 2 ) and E β (e 2 )[x → ν] = E β (e 2 ), and so there can be no occurrence of x in E β (e 2 ).

• if G(e 2 ) = ghost: by the definition of E(•)

E β (let β x = e 1 in e 2 ) E ghost (e 1 )
Since G(e 1 ) = reg, by IH

E(δ) • E(µ) • E ghost (e 1 ) ⇓ E(µ ) • E ghost (ν)
which completes this case;

• if β = ghost and obs( 1 ): by the definition of E(•)

E β (let β x = e 1 in e 2 ) let reg _ = E ghost (e 1 ) in E β (e 2 )
By the substitution lemma

∆ • Γ • Σ e 2 [x → ν] : (π 2 , 2 , γ 2 )[x → ν] By IH E(δ) • E(µ) • E ghost (e 1 ) ⇓ E(µ ) • E ghost (ν) E(δ) • E(µ ) • E β (e 2 [x → ν]) ⇓ E(µ ) • E β (r)
where E ghost (ν) = (). By the auxiliary lemma 4.3.1

E(δ) • E(µ ) • E β (e 2 )[x → ν] ⇓ E(µ )E β (r)
Since no occurrence of x can happen in E β (e 2 ), we have

E β (e 2 [x → ν]) = E β (e 2 ) = E β (e 2 )[ _ → ()]
We can build the following judgment:

E(δ) • E(µ) • E ghost (e 1 ) ⇓ E(µ ) • () E(δ) • E(µ ) • E β (e 2 )[ _ → ()] ⇓ E(µ ) • E β (r) E(δ) • E(µ) • let reg _ = E ghost (e 1 ) in E β (e 2 ) ⇓ E(µ ) • E β (r)
which completes this case;

• if x ∈ FV(E β (e 2 )
) and e 1 = ∅: similar to the case G(e 1 ) = ghost;

• finally, the last case is the one where

E β 2 (let β x = e 1 in e 2 ) let reg x β = E β (e 1 ) in E β 2 (e 2 )
By the substitution lemma

∆ • Γ • Σ e 2 [x → ν] : (π 2 , 2 , γ 2 )[x → ν] By IH E(δ) • E(µ) • E β (e 1 ) ⇓ ν β E(δ) • E(µ )E β (e 2 [x → ν]) ⇓ E(µ ) • E β (r)
By the auxiliary lemma 4.3.1

E(δ) • E(µ ) • E β (e 2 )[x → ν] ⇓ E(µ ) • E β (r)
There is no occurrence of ghost variables from x in E β (e 2 ), so the following holds:

E β (e 2 )[x → ν] = E β (e 2 )[x β → ν β ]
We can build the following derivation:

E(δ) • E(µ) • E β (E(µ ) • e 1 ) ⇓ E(µ ) • ν β E(δ) • E(µ ) • E β (e 2 )[x β → ν β ] ⇓ E(µ ) • E β (r) E(δ) • E(µ) • let reg x β = E β (e 1 ) in E β (e 2 ) ⇓ E(µ ) • E β (r)
which completes this case.

case (EvalApp). The semantics evaluation ends up with

δ(f) = (x : π, e) x : π = ν δ • µ • e[x → ν] ⇓ µ • r δ • µ • f(ν) ⇓ µ • r
Since the whole application is a regular expression, the only rule we could have used at the bottom of the typing derivation is (TApp), as follows:

θ = [α → τ] ρ = [x → ā] ∆(f) = ∀α.(x : βτ ) → σ ∀i. ∆ • Γ • Σ a i : (β i τ i θ, ∅, ∅) ∀i. β i β i ∆ • Γ • Σ f τ (ā) : σθρ By the definition of E(•), we have E β (f τ (ā)) let reg x = f τ (ā β ) in x β
We distinguish two sub-cases:

• in case e[x → ν] evaluates down to a result r such that abort r: we have the body e of f is typed with

∆ • Γ + [x : β i τ i θ] • Σ e : σ By the substitution lemma ∆ • Γ • Σ e[x → ν] : σ where σ = (β , , γ). By IH E(δ) • E(µ) • E β (e[x → ν]) ⇓ E(µ ) • E β (r)
By the auxiliary lemma 4.3.1

E(δ) • E(µ) • E β (e)[x → ν] ⇓ E(µ ) • E β (r)
Since there cannot be any occurrence of the ghost variables in x in E β (e), we have

E β (e)[x → ν] = E β (e)[x β → ν β ]
We can build the following derivation:

EvalApp E(δ) • E(µ) • E β (e)[x β → ν β ] ⇓ E(µ ) • E β (r) E(δ) = (E(x : π), E β (e)) E(x : π) = ν β E(δ) • E(µ) • f τ (ν β ) ⇓ E(µ ) • E β (r) abort E β (r) E(δ) • E(µ) • let reg x = f τ (ā β ) in x β ⇓ E(µ ) • E β (r)
EvalLetAbort which completes this case.

• in case e[x → ν] evaluates down to a sequence of values ν r : using the same reasoning as in the previous case, we have

E(µ) • E β (e)[x β → ν β ] ⇓ E(µ ) • ν r β
The following trivially holds:

E(δ) • E(µ ) • ν r β ⇓ E(µ ) • ν r β
We have that x β [x β → ν r β ] = ν r β , where β β. This holds since the sequence x β corresponds to the regular results of application, and x β and ν β are restrictions of x β and ν β , respectively. We can build the following derivation:

EvalApp E(δ) • E(µ) • E β (e)[x β → ν β ] ⇓ E(µ ) • ν r β E(δ) = (E(x : π), E β (e)) E(x : π) = ν β E(δ) • E(µ) • f τ (ν β ) ⇓ E(µ ) • ν r β E(δ) • E(µ ) • ν r β ⇓ E(µ ) • ν r β E(δ) • E(µ) • let reg x = f τ (ā β ) in x β ⇓ E(µ ) • ν r β
EvalLet which completes this case.

case (EvalFun). The semantics evaluation ends up with

δ[f → (x : π, e 1 )] • µ • e 2 ⇓ µ • r δ • µ • fun f(x : π) : σ = e 1 in e 2 ⇓ µ • r
The typing derivation ends up with

∆ • Γ + [x : π] • Σ e 1 : σ σ Γ +[x:π] (β τ, , γ) ∆ + [f : ∀α.(x : π) → (β τ, , γ)] • Γ • Σ e 2 : σ α#Γ α ∈ ∆ ∆ • Γ • Σ fun f α (x : π) : (β τ, , γ) = e 1 in e 2 : σ (TFun)
We distinguish two sub-cases, according to the ghost status of function f:

• if f is a ghost function: in this case, by the definition of E(•):

E β (fun f α (x : π) : (β τ, , γ) = e 1 in e 2 ) E β (e 2 )
By IH

E(δ) • E(µ) • E β (e 2 ) ⇓ E(µ ) • E β (r)
where f is not contained in E(δ) since it is a ghost function. This completes the case.

• if f is a regular function: in this case, by the definition of E(•):

E β (fun f α (x : π) : (β τ, , γ) = e 1 in e 2 ) fun f α (E(x : π)) : E(β τ, , γ) = E β (e 1 ) in E β (e 2 )
By IH

E(δ[ → (x : π, e 1 )]) • E(µ) • E β (e 2 ) ⇓ E(µ ) • E β (r)
where

E(δ[f → (x : π, e 1 )]) = E(δ)[f → (E(x : π), E β (e 1
))]. We can build the following derivation:

E(δ)[f → (E(x : π), E β (e 1 ))] • E(µ) • E β (e 2 ) ⇓ E(µ ) • E β (r) E(δ) • E(µ) • fun f(x : π) : E(β τ, , γ) = E β (e 1 ) in E β (e 2 ) ⇓ E(µ ) • E β (r)
which completes this case.

case (EvalRec).

Similar to the previous case. As for the (TLoopDiv) case, the divergence effect does not affect the evaluation derivation.

case (EvalIfFalse). The semantics evaluation ends up with

δ • µ • e 2 ⇓ µ • r δ • µ • if false then e 1 else e 2 ⇓ µ • r
We know that r = absurd, so we only need to distinguish two cases according to the typing rule used at the bottom of the derivation:

• case (TIfAbsurd1): the typing derivation ends up with

∆ • Γ • Σ false : (βBool, ∅, ∅) ∆ • Γ • Σ e 2 : σ ∆ • Γ • Σ if false then absurd else e 2 : σ By IH E(δ) • E(µ) • E β (e 2 ) ⇓ E(µ ) • E β (r)
By the definition of E(•):

E β (if false then absurd else e 2 ) if false then absurd else E β (e 2 )
This is the only possibility, since the constant false is always typed as a regular variable. We can build the following derivation:

E(δ) • E(µ) • E β (e 2 ) ⇓ E(µ ) • E β (r) E(δ) • E(µ) • if false then e 1 else E β (e 2 ) ⇓ E(µ ) • E β (r)
which completes this case.

• case (TIf ): the typing derivation ends up with

∆ • Γ • Σ false : (reg Bool, ∅, ∅) ∆ • Γ • Σ e 1 : σ 1 ∆ • Γ • Σ e 2 : σ 2 ∆ • Γ • Σ if false then e 1 else e 2 : σ 1 ∪ σ 2 (TIf)
By the definition of E(•):

E β (if false then e 1 else e 2 ) if false then E β (e 1 ) else E β (e 2 )
Expressions e 1 and e 2 must be both typed as regular expressions. By IH

E(δ) • E(µ) • E β (e 2 ) ⇓ E(µ ) • E β (r)
We can build the following derivation:

E(δ) • E(µ) • E β (e 2 ) ⇓ E(µ ) • E β (r) E(δ) • E(µ) • if false then E β (e 1 ) else E β (e 2 ) ⇓ E(µ ) • E β (r)
which completes this case. case (EvalIfTrue). Similar to the previous case.

case (EvalTry). The semantics evaluation ends up with

δ • µ • e 0 ⇓ µ • ν δ • µ • try e 0 with Ex ⇒ e ⇓ µ • ν
The typing derivation ends up with

∆ • Γ • Σ e 0 : (π, , γ) ∀i. E i : π i ∀i. ∆ • Γ + [x : π i ] • Σ e i : σ i ∆ • Γ • Σ try e 0 with Ex ⇒ e : (π, -raises E i , γ -raises E i ) ∪ i σ i
By the definition of E(•):

E β (try e 0 with Ex ⇒ e ) try E β (e 0 ) with E β (Ex ⇒ e)

By IH E(δ) • E(µ) • E β (e 0 ) ⇓ E(µ ) • E β (ν)
We can build the following derivation:

E(δ) • E(µ) • E β (e 0 ) ⇓ E(µ ) • E β (ν) E(δ) • E(µ) • try E β (e 0 ) with E β (Ex ⇒ e) ⇓ E(µ ) • E β (ν)
which completes this case.

case (EvalTryAbort).

The semantics evaluation ends up with

δ • µ • e 0 ⇓ µ • r abort r ∀i. E i = r δ • µ • try e 0 with Ex ⇒ e ⇓ µ • r
The typing derivation ends up with

∆ • Γ • Σ e 0 : (π, , γ) ∀i. E i : π i ∀i. ∆ • Γ + [x : π i ] • Σ e i : σ i ∆ • Γ • Σ try e 0 with Ex ⇒ e : (π, -raises E i , γ -raises E i ) ∪ i σ i
By the definition of E(•):

E β (try e 0 with Ex ⇒ e ) try E β (e 0 ) with E β (Ex ⇒ e)

By IH E(δ) • E(µ) • E β (e 0 ) ⇓ E(µ ) • E β (r)
We can build the following derivation:

E(δ) • E(µ) • E β (e 0 ) ⇓ E(µ ) • E β (r) abort E β (r) ∀i. E i = E β (r) E(δ) • E(µ) • try E β (e 0 ) with E β (Ex ⇒ e) ⇓ E(µ ) • E β (r)
which completes this case.

case (EvalTryExn). The semantics evaluation ends up with

δ • µ • e 0 ⇓ µ • raise E ν E = E i ∀j. j < i → E = E j δ • µ • e i [x → ν] ⇓ µ • r δ • µ • try e 0 with Ex ⇒ e ⇓ µ • r
The typing derivation ends up with

∆ • Γ • Σ e 0 : (π, , γ) ∀i. E i : π i ∀i. ∆ • Γ + [x : π i ] • Σ e i : σ i ∆ • Γ • Σ try e 0 with Ex ⇒ e : (π, -raises E i , γ -raises E i ) ∪ i σ i
By the definition of E(•):

E β (try e 0 with Ex ⇒ e ) try E β (e 0 ) with E β (Ex ⇒ e)
We distinguish two sub-cases, according to the ghost status of e i :

• in case e i is a regular expression: by IH

E(δ) • E(µ) • E β (e 0 ) ⇓ E(µ ) • E ν M(E ) E(δ) • E(µ ) • E β (e i [x → ν]) ⇓ E(µ ) • E β (r)
By the auxiliary lemma 4.3.1

E(δ) • E(µ ) • E β (e i )[x → ν] ⇓ E(µ ) • E β (r)
and since there can only be regular occurrences of x in E β (e i )

E β (e i )[x → ν] = E β (e i )[x M(E ) → ν M(E ) ]
We can build the following derivation:

E(δ) • E(µ) • E β (e 0 ) ⇓ E(µ ) • E ν M(E ) E = E i ∀j. j < i → E = E j E(δ) • E(µ ) • E β (e i )[x M(E ) → ν M(E ) ] ⇓ E(µ ) • E β (r) E(δ) • E(µ) • try e 0 with Ex ⇒ e ⇓ E(µ ) • E β (r)
which completes this case.

• in case e i is a ghost expression: by IH

E(δ) • E(µ) • E β (e 0 ) ⇓ E(µ ) • E ν M(E ) By the definition of E(•) E β (E x ⇒ e i ) E x E ⇒ ()
We know that β = ghost and that µ = µ , otherwise e i would be an effectful expression and could not be considered as a ghost expression. We then have

E β (r) = ()
We can build the following derivation

E(δ) • E(µ) • E β (e 0 ) ⇓ E(µ ) • E ν M(E ) E = E i ∀j. j < i → E = E j E(δ) • E(µ ) • () ⇓ E(µ ) • () E(δ) • E(µ) • try e 0 with Ex ⇒ e ⇓ E(µ ) • () which completes this case. δ • µ • e div E β (δ • µ • e) div co E β co E β

case (EvalRecord).

There are only two possible masks to extract such an expression. Either β = ghost, in which case

E ghost ({ f = ν }) ()
We have E ghost (l) = () thus, the result follows immediately from the trivial evaluation

E(δ) • E(µ) • () ⇓ E(µ) • () Otherwise β = reg, in which case E reg ({ f = ν }) { f = ν } M(f)
Since E reg (l) = l, we can build the following derivation

l ∈ dom(E(µ)) E(µ ) = E(µ)[l → { f = ν } M(f) ] E(δ) • E(µ) • { f = ν } M(f) ⇓ E(µ ) • l
which completes this case.

case (EvalGet).

Similar to the previous case, even easier when β = reg. For both cases, location l is always in dom(E(µ)).

case (EvalAssign).

Trivial: by the hypotheses of the theorem, such an assignment stands for a regular effect, so the extraction function does not affect this expression.

case (EvalErr). Impossible.

Preservation of Divergent Evaluation

We focus now on the proof that a divergent KidML program is extracted into a program that diverges as well. Ghost code cannot diverge, hence divergence is an effect that we preserve in the extracted program. We use our co-inductive evaluation judgment and, once again, the technique of forward simulation to prove such a result, as illustrated in Fig. 4.3. This preservation theorem is state as follows: Proof. By co-induction on the semantics evaluation δ • µ • e ⇓ co div. This proof actually follows the same structure as the proof of theorem 4.3.2 so we do not detail all the cases here. We present just the cases for the evaluation of let..in expressions. Since the evaluation of this expression diverges, the rule used at the bottom of typing derivation is (TLoopDiv), as follows:

¬CheckTermination(loop e) ∆ • Γ • Σ e : (Unit, , γ) ∆ • Γ • Σ loop e : (reg τ, + div, γ)
We distinguish two sub-cases:

• sub-case (CoEvalLetAbort): we can build the following derivation: By co-inductive hypothesis

δ • µ • e ⇓ co div abort div δ • µ • let reg _ = e in
E(δ) • E(µ) • E(e) ⇓ co div
We can build the following derivation: which completes this case.

E(δ) • E(µ) • E(e) ⇓ co div abort div E(δ) • E(µ) • let reg_ = E(
• sub-case (CoEvalLet): we can build the following derivation: By co-induction hypotheses

δ • µ • e ⇓ µ • () µ • loop e ⇓ co div δ • µ • let reg _ = e in
E(δ) • E(µ ) • loop E(e) ⇓ co div
We can build the following derivation:

E(δ) • E(µ) • e ⇓ () E(δ) • E(µ ) • loop E(e) ⇓ co div E(δ) • E(µ ) • let reg _ = E(
∆ • Γ • Σ e 1 : (β 1 τ 1 , 1 , γ 1 ) β 1 β ∆ • Γ + [x : β τ 1 ] • Σ e 2 : (π 2 , 2 , γ 2 ) ∆ • Γ • Σ let β x = e 1 in e 2 : (π 2 , 1 ∪ 2 , γ 1 ∪ γ 2 )
By co-induction hypothesis

E(δ) • E(µ) • E β (e 1 ) ⇓ co div
We now proceed by case analysis on the definition of the extraction function. Since e 1 is a stateful expression, the first and the third cases of the let..in extraction rule cannot happen. For the remaining three cases, we can always use rule (CoEvalLetAbort) to the complete the case. For instance, if e 2 is a regular expression, we complete the case with the following derivation: In this case, expression e 2 diverges causing the whole let..in expression to diverge. The evaluation of e 1 terminates in the sequence of values ν, so we have

E(δ) • E(µ) • E β (e 1 ) ⇓ co div abort div E(δ) • E(µ) • let reg x β = E β (e 1 )
δ • µ • e 1 ⇓ µ • ν
The typing derivation ends up with

∆ • Γ • Σ e 1 : (β 1 τ 1 , 1 , γ 1 ) β 1 β ∆ • Γ + [x : β τ 1 ] • Σ e 2 : (π 2 , 2 , γ 2 ) ∆ • Γ • Σ let β x = e 1 in e 2 : (π 2 , 1 ∪ 2 , γ 1 ∪ γ 2 )
By co-induction hypotheses

E(δ) • E(µ) • E β (e 2 ) ⇓ co div
We now proceed by case analysis on the definition of the extraction function. The first and third cases in the extraction of a let..in expression follow directly by the co-induction hypotheses. Since e 2 is a stateful expression, the second case is impossible. For the remaining two cases, we can always use rule (CoEvalLet) and the co-induction hypotheses to build a derivation that completes the case. We distinguish two sub-cases, according to the typing rule used at the bottom of the derivation.

case (CoEvalRec). Semantics co-evaluation ends up with

• sub-case (TRec): typing derivation ends up with

∆ + [f : ∀α.(x : π) → σ] • Γ + [x : π] • Σ e 1 : σ σ Γ +[x:π] σ CheckTermination(rec f α (x : π) : σ = e 1 ) σ = (β τ, , γ) ∆ + [f : ∀α.(x : π) → σ] • Γ • Σ e 2 : σ α ∈ Γ α ∈ ∆ ∆ • Γ • Σ rec f α (x : π) : σ = e 1 in e 2 : σ By co-induction hypothesis E(δ[f → (x : π, e 1 )]) • E(µ) • E β (e 2 ) ⇓ co div
In this case, a call to function f always terminates, hence this is not the source of divergence for the evaluation of expression e 2 . In particular, f can be a ghost function. If that is the case, we have

E(δ[f → (x : π, e 1 )]) = E(δ)
and the proof follows by the co-induction hypothesis, since the whole rec..in expression extracts to E β (e 2 ). Otherwise, f is a regular function and we have

E(δ[f → (x : π, e 1 )]) = E(δ)[f → (E(x : π), E β (e 1 ))]
We can build the following derivation: • sub-case (TRecDiv): typing derivation ends up with

E(δ)[f → (E(x : π), E β (e 1 ))] • E(µ) • E β (e 2 ) ⇓ co div E(δ) • E(µ) • rec f α (E(x : π)) : E(σ) = E β (e 1 )
∆ + [f : ∀α.(x : π) → σ] • Γ + [x : π] • Σ e 1 : σ σ Γ +[x:π] σ ¬CheckTermination(rec f α (x : π) : σ = e 1 ) σ = (βτ, + div, γ) ∆ + [f : ∀α.(x : π) → σ] • Γ • Σ e 2 : σ α ∈ Γ α ∈ ∆ ∆ • Γ • Σ rec f α (x : π) : σ = e 1 in e 2 : σ
Since f is a stateful function, we can only apply the following rule of extraction:

E β (rec f α (x : π) : σ = e 1 in e 2 rec f α (E((β τ, , γ))) : E(β τ, , γ) = E β (e 1 )in E β (e 2 ))
We complete this case by using the co-induction hypothesis and the rule (CoEvalRec) to build the derivation. This is similar to second sub-case of the (CoEval) case.

Extraction Machinery

In this section, we present the new Why3 extraction mechanism. We describe our implementation effort, with a focus on the new architecture of the mechanism, code optimizations, and interaction with drivers. We give, as well, an overview of the new Why3 extract command, with a detailed explanation of the command line options and different entry points for extraction.

Extraction Implementation.

We have re-implemented from scratch the Why3 extraction mechanism. This programming effort currently makes up to more than 2.5k of OCaml non-blank lines of code. We entirely re-designed the extraction mechanism architecture to clearly separate the code translation part from the code printing process. The WhyML internal abstract syntax tree is translated into an intermediate representation, an ML-like language, from which all the traces of ghost code and logical annotations have been removed. The implemented extraction function follows the definition we give in Fig. Let us note the two assert expressions at the beginning of the function definition. These ensure that we never try to extract a ghost expression, and that the mask given as the mask of expression e is always a sub-mask, i.e., it is at most as ghost as the mask given as an argument to the extract function. These assert expressions correspond exactly to the statement of lemma 4.1.1, and are used as mean of defensive programming. The four cases in the definition of extract that we present here are also present in the definition of Fig. 4.1. When mask is completely ghost, we extract constants and variables into e_unit, a smart constructor of the intermediate language that build the unit value. On the other hand, if the mask is visible, we call smart constructors e_const and e_var to extract, respectively, constants and program variables. Finally, the remaining two cases correspond to the first two cases in extraction of a let..in expression. In the first one, we extract only sub-expression e 2 since e 1 is ghost. In the second case, we extract e 1 with a ghost mask, as we are only interested in keeping the side effects produced by this expression and not its result. For both cases of let..in extraction, the assert expressions on top of the function body, together with the when clauses of each branch, ensure that each recursive call of extract is done on regular expressions. Moreover, we know that for the second case e 1 is an effectful expression, otherwise the whole let..in expression would be considered ghost.

To print the extracted code we use a printer, an OCaml program that takes the intermediate representation and prints it into the desired programming language syntax. In the scope of this work, we wrote the printer for the OCaml language. This is an essential ingredient in our quest to use Why3 to produce verified OCaml programs. We wrote, as well, a CakeML [START_REF] Kumar | CakeML: A verified implementation of ML[END_REF] printer, in order to translate verified WhyML programs into the input language of a certified compiler. Extending Why3 with the support for the extraction towards a new programming language is now just a matter of writing a new printer. Giving the simplicity of the intermediate representation, when compared with the WhyML internal AST, writing such a printer is not a major task. In fact, Why3 currently supports extraction of C programs [START_REF] Rieu-Helft | How to get an efficient yet verified arbitrary-precision integer library[END_REF], using a printer conceived on top of our intermediate representation.

Optimizations. During the code translation phase, we perform some optimizations over the extraction result. An important optimization is the simplification of singleton record types. When reasoning about data structures, we normally introduce a logical model for such a data structure. Such a model is typically introduced as ghost value and maintained as a field of a record type which contains the data structure. Let us consider the case of type t, the of permutations presented in Sec. 2.2.1. When we remove the two ghost fields, we end up with the following type definition:

WhyML type t = { a: array63 }
The Why3 extraction mechanism inlines such type definitions, and simplifies the code that accesses this field or that creates a value of this record-type. This avoids a dynamic allocation for a singlefielded record.

Another important optimization performed during extraction is the elimination of superfluous let..in expressions. These are mainly introduced by the internal representation of WhyML in A-normal form. Even if locally binding every sub-expression, via what we call a proxy variable, does not change the meaning of the program, it becomes rather tedious to read a code containing all those extra bindings. However, not all let..in bindings introduced internally by Why3 can be removed. Let us consider, for instance, the following WhyML program:

WhyML let conflict_assign () : unit = let f (x y: int63) : int63 = assert { y = 42 }; y in let i = ref 42 in let _ = f (i := 0; !i) !i in assert { !i = 0 }
In Why3, it is specified that the order of arguments evaluation is done from right to left. The internal use of A-normal form automatically guarantees that this order of evaluation is respected: However, the order in which function arguments are evaluated in OCaml is not specified. This means that some versions of the OCaml compiler might start by evaluating expression i := 0; !i. In particular, function f is called with both arguments equal to zero, which breaks the intermediate assertion proved by Why3.

Interaction with drivers. Extraction drivers introduce textual substitutions for some of the WhyML symbols used in our development. For each type, function, or exception name encountered during extraction, we check if such a symbol is defined in the driver. If this is the case, we replace every occurrence of such a symbol by the code provided in the driver. In the particular case of extraction to OCaml, we use the ocaml64 driver to map some of the Why3 standard library elements to the corresponding OCaml standard library counterparts. The following is a piece of this driver: In order to avoid some potential faulty expressions, we enclose within parentheses every expression issued from the driver. This is, however, clearly not enough to prevent every potential problem rising from the driver. Each string can contain arbitrary code that is never verified, nor even type-checked, which makes every substitution a potential source of bugs in extracted code. Drivers are, thus, part of the trusted computing base of Why3. Nonetheless, a typical driver is supposed to only introduce very simple substitutions, which we can convince ourselves that are the source of any problem just by carefully reading the driver's contents.

The why3 extract command.

To extract a program from a WhyML development, the user must invoke the extract tool of the Why3 framework, directly from her terminal. The general usage of this command is as follows:

Terminal why3 extract [options] -D <driver> [-o <dir|file>] [<file>.<Module>*.<symbol>?|-]

The available command options are the following:

• --flat (this option is activated by default): instructs Why3 to print the result of extraction into a single file, even if multiple WhyML modules/files are extracted;

• --modular: instructs Why3 to print each WhyML module targeted by extraction into a separated file; we can use this option to extract functorial code, as we shall present in Sec. 5.3.4; such an option cannot be combined with the --flat one;

• --recursive: instructs Why3 to perform a recursive extraction, i.e., the dependencies of each WhyML symbol are also extracted; this option can be combined with both the --flat and --modular ones.

To perform recursive extraction, we have implemented a calculus of dependencies for our intermediate representation tree. It consists of an higher-order iterator that traverses bottom-up the abstract syntax tree issued from extraction, using Why3 standard library functions to fetch the definition of a symbol from its name. The -D option of the command line is mandatory and expects the name of a driver. A single call to extract can be given multiple drivers; it suffices to prefix each one with an extra -D. The -o option specifies the output file to print the extraction result. Under flat extraction, if no output file is given, the result is printed to standard output. This option is mandatory under a modular extraction, and one must specify a directory in which to print the different resulting files.

Finally, we can call the Why3 extraction on different entry points. Extraction can be applied to a complete .mlw file, in which case the contents of the whole file is extracted. Extraction can also be applied to a single module, in which case we give to the command line an argument of the form f.M, where module M is contained in the Why3 file named f.mlw. Last, we can also chose to extract the definition of a single WhyML symbol, i.e., a function, a type, or an exception name. In this case, the extract command terminates by f.M.t, where symbol t is defined inside module M, which is contained in the f.mlw file.

Discussion and Related Work

Correct-by-construction programs via code extraction. The Coq proof assistant provides an extraction mechanism [START_REF] Letouzey | A new extraction for Coq[END_REF][START_REF] Letouzey | Programmation fonctionnelle certifiée: l'extraction de programmes dans l'assistant Coq[END_REF] that can be used to extract a certified functional program from a proof term. The basis for Coq's extraction is a clear separation between propositions that are "computational informative" and propositions that have only "logical" contents [START_REF] Paulin-Mohring | Extracting F ω 's programs from proofs in the Calculus of Constructions[END_REF]. In practice, this is done by marking the computational-relevant elements of a proof with sort Set, and the parts that are useless for computation with sort Prop. Coq's type system ensures that this marking is correct, which is similar to our use of KidML's type system to ensure the property of non-interference. Coq currently supports extraction towards OCaml, Haskell, and the Scheme language.

The Coq extraction mechanism has been successfully used to produce correct-by-construction implementations of industrial-size software. An example is CompCert [START_REF] Leroy | A formally verified compiler back-end[END_REF], a realistic, optimizing, and formally verified C compiler. Coq is used to prove that the different steps of compilation preserve the semantics of the C source code, as well as to extract an executable OCaml implementation of the compiler. Another impressive example that follows this approach is the Verasco static analyzer [START_REF] Jourdan | A formally-verified C static analyzer[END_REF]. This analyzer uses abstract interpretation [START_REF] Cousot | Abstract interpretation: A unified lattice model for static analysis of programs by construction or approximation of fixpoints[END_REF] to search for run-time errors in C programs. Verasco is proved correct using Coq, which guarantees that programs that analyze without alarms are free of run-time errors. The OCaml sources of this project are, once again, obtained via the Coq extraction mechanism.

Another example of a verification framework featuring an extraction mechanism for ML-like programs is the FoCaLiZe atelier [START_REF] Hardin | [END_REF]. The FoCaLiZe environment presents a functional programming language with object-oriented features, on which the programmer can write specifications and formal proofs of programs. Currently, FoCaLiZe translates a proof to three languages: OCaml, which lets the user build an executable program; and Coq and Dedukti [START_REF] Assaf | Expressing theories in the λΠ-calculus modulo theory and in the Dedukti system[END_REF], in order to check the proofs.

Formalization of extraction.

The formalization of our extraction function is greatly influenced by the previous work of Jean-Christophe Filliâtre, Léon Gondelman [72, Chap.2], and Andrei Paskevich [START_REF] Filliâtre | The spirit of ghost code[END_REF]. In that work, the authors define a code ghost erasure procedure over GhostML, a small ML-like language. This erasure function is parameterized by a Boolean value and a GhostML expression. Whenever the first parameter is bottom, the whole expression is converted to the unit value. Otherwise, an extracted program is produced as a morphism of the source program. This closely resembles our use of masks during extraction, where bottom corresponds to an entirely ghost mask and top to a regular one. A proof of typing and semantics preservation is given for such erasure proceeding. Contrary to our approach, these proofs are done using a small-step semantics judgment. Another important difference is that a ghost expression is systematically replaced by the unit value, while our extraction function completely erases any traces of ghost code, whenever possible. Moreover, an expression is either completely ghost or completely regular, which simplifies the design and soundness proof of the erasure procedure.

Differences with respect to the Why3 extraction.

The extraction mechanism we present in this chapter is a significative sub-set of what we have actually implemented inside the Why3 framework. Our presentation of the extraction function closely follows the OCaml implementation that lives inside the Why3 source code. In the following, we detail on some of the implemented features that are missing in our formalized extraction function.

There are some WhyML syntactic constructions that we do not include in our presentation. The most evident is pattern matching and algebraic data types declaration. We chose to exclude pattern-matching as we believe this would only make our formalization heavier, without actually making it more interesting. Another distinguished feature of WhyML is its module system. The modular structure of a development can be replicated in the extracted code, via the modular option of the extract command. It could extend our extraction function to take into account the modular structure of a WhyML development, and it would be interesting to investigate how we would generalize our proof of semantic preservation to take non-closed programs into account.

An important difference between the implemented extraction mechanism and the one we present in this thesis is partial application of functions. While the KidML language only features complete application, WhyML allows the programmer to partially apply functional symbols. From a point of view of code extraction, this poses some interesting challenges. Let us consider the following WhyML program:

WhyML let last_ghost_arg () = let f (x: int63) (ghost y: int63) = x / 0 in let partial = f 0 in 42
Naively, we could expect the following extracted code:

OCaml (extracted) let last_ghost_arg () = let f (x: int) = x / 0 in let partial = f 0 in 42
At first view, and given the ghost status of argument y, this seems a fair result of extraction. However, the partial application of f must be considered carefully. The above WhyML program produces no run-time error, since f is never completely applied, hence we never evaluate the division by zero expression. On the other hand, f is extracted as a single-argument function thus, when expression f 0 is evaluated, a division-by-zero error is raised. The extracted code cannot introduce any behavior that does not happen in the original WhyML code. To avoid this pitfall, whenever a WhyML function is partially applied and the remaining arguments are all ghost arguments, we encapsulate such an application with a function expecting a unit argument. For the above example, this is as follows:

OCaml (extracted) let partial () = f 0 in ...
It would be interesting to investigate how we could extend our language and extraction mechanism to include partial function application, and observe how this would impact our formalization.

No one in the brief history of computing has ever written a piece of perfect software. It's unlikely that you'll be the first.

Andy Hunt

5

A Toolchain for Verified OCaml Programs

In this chapter, we present a new toolchain and methodology to produce verified OCaml programs. The core of our methodology is based on the material presented in the previous chapters, namely the use of the WhyML language to derive verified implementations, and the use of the Why3 extraction mechanism to obtain correct-by-construction OCaml code.

Our toolchain is part of a bigger project, named VOCaL [START_REF] Charguéraud | VOCAL -A Verified OCaml Library[END_REF], whose ambition is to provide a mechanically-verified library of efficient general-purpose data structures and algorithms, written in the OCaml language. One novelty of VOCaL1 is the collaborative use of three different verification tools, namely Coq [START_REF] Coq | The Coq Proof Assistant Reference Manual -Version V8.6[END_REF], the CFML tool [START_REF] Charguéraud | Characteristic formulae for the verification of imperative programs[END_REF], and Why3 itself. To reconcile these three tools together, given their distinct logic and programming frameworks, one of the main lines of work in the VOCaL project is the design of a specification language for OCaml, similar to what JML is for Java [START_REF] Burdy | An overview of JML tools and applications[END_REF][START_REF] Gary | JML: notations and tools supporting detailed design in Java[END_REF], or ACSL for C [START_REF] Baudin | ACSL: ANSI/ISO C Specification Language, version 1.4[END_REF]. Such a specification language is not tied to any particular verification tool, hence it can be used as an entry point for the three tools of the VOCaL project. An important aspect about this specification language is that it must be both mathematically rigorous and easy to understand by an OCaml programmer who is not necessarily a proof expert. This last point is crucial, since we attach specification elements to .mli files, which are traditionally used as documentation units on the behavior of the library code, as the user is not expected to look into the details of implementation. In such a way, the introduced specification cannot disturb the library user, but rather be a source of extra rigorous documentation, helping the programmer to understand the behavior of the library operations. In the following, we shall refer to this new specification as OSL, short for OCaml Specification Language. This chapter is organized as follows. We present our methodology and toolchain in Sec. 5.1. In Sec. 5.2, we use the example of a verified union-find implementation as a complete illustration of all the steps in our methodology. Sec. 5.3 completes the union-find case study by presenting challenging aspects that arise during the proof of OCaml programs, and how we deal with them. We conclude with some discussion and related work in Sec. 5.5.

Methodology

There are various different approaches to tackle the development of formally verified programs. The most direct and, perhaps, most widespread method is to augment an existing mainstream programming language with specification annotations (function contracts, loop invariants, etc.) and prove the conformance of the code to the specification, normally through an intermediate verification platform. Some examples of such approach include the VeriFast [START_REF] Jacobs | VeriFast: A powerful, sound, predictable, fast verifier for C and Java[END_REF] and KeY [START_REF]Deductive Software Verification -The KeY Book -From Theory to Practice[END_REF] tools for Java, the Frama-C [START_REF] Kirchner | Frama-c: A software analysis perspective[END_REF] and VCC [START_REF] Cohen | VCC: A practical system for verifying concurrent C[END_REF] (using Boogie [START_REF] Barnett | Boogie: A Modular Reusable Verifier for Object-Oriented Programs[END_REF] as an intermediate language) frameworks for the C language, GNATprove [39] (using Why3 as an intermediate language) for Ada/SPARK, and CFML (using Coq as an intermediate language) for OCaml. A major technical challenge resulting from this approach is the need to encode a significant fragment of a real-life programming language, which was most likely not designed with verification in mind, into a suitable program logic. Other than the hardness of defining such an encoding, the generated verification conditions may be rather complex which makes the proof difficult for both automated or interactive tools.

A different approach consists in developing formally verified code in a dedicated verification environment and then translate it to an existing programming language. Via this translation mechanism one produces a so called correct-by-construction program. Following this approach, one can cite PVS [START_REF] Owre | PVS: A prototype verification system[END_REF], Coq , the B method [START_REF] Abrial | The B-Book, assigning programs to meaning[END_REF], F [START_REF] Swamy | Dependent types and multimonadic effects in F*[END_REF], Dafny [START_REF] Rustan | Dafny: An automatic program verifier for functional correctness[END_REF], and Why3. This approach works well for self-contained developments, e.g., the CompCert [99] verified C compiler, but faces some problems when it comes to integrate the verified code into a larger development. The automatically generated code is typically a clobbered mess while the original source code, written in the specific language of the verification platform, is normally incomprehensible to a common programmer.

Our methodology to use the Why3 framework to producing verified OCaml code is an attempt to reconcile these two approaches, avoiding some of the aforementioned drawbacks. The OCaml language has a number of features that make it a particularly well-suited target language for our approach. The separate compilation in OCaml is organized around the notions of interface, declarations of types and function signatures collected in a .mli file, and an implementation providing the definitions of types and functions, collected in a .ml file. We split our verification and implementation process into several steps. First, given an OCaml .mli file, we annotate declarations with specification elements such as function contracts, type invariants, etc., using the new OCaml specification language. From the annotated .mli file, we then automatically generate a corresponding Why3 input file, in which all annotations are translated into WhyML. This is done using a new Why3 plugin, which we specifically developed as part of our methodology. The next step is to provide a verified WhyML implementation of the declared operations and types. This means that, besides implementing and verifying a WhyML program, we also establish its correctness with respect to the specification we first gave in the .mli file. Lastly, we use the Why3 extraction mechanism to translate our verified WhyML implementation into a correct-by-construction OCaml code, which we print to a .ml file.

An overview of our methodology is given in Fig. 5.1. In the diagram, the rows correspond to the different levels of abstraction (interface vs. implementation) and the columns correspond to environments (OCaml vs. Why3). The solid rectangles represent the user-written files, namely the annotated OCaml interface and the WhyML implementation, and the dashed rectangles represent the automatically generated files, namely the WhyML interface and the OCaml implementation. Whenever an OCaml type cannot be mapped directly to a WhyML type, due to use of mutable data beyond the reach of WhyML's type system [START_REF] Filliâtre | A pragmatic type system for deductive verification[END_REF], a custom memory model is built for this type. When it comes to translation of WhyML to OCaml, we return to the original OCaml type using a consistent substitution file. This is illustrated in the central part of the diagram.

In the remaining of this chapter, we go through several case studies in order to illustrate our methodology. We use the example of a union-find library to explain our verification and implementation workflow in detail. Other case studies are presented in Sec. 5.3.

A Case Study: Union-Find

To illustrate our methodology, let us consider the verification of a union-find library. Such a case study is a collaboratively development with Martin Clochard, Jean-Christophe Filliâtre, and Simão Melo de Sousa. We reuse the OCaml API from Arthur Charguéraud and François Pottier's proof [START_REF] Charguéraud | Verifying the correctness and amortized complexity of a union-find implementation in separation logic with time credits[END_REF]: We assume the interface to be enclosed in a file named UnionFind.mli. Polymorphic type elem stands for the type of elements of each equivalence class. In this API, a value of type 'a is attached to every equivalence class. The make function takes a value of such type and returns a fresh elem, representing a new singleton class. Functions get and set are used, respectively, to retrieve and modify the information attached to an equivalent class. The return type of the set operation indicates that the equivalence class is updated via a side-effect, which lets us guess that function set performs some effectful computation. The remaining functions in the API are the expected ones for a union-find implementation: the classical function find takes an element of and returns the representative of the equivalent class to which the element belongs; function eq checks if two elements belong to the same equivalence class; finally, union merges two equivalence classes, also via a side-effect.

In the following sections, we detail on the OCaml and WhyML developments that we use to produce a correct-by-construction implementation of the union-find library. We show how we specify the interface of union-find, using the new OCaml specification language, and how we can use Why3 to build a verified implementation of the union-find operations. We prove that such an implementation conforms to the specified interface, through a proof of data refinement. Finally, we use the Why3 extraction mechanism to mechanically generate a compilable OCaml implementation of the union-find library.

Specification

"With great proofs, comes great specification". Before engaging ourselves in the proof of the unionfind implementation, we must first devise a formal specification for the union-find operations. Such a specification is added to the UnionFind.mli file, in a form of special comments starting with '@'. Being declared inside comments, specification elements are ignored by the OCaml compiler.

In order to specify to the API operations, we need to be able to reason about the elements in the union-find universe. To this end, we first introduce the following ghost type uf: This is a mutable, abstract data type, whose contents we model through a set dom, a function rep, and a function img. We add, as well, three invariants to type uf. These ensure that the set dom is, indeed, partitioned by the relation "to have the same values by rep". In the following, we use type uf to derive the specification of the union-find operations. We use a subset of the operations declared in the UnionFind.mli interface to illustrate the use of OSL. Let us use make as a first example. Such an operation is specified in OSL as follows: A function specification is attached to a val symbol. The first line names the argument v and the returned value e, so that we can refer to them in the function contract. For the purpose of specification, function make receives an extra argument uf of type uf 'a, which we identify as a ghost argument using square brackets. The function contract is given in the form of pre-and postconditions, as well as modifies clauses to indicate a modification effect performed by the function. In this case, modifies accounts for the fact that we update the logical models dom, rep, and img, following the creation of a new equivalence class. Explicitly mentioning the modification effects of functions gives the programmer some extra understanding about the execution of the function, which cannot be stated in the OCaml interface. In the case of make, this tells the programmer that a call to make produces some side-effect. The postcondition states four different properties: [START_REF] Abrial | The B-Book, assigning programs to meaning[END_REF] the element e did not belong to set dom before the function execution (the old keyword represents the value of its argument in the state prior to function execution), i.e., we create, indeed, a fresh equivalence class; [START_REF]Deductive Software Verification -The KeY Book -From Theory to Practice[END_REF] we add e to set dom;

OCaml (annotated) (*
(3) we extend rep with e as the representative element of the new equivalence class; (4) we extend img with v as the image of the new equivalence class. Note that we use the name of models as projections over the value uf.

Next, we define the following specification for the find function: The above pre-condition requires that element e to be in the dom set, i.e., the domain of elements in the union-find universe. The postcondition ensures that the find operation does not change the value of the logical models, and also that the returned value r stands for the representative element of the equivalence class of e. The most interesting part of this specification lies in the modifies clause. Even if we do not change the value of any of the models of type uf, function find internally performs a side-effect. This is nothing else than the effect of path-compression.

We complete our presentation of the union-find specification with the union function. This function is specified as follows: We can only merge two existing elements, which we state in the pre-condition. When merging two equivalence classes, we do not allocate any new element, as stated in the first postcondition. The remaining of the union postcondition demands a little more of attention. The union operation takes two equivalence classes and shall map every element in the classes of e1 and e2 to the same representative value. The representative element of the merged class is either the representative value of e1 or the representative value of e2, before the merging. We retrieve such an element using the existential quantification in the postcondition. We can then specify that, for every element x in the dom set, if x is in the same equivalence class as e1 or e2, then rep uf x is updated to r.

Otherwise, x just keeps the same representative as before. We update function img in a similar way. Finally, the modifies clause accounts for path-compression and the connection of elements to a new representative value.

Functions get, set, and eq, which we do not show here, have similar contracts to those of functions make, find, and union.

Verified Implementation

The next step is to implement and verify the union-find data structure. The OCaml implementation we target is based on the following data types:

Link Link

The picture on the right shows a possible state of the union-find universe, built from the given data types. The rectangular nodes stand for Root elements (the representatives of classes), whereas the round ones stand for Link. The leftmost class is of rank two, as the longest chain until the root features two Link nodes. We represent the information associated to each equivalence class using symbols α and β.

Memory model. Unfortunately, the given OCaml type definition cannot be directly translated to WhyML. The reason is that recursive mutable types are beyond the scope of Why3's type-and-effect discipline [START_REF] Filliâtre | A pragmatic type system for deductive verification[END_REF]. The solution is to resort to an explicit memory model, that is a set of types for pointers and memory together with the operations to allocate, read, and write memory. In this case, we introduce the following WhyML type to model the heaps contents: In this type of the heap, non-allocated locations are mapped to None, and each allocated location is mapped to Some c, for some value of type 'a.

We declare some primitive functions to manipulate the heap and locations. For instance, the following returns a fresh and empty heap: The specification of function empty_memory states that no location is allocated in the newly created heap. This is very easily expressed using set dom. To interact with the heap, we introduce functions get_ref and set_ref which, respectively, get the current value associated to a location and update the value pointed by a reference in the memory. These are specified as follows: Note that we pass the heap as a ghost argument, instead of declaring a single global variable to model the heap. The reason is two-fold. First, global variables must have monomorphic types. Second, by using "small heaps" passed through the chain of function calls as hidden arguments, we statically enforce separation between the heaps and avoid complicated frame conditions. This is yet another instance of Burstall's "component-as-array". Soundness of heap manipulation is guaranteed by the fact that mem_ref is a private data type that is updated through abstract functions.

Finally, we introduce the following function to compare the memory address of two locations: We use the loc_ref type of the memory model to represent references, and instantiate it here with an argument of type content 'a.

The interesting aspect about the above type definitions is the definition of type rank. This is an alias for type int63, the type of WhyML machine integers. As we describe in Chap. 2, type int63 is directly extracted to the int type of OCaml, producing efficient code. From a proof perspective, however, the price to pay is to prove the absence of arithmetic overflows.

In Chap. 2, the proof that reference n does not overflow is made simpler because its value is bound by the length of the array t. There are, however, some situations where it is much more subtle to deal with machine arithmetic. Let us consider, for instance, the following two equivalence classes:

Link

There is now a single equivalence class of rank three, which results from linking the root of the former rightmost class to the root of the leftmost one. In WhyML, we can implement the code performing this union operation as follows:

WhyML let union (ghost memo: mem_ref (content 'a)) (x y: elem 'a) = let x, y = find memo x, find memo y in if x == y then x else match get_ref memo x, get_ref memo y with | Root rx vx, Root ry vy -> if rx = ry then begin set_ref memo y (Link x); set_ref memo x (Root (rx + 1) vx) end ... Since the rank is of type int63, from such an operation Why3 generates the verification condition rx + 1 ≤ 2 62 -1. Without any further knowledge on rx, this is simply not provable. One obvious solution would be to add such an inequality as a pre-condition of union. This would make the proof of overflow absence trivial, but this extra pre-condition would fatally pollute the proof of any client of union. Another solution would be to use arbitrary-precision integers, which would not require a proof of overflow absence. However, this would make the extracted code less efficient and would require the use of an external arbitrary-precision library to run the extracted code.

We adopt here the solution proposed by Martin Clochard, Jean-Christophe Filliâtre, and Andrei Paskevich [START_REF] Clochard | How to avoid proving the absence of integer overflows[END_REF]. The idea is as follows: unless we expect our program to have century-long runs, we can rest assured that an integer variable, whose value only grows by one at a time is, for all intents and purposes, safe from overflow. To materialize this meta-argument, the authors introduce a new type of integers, called Peano integers, with limited arithmetic operations. In Why3, this results in a new library Peano, with a type t for Peano integers, a zero constant zero, a successor function succ, and comparison function lt and eq, as follows: We forgo the non-overflow precondition for Peano.succ, as there is no other way of producing a Peano integer than by starting at zero and incrementing it one by one, and so the 2 62 limit is never reached in any real-life situation. In our verified implementation of union-find, we alias type rank with Peano.t, instead of int63: Since Peano.succ has no precondition, there is no proof of absence of overflow to be done. In particular, no extra pre-condition is required for function union.

When it comes to extract OCaml code, type Peano.t is translated into OCaml's type int, function Peano.succ into a machine addition, and lt and eq are translated, respectively, into the OCaml operations < and =. This is done by extending our ocaml64 driver with the following: Driver module Peano syntax type t "int" syntax val succ "%1 + 1" syntax val lt "%1 < %2" syntax val eq "%1 = %2" end Implementation of data type uf. Having defined the memory model and the types element and content, we can implement and verify the union-find data structure. In particular, we have to implement the data type uf. It is a record data type that contains, in addition to the fields dom, rep, and img, the contents of the memory allocated by the union-find structure:

WhyML type uf 'a = { ghost mutable dom : set (elem 'a); ghost mutable rep : elem 'a -> elem 'a; ghost mutable img : elem 'a -> 'a; ghost memo: mem_ref (content 'a); } Encapsulation of the set of allocated pointers in type uf closely resembles the treatment of memory footprint in the context of dynamic frames [START_REF] Kassios | The dynamic frames theory[END_REF][START_REF] Ioannis | Dynamic frames: Support for framing, dependencies and sharing without restrictions[END_REF][START_REF] Ioannis | A Theory of Object Oriented Refinement[END_REF]. Note that type uf is only useful for specification purposes, as it contains only ghost fields. In order to specify the type uf, we keep the same type invariants as declared in UnionFind.mli interface:

WhyML invariant { forall x. mem x dom -> img x = img (rep x) } invariant { forall x. mem x dom -> rep (rep x) = rep x } invariant { forall x. mem x dom -> mem (rep x) dom }
and add some new conditions to relate the contents of memo with those of set dom, and functions rep and img. First, we state that an element belongs to the union-find universe if and only if it is allocated,

WhyML invariant { forall x. mem x dom <-> allocated memo x }
where allocated is defined as follows: If x points to a Link y node in memory, then y is different of x (no loops are allowed in union-find), element y is also allocated, and has the representative element as x; on the other, if x points to a Root _ v node, then the img of x is v, and x is its own representative element. This completes the specification of the union-find universe, with respect to the built memory model.

Verified implementation of union-find operations.

After implementing the uf data type, we can now focus on the implementation of the union-find operations. As declared in the interface, all union-find functions receive a ghost parameter of type uf and then exploit it to perform read/write operations on memory. For instance, the make function is implemented and specified as follows: Here, the specification is much simpler than the one we gave in the interface. Why3 is able to infer that the only field of uf modified by the execution of find is memo. There is, hence, no need to state that the values of dom, rep, and img are unchanged. The call to set_ref accounts for path compression, as it links every element in the equivalence class directly to the representative element computed by the recursive calls.

Proof of termination.

Let us note that we have not, yet, supplied a termination measure for function find. In fact, proving the termination of this function is one of most challenging aspects in the proof of the union-find data structure.

We adopt here a solution that consists in maintaining a distance value associated to each element of an equivalence class. When following Link pointers, distances strictly increase, as we illustrate in the following diagram: The distance values we assign to each element are of no particular interest, as long as they increase until we reach a Root node. We introduce, as well, a maximal value as an upper bound for distances. We materialize these two notions by adding to type uf two new fields dst and maxd, as follows:

WhyML type uf 'a = { ... ghost mutable dst : elem 'a -> int; ghost mutable maxd: int; } We also equip this data type with the following new invariants:

WhyML invariant { forall x. match memo.

refs x with

| Some (Link y) -> dst x < dst y | _ -> true end } invariant { 0 <= maxd } invariant { forall x. mem x dom -> dst x <= maxd } Such invariants state the increasing property of the dst values and that these are limited by the value of maxd. We straightforwardly update function make by adding the line This is very similar to the specification of function union, except for the pre-condition stating that x and y must be representative elements. The implementation is as follows: we first compare elements x and y, and if these point to the same location in memory we immediately return x, WhyML = if x == y then x on the other hand, if these are different pointers, we use function get_ref to get the value associated to x and y. If x and y are Root elements, then we merge the two equivalence classes according to the ranks. If x is the less-ranked representative element, then we link x to y, the representative element of the new class, The merge of the two classes is done using the set_ref function, which changes in memory the value associated with x. The rest of the code updates fields rep, img, maxd, and dst. Syntax

WhyML x.f 1 , . . . , x.f n <-v 1 , . . . , v n
stands for the multiple assignment operation, i.e., we assign each field f i of record x to the value v i . We increment the value of maxd and update dst such that dst y is equal to the maximum between dst x and dst y, plus one. This way, all distances are kept strictly increasing and smaller to the value of maxd, hence we preserve the invariant of type uf. If x and y are equally-ranked root elements, we know that the new class must be of an higher rank. In such a case, we update the value associated with x in memory to Root (Peano.succ rx) vx. This completes the proof of functions union and link.

We omit the implementation of functions get, set, and eq. These have all straightforward implementations and the contract of these functions follows exactly the one we give in the interface file. It is worth pointing out that all the generated verification conditions by Why3 for the union-find operations are discharged fully automatically. No auxiliary lemma or intermediate assertion is ever needed.

A digression on specification styles. The way we specify the uf data type is crucial to complete the proof of the union-find data structure. The use of universal quantification and the specification in terms of model fields dom, rep, img, dst, and maxd is the secret to achieve a fully automated proof of correction of the implemented operations. In particular, proving termination is almost trivial thanks to the use of fields maxd and dst. We refer to such kind of invariants as local invariants.

We could, very well, specify type uf in terms of some inductive definition, introducing the notion of path and reachability between union-find elements. For instance, we could use the following definition of path: The statement path memo x y n stands for "under heap memo, there is a path from x to y of length at most n". Then, we would have WhyML forall x. mem x dom -> path memo x (rep x) maxd as an invariant of the uf data type, which ensures the termination of function find. However, from a practical point of view, the use of inductive predicate path would fatally compromise the automation of the proof. At each assignment in the heap, we would have to re-establish the path invariant for every chain in the union-find universe (some are unchanged, some are shortened, etc.). This would require, undoubtedly, some auxiliary lemmas being proved by induction.

Proof of Refinement and Specification Inclusion

Once we have implemented and verified all operations, we move on to a proof of specification inclusion. This means proving that the specification of the verified union-find operations conforms to the specification written in the .mli file and translated to WhyML by the new Why3 plugin. A proof of specification inclusion is very similar to the use of Hoare Logic consequence rule. The following Hoare Logic rule

P =⇒ P { P } c { Q } Q =⇒ Q { P } c { Q } Consequence
states that the Hoare triple { P } c { Q } is valid if we can derive the Hoare triple { P } c { Q }, where P implies P and Q implies Q. In fact, this rule allows us to strengthen the pre-condition and weaken the postcondition of program c. In a proof of specification inclusion, we lift such a reasoning to the level of functions. Let us consider, for instance, that we declare the following function f in the interface file OCaml (annotated) val f x : τ x : τ (*@ r = f y requires P(y) ensures Q(y, r) *)

and that we give the following implementation to f

WhyML let f (x : τ x ) : τ requires { P (x ) } ensures { Q (x , result) } = c
The specification inclusion proof of f amounts to proving the following implications:

P(x) =⇒ P (x) Q (x, r) =⇒ Q(x, r)
In Why3, a specification inclusion proof is done using the WhyML clone command. For the given example, we would write the following: WhyML clone import Interface.Sig with val f = Impl.f where we assume function f is contained in file interface.mli, where Sig is the WhyML module automatically generated by the Why3 plugin, and the implementation of function f is contained in a module named Impl. Instead of directly generating the above verification conditions, Why3 generates the following piece of code:

WhyML let f_correct (x : τ x ) : τ requires { P(x) } ensures { Q(x, result) } = Impl.f (x)
This way, it uses the Why3 verification conditions generator to generate the necessary verification conditions, avoiding the error-prone task of directly generating implications to prove specification inclusion.

In the case of our union-find WhyML development, the proof of inclusion is as simple as the following clone expression: WhyML clone import UnionFind.Sig with type elem = Impl.elem, type uf = Impl.uf, val make = Impl.make, val find = Impl.find, val eq = Impl.eq , val get = Impl.get, val set = Impl.set, val union = Impl.union For every symbol declared in the interface file UnionFind.mli, we give a substitution by the corresponding implementation symbol, which we assume to be defined in the WhyML module Impl. The result of the above clone statement is an instance of module Sig, modulo the given substitution, while generating verification conditions for specification inclusion.

Note that we also assign type symbols elem and uf, declared in file UnionFind.mli, with the corresponding implementations. If the types do not possess any invariant, this is just a textual substitution of the the type names. On the other hand, whenever types are equipped with some invariant, as it is the case of uf, there is a refinement proof to be done. Let us suppose that we have declared in the interface the following data type:

OCaml (annotated) type t (*@ model f : τ invariant I(f) *)
and have the following implementation of type t:

WhyML type t = { f : τ; } invariant { I (f ) }
In order to prove that the implementation of type t refines the specification given for type t in the interface, one must prove that the following condition holds:

I (f ) =⇒ I(f)
Other than generating the above verification condition, Why3 also checks that the sequence f of fields declared in the interface is a sub-sequence of fields f given in the implementation, as well as the following additional condition on the ghost status of fields: a field declared as ghost in the interface can either be kept a ghost field in the implementation, or become a regular field; a field declared as regular can only be implemented as a regular field. In the case of type uf, the refinement proof is trivial since every field in the implementation keeps the same ghost status as declared in the interface, and the interface invariant is formed of three conjunctions that are also in the implementation invariant. All the generated verification conditions for the specification inclusion and refinement proof of union-find are automatically discharged. This completes the specification, implementation and verification task of our methodology.

Extraction to OCaml

The last step in our methodology diagram consists in translating WhyML to OCaml, using Why3's extraction mechanism. From our memory model, we build the following custom driver:

Driver module UnionFind.Impl syntax type loc_ref "%1 ref" syntax val (==)
"%1 == %2" syntax val alloc_ref "ref %1" syntax val get_ref "!%1" syntax val set_ref "%1 := %2" end Note that the argument %1 of functions in the driver does not refer to the heap. Indeed, because of its ghost status, this is erased by the extraction mechanism. The OCaml code extracted from the WhyML union-find implementation is given in Fig. 5.2.

With extraction, we close our methodology diagram. We can now use the OCaml compiler to compile the extracted code against the UnionFind.mli interface. From our toolchain point of view, this not only guarantees that the extracted code type checks with respect to what we declare in the interface file, but also that this implementation conforms to the specification included in UnionFind.mli file.

Challenges

We now illustrate our toolchain over several other examples. Each one presents a particular issue in the process of verifying OCaml code.

Non-verified Client Code

In the task of deductive verification, it is customary to annotate functions with some pre-conditions that prevent, for instance, run-time errors to happen during execution. Within the same formal development, client code must respect these pre-conditions, which guarantees that every call to the verified function is safe. When we come out of our closed world of verified software, and translate a program proof into a compilable code with the purpose of distributing it, the panorama is completely different. Our verified implementation, that we so hardly obtained, can now be executed from a non-verified code base. In particular, some pre-conditions can be violated which might result in a run-time faulty behavior. In a perfect world, our correct-by-construction implementation should be defensive against non-verified client code, but without sacrificing the ability to call such an implementation from a verified piece of software.

In this section, we describe our proposal to deal with both verified and non-verified client code within our toolchain. Let us take the example of vectors [138, p. 136-137], also known as re-sizable or growing arrays, to illustrate this approach. A vector is a data structure that encapsulates an array which automatically expands or shrinks, when necessary. The following is a scheme of such a data structure:

0 size data = view . . . dummy. . .
The internal array is allocated with a certain capacity, which we might not be using entirely during the lifetime of a vector. We say that there is a part of the vector filled with significant elements, which corresponds to the view section in the above scheme. We refer to the number of significant elements as the size of the vector. The rest of the internal array is filled with a dummy value. When one implements the vector data structure in OCaml, it is the dummy value that allows the GC to reclaim pointers of the We can insert or remove elements from a vector, and the strategy of when to expand and when to shrink the internal array, is what makes vectors such an interesting data structure. A good implementation doubles the capacity when growing, and shrinks it (normally to half the capacity) whenever the number of elements is one-fourth of the array capacity. This way, inserting n elements in a vector takes O(n) time, i.e., each insertion operation has amortized constant time complexity (several proof techniques for such a complexity bound are given in Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein's Introduction to Algorithms textbook [43, chap. 17]). Given the use of a constant dummy value, when we shrink the internal array the OCaml's GC can reclaim pointers to values that aren't used anymore.

The following is a sub-set of a typical OCaml interface for the vector data structure:

OCaml type 'a t exception Empty val create : ?capacity:int -> dummy:'a -> 'a t val resize : 'a t -> int -> unit val push : 'a t -> 'a -> unit val pop : 'a t -> 'a val get : 'a t -> int -> 'a val set : 'a t -> int -> 'a -> unit

This interface file provides the polymorphic type t of vectors and the following operations: function create returns a zero-sized fresh vector, where the labeled argument dummy is the user-supplied value to fulfill the empty part of the array and the optional argument capacity stands for the initial capacity of the vector; function resize takes a vector v and an integer n as arguments and sets the size of v to n; function push inserts its second argument at the rightmost position of its first argument vector; finally, function pop removes and returns the rightmost element of a vector, or raises Empty if the vector contains no elements. Following our methodology diagram, we provide a specification for the vector's interface. We give the following specification for type t:

OCaml (annotated)
type 'a t (*@ mutable model view: 'a seq *) (*@ invariant length view <= Sys.max_array_length *)

The vector data structure is modeled by a single mutable model field named view, exactly as in the previously shown scheme. This view field represents the sequence of significant elements of the vector. The sole restriction we place on this field is that its length never exceeds the max_array_length constant, defined in the OCaml System interface module. This limits the maximum length of the array, and this invariant guarantees that we never allocate an internal array bigger than max_array_length. The specification of create is straightforward, hence we do not detail it here. The specification of resize is more interesting: The checks keyword introduces a pre-condition that is meant to be verified at run-time. Instead of letting resize trying to allocate an array with a bad length, we dynamically test the value of n and stop the execution immediately if this is not within bounds. Concretely, we want an unsatisfied checks pre-condition to result in an Invalid_argument exception being raised, as is customary in OCaml libraries. When fed to our plugin, the above specification generates the following WhyML code:

OCaml (annotated)
WhyML val unsafe_resize (a:t 'a) (n:int63) : unit requires { 0 <= n /\ n <= max_array_length } ensures { length (view a) = n } ensures { forall i. 0 <= i /\ i < min (length (view a1)) n -> (view a)[i] = (view a1)[i] } val resize (a:t 'a) (n:int63) : unit ensures { length (view a) = n } ensures { forall i. 0 <= i /\ i < min (length (view a1)) n -> (view a)[i] = (view a1)[i] } raises { Invalid_argument -> not (0 <= n /\ n <= max_array_length) }
The plugin introduces a new function named unsafe_resize where the checks condition is given in the form of a traditional pre-condition. We expect this function to be called from a verified client code, where we do not want to pay the price of unnecessary run-time tests but rather verify the given pre-condition. On the other hand, function resize turns the checks condition into a raises clause. This function dynamically tests the checks condition and raises Invalid_argument if such a condition is not met.

From an implementation perspective, we must provide both the unsafe_resize and the resize functions. The latter is straightforwardly implemented as follows: ... The pre-condition of push and the type invariant of argument a imply the pre-condition of the call to unsafe_resize. All the verification conditions generated for push are proved by SMT solvers. We skip the specification and implementation of functions get and set, since these are straightforward.

Not all requires are born to be checks. Giving the presence of the checks in OSL, it seems tempting to get all requires conditions tested at run-time. Let us use an example to show why this is not always possible. In the following, we detail on the verified implementation of a binary search routine to find the insertion place of an element in an array. The following is the OCaml interface defining these two operations:

OCaml val bisectr : ('a -> 'a -> int) -> 'a array -> int -> int -> 'a -> int

We give the following specification to function bisectr:

OCaml (annotated)
val bisectr : ('a -> 'a -> int) -> 'a array -> int -> int -> 'a -> int (*@ r = binary_search_right cmp a fromi toi v checks 0 <= fromi <= toi <= Array.length a requires Order.is_pre_order cmp requires forall i j. fromi <= i <= j < toi -> cmp a.(i) a.(j) <= 0 ensures fromi <= r <= toi

ensures forall i. fromi <= i < r -> cmp a.(i) v <= 0 ensures forall i. r <= i < toi -> cmp a.(i) v > 0 *)
The first pre-condition states that the arguments fromi and toi must be valid indexes in the array. We give such a pre-condition in a checks clause, as this is definitely a property that we want and can test at zrun-time. The second pre-condition states that the argument cmp is a preorder (we assume here that predicate is_pre_order is defined in a module named Order, issued from a specification library). This cannot be checked at run-time and thus we keep it as a traditional requires clause. Finally, the third pre-condition indicates that the array a is sorted within the range [fromi..toi[. There is no practical constraint that would prevent us from writing a loop that scans the array and tests if the elements respect the order induced by function cmp. However, this would incur a linear cost, beyond the logarithmic cost of the binary search itself. Hence, we keep this as a requires pre-condition.

Higher-order Effectful Functions

In the example of the previous section, function bisectr is a higher-order function as this takes as an argument the comparison function cmp. In our specification (and in the subsequent proof), we implicitly assume function cmp to be pure. This assumption is somewhat justified, since we require cmp to implement a preorder, which would make it really difficult to specify and to use if it would depend on the state or, worse, had some side-effects. In general, assuming that higher-order functions are only given pure arguments is not acceptable. A typical example of a stateful higher-order function is an iterator over the elements of a collection. In OCaml, it is idiomatic to provide, for some abstract type t, an iter function of the form OCaml val iter : (elt -> unit) -> t -> unit A call to iter f c applies function f sequentially to each element of the collection c. This is only interesting if f performs some side-effects2 . Providing a complete specification to this function would require a richer specification logic to account for function effects [START_REF] Charguéraud | Characteristic Formulae for Mechanized Program Verification[END_REF][START_REF] Kanig | Spécification et preuve de programmes d'ordre supérieur[END_REF], including abrupt termination if an exception is raised during iteration. This would lead to a rather unreadable contract for the OCaml programmer, sacrificing the principle of a simple specification language. Moreover, this would probably mean a richer logic than that of Why3, which would make it impossible to conduct our methodology within the Why3 framework.

We claim that the best specification for an iter-like function is an operationally equivalent program with a clear meaning to any OCaml programmer. If we consider the case of the iter function over arrays, we propose the following specification:

OCaml (annotated)
val iter : ('a -> unit) -> 'a array -> unit (*@ iter f a equivalent "for i = 0 to Array.length a -1 do f a.(i) done" *)

The equivalent keyword of OSL is used to introduce a program with the same operational behavior as iter. In this case, it is almost the same as the standard library implementation of iter3 . If we consider the more general case of some abstract collection, we can still give iter a specification, as follows:

OCaml (annotated)
val iter : ('a -> unit) -> 'a t -> unit (*@ iter f c equivalent "List.iter f (elements c)" *)

Function elements returns the list of the elements in the order they are traversed. In particular, we move the problem of specifying the iteration order to the specification of the elements function.

In Chap. 6, we present several examples of elements-like functions, which we specify using our approach of characterizing the returned list with two predicates [START_REF] Filliâtre | A modular way to reason about iteration[END_REF], one to identify valid prefixes and the other to identify complete lists. Combined with the semantics of List.iter, known by any OCaml programmer, the given equivalent clause fully specifies the behavior of iter. However, the program given in terms of elements is inefficient, since it builds an intermediate list unnecessarily. This makes it less suited to be used as an actual implementation of iter. The use of keyword equivalent suggests a proof of program equivalence between the specification program and the actual implementation. Currently, Why3 does not provide such a possibility, and so we simply use the program given in the equivalent clause as the extraction result of function iter. This is not satisfactory, as we are not providing any formal guarantee about this piece of code. In Sec. 5.5, we discuss some related work on programs equivalence proofs and suggest some possible solutions to extend Why3 in order to conduct equivalence proofs.

Recursive Mutable Data Types

As seen in Sec. 5.2, the static discipline of Why3 limits the range of ephemeral data structures and heap-manipulating programs that can be encoded directly in WhyML. The solution is to resort to an explicit memory model, with a type of pointers and heap together with operations to interact with memory. In this section, we complement our presentation of pointer-based data structures from Sec. 5.2. We use our approach to systematically build memory models from arbitrarily, (possibly) recursive mutable OCaml data types. An example of such data type are precisely the content and elem types, on page 124. In this section, we present another example of a recursive mutable data type, whose definition is based on recently-introduced features of the OCaml language. Part of the ideas and material used in this section have been already presented in a JFLA (Journées Francophones des Langages Applicatifs) 2018 paper [START_REF] Filliâtre | Vérification de programmes fortement impératifs avec Why3[END_REF].

Since version 4.03 of the OCaml compiler4 , one can use records syntax to define the arguments of algebraic data type constructors. For instance, one can declare a type of mutable singly-linked lists as follows:

OCaml type 'a cell = Nil | Cons of { content: 'a; mutable next: 'a cell; } This type avoids the extra memory allocations that would happen had we used a record and an option type to define type cell, as follows:

OCaml type 'a cell = { content: 'a; mutable next: 'a cell option; } Other solutions amount at using recursive values or, even worse, the Obj.magic function [START_REF] Filliâtre | Le petit guide du bouturage, ou comment réaliser des arbres mutables en OCaml[END_REF]. With the cell type we can obtain a list representation similar to those of a Java or C code, where the Nil constructor works as the null pointer. However, being an algebraic data type, the OCaml type system ensures that we do not use Nil to access the fields of content or next.

We use the example of an in-place mergesort routine verification to illustrate the use of mutable singly-linked lists. Let us consider the following OCaml implementation: For readability purposes, we omit the code of functions split and merge. Our goal here is to use Why3 to get a correct-by-construction version of function mergesort. Given the definition of type cell, we cannot directly encode such a type in WhyML and so the solution is, again, to introduce an explicit memory model. This is actually the approach adopted in tools using Why3 as an intermediate language, e.g., Frama-C [START_REF] Kirchner | Frama-c: A software analysis perspective[END_REF]. Contrary to such tools, though, we do not map every WhyML element to a common memory model. We build a specific memory model for type cell and keep using traditional WhyML types whenever possible. We begin by introducing the WhyML types corresponding to cell and memory locations, as follows:

WhyML type loc 'a type cell 'a = Nil | Cons (loc 'a)

Next, we model the heap contents using the following record type:

WhyML type mem 'a = private { mutable content : loc 'a -> option 'a; mutable next : loc 'a -> option (cell 'a); } invariant { forall l. content l = None <-> next l = None } Fields content and next mimic to the two fields of constructor Cons argument. The type invariant ensures that both fields are simultaneously None. It is worth pointing out that the choice of introducing a heap type with several distinct fields is not innocent: this statically guarantees that a modification of one field does not affect the other, which makes the proof process much more easy. This idea dates back to 1972 and is due to Rod Burstall [START_REF] Burstall | Some techniques for proving correctness of programs which alter data structures[END_REF]. It is known under the designation of "component-as-array" memory model.

Similarly to what we did for the union-find example, we introduce several abstract functions to interact with the mem type. We do not show them here, as these very closely resemble the ones we defined for union-find.

Proof of function mergesort. We can now use the introduced memory model to implement, and then verify, a WhyML version of function mergesort. The complete code is give in Fig. 5.3.

Let us begin by explaining the definition of predicate is_list (lines 1-9). The idea is to model the elements from cell from until cell to (both inclusively) by means of a finite logical sequence of type view 'a. Such a type is defined as follows:

WhyML type view 'a = seq (loc 'a)

For the case of an empty list (lines 1-3), we state both from and to point to same location in memory. On the other hand, if the list is non-empty, then from is a Cons node, its argument is the first element of sequence s (line 5), the list is finite and ends up with cell to (line 6), it does not contain any repeated element, hence no cycle (lines 7-8), and finally that the intermediate elements are those of sequence s. Next, predicate frame_mem (lines 11-13) states that a sequence s presents the same values for next and contents under memories m1 and m2. From predicates frame_mem and disjoint, we can deduce the definition of predicate frame (lines 15 and 16). This states that, for every sequence ss disjoint from s, we have that the next and contents projections map the elements of ss to the same values in both memory m1 and m2. This is useful to state that a part of the memory is not changed by the execution of some function, i.e., we can frame the part of memory changed during execution.

We can now use predicates is_list and frame to give a contract to function split (lines 15-21): the first pre-condition requires l1 and l2 to point to well-formed nil-terminated lists; the second one states that we only split lists of at least two elements. The first postcondition ensures that the returned lists l1 and l2 are also well-formed nil-terminated lists, with respect to sequences s1 and s2, respectively. Finally, we ensure that the only memory locations updated during the execution of split are those contained in sequence s.

Function merge presents a similar contract (lines [START_REF] Charguéraud | Characteristic formulae for the verification of imperative programs[END_REF][START_REF] Charguéraud | Pretty-big-step semantics[END_REF][START_REF] Charguéraud | VOCAL -A Verified OCaml Library[END_REF][START_REF] Charguéraud | Verifying the correctness and amortized complexity of a union-find implementation in separation logic with time credits[END_REF][START_REF] Church | A formulation of the simple theory of types[END_REF][START_REF] Church | The Calculi of Lambda Conversion[END_REF][START_REF] Clarke | Automatic verification of finite-state concurrent systems using temporal logic specifications[END_REF]. This function takes as arguments the memory, a cmp function defining a pre-order (line 29), and two lists sorted for the order relation induced by cmp (lines 30 and 31), disjoint from each other (line 32). The merge function returns a new sorted list l (line 33), whose elements are a permutation of the elements in lists l1 and l2 (line [START_REF] Church | The Calculi of Lambda Conversion[END_REF]. We also ensure that the execution of merge only changes the part of the heap corresponding to locations of the s1 and s2 sequences (line 35).

Finally, we can give a suitable contract to function mergesort (lines [START_REF] Sylvain Conchon | Cubicle: A parallel SMT-based model checker for parameterized systems[END_REF][START_REF] Coplien | Advanced C++ Programming Styles and Idioms[END_REF][START_REF] Coquand | [END_REF][START_REF] Thomas | Introduction to Algorithms[END_REF][START_REF] Cousot | Methods and logics for proving programs[END_REF][START_REF] Cousot | Abstract interpretation: A unified lattice model for static analysis of programs by construction or approximation of fixpoints[END_REF] and prove that the implementation conforms to such a specification. From the specification of functions split and merge, we can easily prove the correctness of the body of mergesort. In fact, all the generated verification conditions by Why3 for this function are automatically discharged. The whole WhyML development of this in-place mergesort routine is composed of more than 200 lines of specification (auxiliary lemmas, function contracts, etc.), and by almost 200 lines of code (this includes ghost code).

Functors

The WhyML language features a module system rather different from that of OCaml. We can divide a WhyML development into several top-level modules, introduced with the module keyword; a module can be further divided into several sub-namespaces, introduced via the keyword scope. This is an important difference w.r.t. OCaml, where modules can feature sub-modules. Another distinguishing feature of WhyML is that it allows uninterpreted and defined symbols to appear in the same namespace. In this section, we explain how we use the WhyML module system to mimic some of the most interesting features of the OCaml language, namely functors. We use the Pairing Heaps [START_REF] Michael | The pairing heap: A new form of self-adjusting heap[END_REF][START_REF] Okasaki | Purely Functional Data Structures[END_REF] data structure as our working example of specification, translation and extraction of an OCaml functor within our toolchain of verified OCaml programs.

Functors are used in OCaml to implement modules parameterized by other modules. A typical example is data structure that requires its elements to be equipped with an order relation, e.g., a priority queue implementation. Within our toolchain, we are able to give a specification to an OCaml interface that declares such a data structure.

end

Let us explain the hierarchy of modules and namespaces from the point of view of our plugin. The top-level module of a .mli file is translated by our plugin into the WhyML module Sig. Any sub-module declared in the same .mli file is recursively extracted into a WhyML namespace, which is the closest we get to OCaml sub-modules. File PairingHeap.mli features two sub-modules: on one hand, Make is a sub-module of the top-level module; on the other hand, the functor signature X is a sub-module of Make, so our plugin includes the definition of scope X inside the body of scope Make.

The next step is to provide a WhyML implementation with identical namespaces. The following is a sketch of our WhyML implementation of Pairing Heaps: This a WhyML implementation of significant size so, for the sack of readability, we do not show it entirely here and focus only on the most relevant aspects. Let us begin by noting that every symbol declared in scope X is left undefined. As we shall later see in this section, this generates a functor argument at extraction time. The implementation task happens inside scope Make. Here, we can use symbols from X to provide implementations to our types and functions, just like one would do in an OCaml functor. For instance, type tree is defined in terms of type X.t.

A Pairing Heap is implemented as a multiway tree, as defined by type tree. The heap type encapsulates a field of type tree, together with the logical model of type bag X.t. This way, we can give a specification of Pairing Heaps in terms of the bags structure, also known as multisets. We equip type heap with the following invariant:

WhyML invariant { forall x. nb_occ x bag = occ_heap x data } invariant { card bag = size_heap data } invariant { heap data } invariant { no_middle_empty_heap h }
The first two lines establish the connection between physical representation of the heap, field data, and the logical model bag: the number of occurrences of any element x is the same in both data and bag; the number of elements is the same in both fields. Functions occ_heap are straightforwardly defined, and so we do not give their definition here. The third line of the invariant states that data is a heap-ordered structured. The heap predicate is defined as follows:

WhyML predicate heap (t: tree) = match t with | E -> true | T x l -> le_roots_list x l /\ heap_list l WhyML let delete_min (t: heap) : heap requires { 0 < size t } ensures { occ (minimum t) result = occ (minimum t) t -1 } ensures { forall y. y <> minimum t -> occ y result = occ y t } ensures { size result = size t -1 } = match t.data with | E -> absurd | T x l -> { data = merge_pairs_heap l; bag = diff t.

bag (singleton x) } end

The absurd point is provable thanks to the given pre-condition. All the verification conditions generated for delete_min are discharged automatically. The remaining heap operations are even easier to implement, and their correctness proof is easily done by SMT solvers.

The last step in the proof of Pairing Heaps amounts to specification inclusion. This is no different from the previously presented proofs of specification inclusion, except that we need to the follow the hierarchy of namespaces, in order to provide the good substitution to the clone command. For the case of Pairing Heaps, this is as follows: We omit here the substitution for the Pairing Heaps val functions. Note that Why3 generates verification conditions to prove the definition of axioms is_pre_order and min_def, against the provide implementation of functions compare and minimum. These, and the other verification conditions generated for this clone expression, are discharged by SMT solvers.

Finally, we can extracted an OCaml version of the Pairing Heaps data structure. By default, Why3 refuses to extract a WhyML code where there are some uninterpreted symbols left which are not defined in the driver, e.g., type elt or function compare from the X scope. The only exception is a namespace that contains only uninterpreted symbols. In such a case, we turn it into a functor argument when the --modular option is given in the Why3 extraction command-line, as follows:

Terminal > why3 extract -L . -D ocaml64 --modular PairingHeap.Impl -o .
With such an option, Why3 generates a different OCaml file for each WhyML module, while extracting every scope as a sub-module. The absence of modular would induce a flat extraction, i.e., every WhyML symbol extracted into a single file, contained no sub-module, even if the development is spread across different WhyML modules. With our Pairing Heaps example, we get some OCaml code with the expected structure, i.e., OCaml (extracted) module Make(X: sig type t val compare : t -> t -> t end) = struct type tree = E | T of X. Note that, in the extracted code, type heap is simply an alias for type tree. Indeed, the Why3 extraction mechanism is able to recognize and optimize record types containing a single, nonmutable field.

Experimental Evaluation

We have used our approach to verify several other OCaml modules. These examples illustrate many features we described in this chapter, i.e., preconditions verified at run-time, OCaml functors, higher-order effectful functions, absence of arithmetic overflows, and pointer-based implementations. Figure 5.1 summarizes the size of these examples, column "spec" showing the number of lines in the .mli files and the "code" column showing the number of lines in the WhyML implementation and proof. It is worth stressing out that all the specification and proof tasks are conducted inside our toolchain, and all the proofs are done fully automatically.

Discussion and Related Work

Verified programming libraries. Libraries are the basic building blocks of any realistic programming project. It is thus of crucial importance that libraries are bug-free software artifacts, preventing the programmer from the pain of seeing her implementation crashing due to a piece of code that she did not even write in the first place. As we have already discussed, even massively used and tested libraries can contain bugs, which makes programming libraries interesting candidates for deductive verification. Although this may seem surprising, program verification has seldom been applied to libraries of significant size. The most remarkable exception is the verification of the EiffelBase2 containers library [START_REF] Polikarpova | A fully verified container library[END_REF][START_REF] Polikarpova | A fully verified container library[END_REF], performed with the AutoProof system [START_REF] Tschannen | Autoproof: Autoactive functional verification of object-oriented programs[END_REF]. The verification of the EiffelBase2 library builds on the work of Nadia Polikarpova [START_REF] Polikarpova | Specified and Verified Reusable Components[END_REF], namely on the use of semantic collaboration [START_REF] Polikarpova | Flexible invariants through semantic collaboration[END_REF] and model-based contracts to extend the classic Design by Contract approach of the AutoProof tool. It is our purpose to continue using and improving our methodology to grow our verified library to a size comparable to that of EiffelBase2.

The recent work of Raphael Cauderlier and Mihaela Sighireanu [START_REF] Cauderlier | A verified implementation of the bounded list container[END_REF] reports on the verified implementation of a bounded list container. The authors verify a C implementation of the mentioned container, using the VeriFast platform and its underlying separation logic [START_REF] Reynolds | Separation logic: a logic for shared mutable data structures[END_REF] to prove functional correctness and safety of memory accesses. An interesting aspect pointed out in this work is the importance of reusable specification of data structures, as the specification of the bounded list container is drawn from a previously existing Ada 2012 specification [START_REF] Dross | Correct Code Containing Containers[END_REF]. This opens good perspectives about the proof of bounded lists in other verification systems. For instance, such a data structure is certainly a good candidate to our toolchain and methodology.

Following the trend of verified container libraries, the work by Joachim Breitneret al. [START_REF] Breitner | Ready, set, verify! applying hs-to-coq to real-world haskell code (experience report)[END_REF] explores the use of an Haskell to Coq translation tool to verify significant portions of the Haskell's containers library. The authors attest that no bugs were found during this experience, but their verification effort lead to some direct optimizations in the code of the library. One key difference between our methodology and this works is the effective treatment of arithmetic, in particular the proof of arithmetic overflows absence. When it comes to prove that the size of a set container does not exceed 2 63 -1 elements, the authors invoke a space argument (the amount a memory required to store such a data structure) to translate the Haskell's Int type to Coq's Z type of unbounded integers. Such an argument does not always hold, especially in a setting of a pure language like Haskell, where memory sharing is of great importance. The storage needed for the set container can thus be dramatically reduced. Within our methodology, our proofs are always done under some form of bounded arithmetic.

Using the KeY verification tool, Stijn de Gouw, Frank de Boer, and Jurriaan Rot built a proof of two classical sorting algorithms, Counting sort and Radix sort [START_REF] Stijn De Gouw | Proof pearl: The key to correct and stable sorting[END_REF]. Pursuing their efforts to verify the implementation of massively used algorithms, this team of researchers embraced the challenge of verifying TimSort, the default sorting algorithm for generic collections of the JDK platform. Interestingly, the team was not able to provide a correctness proof for the implementation of TimSort simply because this was broken. This is reported in the work by Stijn de Gouw et al. [START_REF] Stijn De Gouw | Verifying openjdk's sort method for generic collections[END_REF][START_REF] De Boer | Open-JDK's Java.utils.Collection.sort() Is Broken: The Good, the Bad and the Worst Case[END_REF]. More recently, Bernhard Beckert, Jonas Schiffl, Peter Schmitt, and Mattias Ulbrich used KeY to formally verify JDK's dual pivot quicksort implementation [START_REF] Beckert | Proving jdk's dual pivot quicksort correct[END_REF], the default sorting algorithm for integer or long arrays.

Refinement.

One key ingredient in our approach is the ability to refine a Why3 module containing specifications by a Why3 module containing the implementation, with suitable verification conditions generated to ensure correctness of refinement. This is reminiscent of other systems using a refinement approach. One obvious example is the B-method [START_REF] Abrial | The B-Book, assigning programs to meaning[END_REF]. A fundamental difference, though, is that we only proceed in one step, where a B machine is typically refined in several steps. Nonetheless, Why3 modules are richer than B machines. These contain only a global notion of state, whereas in Why3 we can refine record types containing mutable fields, using them locally, and then refine the definition of such data types.

Closer to our work is the integration of module refinement in the Dafny program verifier by Leino and Koenig [START_REF] Koenig | Programming language features for refinement[END_REF]. Dafny module system does not make distinction between interface and implementation: the same notion of module is used both to give abstraction and to refine it. When refining a module in Dafny, one may give definitions to the data structures and methods left uninterpreted in the interface module, bring additional declarations, and refine previously given specifications. The main difference between module refinement in Dafny and Why3 concerns mutable data structures. In Dafny, mutable state is encapsulated within a class and dynamic frames are typically used to control side effects. In Why3, mutable data is encapsulated within record types, and it is the type system that controls side effects.

Mixins [START_REF] Ancona | An algebraic approach to mixins and modularity[END_REF] is another example of a flexible module system that can mix uninterpreted symbols with defined ones. However, contrary to Why3 and Dafny module systems, mixins are designed for programming purposes only.

Proof of programs equivalence. In Sec. 5.3.2, we described our approach to specify the behavior of stateful higher-order functions. We use the equivalent clause of OSL to give a program which we state to be operationally equivalent to a specific higher-order function. Such a program is then appended to the result of the Why3 extraction mechanism. This is not satisfactory since we are not even proving the safety of that piece of code. In order to circumvent this pitfall in our methodology, it would be interesting to explore means to extend Why3 with the ability to prove equivalence of two programs, in the style of Relational Hoare Logic [START_REF] Benton | Simple relational correctness proofs for static analyses and program transformations[END_REF]. A possible direction would be to adapt the work of Gilles Barthe, Juan Manuel Crespo, and César Kunz [START_REF] Barthe | Relational Verification Using Product Programs[END_REF] on the use of product programs to verify relational properties between two programs.

Memory model manipulation.

Our experiments with mutable data structures, presented in Sec. 5.2 and 5.3.3, show that is feasible to conduct proofs on pointer-based mutable data structures using the WhyML language. However, we feel that a better tool support from Why3 would significantly reduce the effort and time spent around the construction of memory models, as well as reasoning about common memory manipulation operations. To this end, we have already started a prototype tool that takes as input a (possibly) recursive mutable OCaml data type, and automatically generates a suitable memory model, together with memory interaction functions and a driver for extraction. In the future, such a prototype tool could become a Why3 plugin, working in very similar way as Jessie [START_REF] Moy | The Jessie plugin for Deduction Verification in Frama-C -Tutorial and Reference Manual[END_REF] works for Frama-C. On the other hand, to relieve some of the effort of reasoning about an explicit memory model, we plan on equipping Why3 with some form of support to reason about memory contents and evolution. This could be, for instance, a separation logic library, as in KIV [START_REF] Reif | Proving system correctness with KIV 3.0[END_REF].

Finally, our use of small heaps statically guarantees the separation of two memory fragments passed as arguments to our memory model functions. This relies on the principle that memory is represented as a private data type and is only updated through abstract functions. We argue that is ensures a correct manipulation of the heap, e.g., we cannot allocate the same pointer in two distinct portions of the heap. This is, however, a rather non-trivial argument which deserves a proof of soundness. In order to formally express such a statement about memory manipulation, we would certainly have to depart from the logic of Why3 and use a proof system based on a richer logic, such as those based on separation logic [START_REF] Charguéraud | Characteristic formulae for the verification of imperative programs[END_REF][START_REF] Krebbers | The essence of higher-order concurrent separation logic[END_REF].

L'éducation c'est la répétition.

French saying

A Modular Way to Reason About Iteration

Iteration is a central concept in programming. It can be as simple as a while loop or a recursive function, but it can also appear as a more complex artifact, such as a cursor, a higher-order iterator, a generator, or a lazy list. When it comes to verifying the correctness of a program, we need tools to reason about iteration. Typically, we provide a suitable loop invariant for a while loop and a contract for a recursive function. In this chapter, we consider the problem of verifying programs where iteration is performed by other means, such as cursors or higher-order iterators.

Our first goal is to devise an approach to specify an iteration process, independently of how this is implemented. We seek for a modular specification, which clearly establishes an abstraction barrier between implementation and client code. A second goal is to use such a specification in the context of deductive program verification. We validate our work using Why3, but the idea is broader and could be implemented in any other deductive verification tool. We experimentally validate our approach through the verification of several implementations of cursor and higherorder iterators, as well as client code. The results of this chapter were partially presented at JFLA 2016 [START_REF] Filliâtre | Itérer avec confiance[END_REF] and at NFM 2016 [START_REF] Filliâtre | A modular way to reason about iteration[END_REF].

Specifying Iteration

We present in this section our proposal to formally specify iteration. We use several examples to illustrate this proposition, including cases of non-deterministic and infinite iteration. In particular, we are interested in answering the following challenges:

• Iteration is not necessarily the traversal of a data structure. It can be, for instance, the result of an algorithm, such as the enumeration of all prime numbers.

• Iteration is not necessarily finite, as in the aforementioned case of prime numbers.

• Iteration is not necessarily deterministic. The simplest example is that of a symbol generator.

From the client point of view, the only required property is that the next element is distinct from the previous ones. Another example is the traversal of a set where elements are presented in some unspecified order. When the iteration is deterministic, however, we want to be able to specify it.

• When iteration depends on mutable data, client code may put iteration in some inconsistent state. In Java, for instance, this problem is solved by maintaining version numbers and by raising an exception in the case of a concurrent modification. In our case, we wish instead to be able to prove, statically, that there is no concurrent modification.

• When a data structure is abstract (for example, a set for which we do not know the implementation) we still want to be able to specify an iteration over its elements and to verify a program using such an iteration. Even when we have access to the implementation of the iteration, we are still interested in performing verification in a modular way with an abstraction barrier. It means verifying the client code independently of a particular implementation for the iteration.

We propose to specify iteration in terms of the finite sequence v of the elements enumerated so far, and only those. This can be depicted as follows:

v 0 v 1 • • • v n-1 already visited ? ? • • • ? to come
By imposing no particular property about the forthcoming elements, we are able to cope with nondeterministic and (potentially) non-terminating enumerations. To reason about the evolution of the iteration process, we introduce two predicates: the first predicate, called permitted, characterizes the elements of v and we can consider such a predicate as the invariant of iteration; the second predicate, called complete, indicates whether the iteration is completed. In the following, v denotes the length of v, v[i] denotes the i-th element of v (assuming a 0-based indexation), and x ∈ v means that x occurs in v.

Let us illustrated the use of permitted and complete through several examples. Consider for instance the iteration over an array a, from left to right. The first predicate, permitted, is as follows:

permitted(v, a) v ≤ length(a) ∧ ∀i. 0 ≤ i < v =⇒ v[i] = a[i]
In other words, the sequence v is a prefix of the array a. The second predicate, complete, simply compares the length of v with that of a:

complete(v, a) v = length(a)
Specifying a right to left iteration is just a matter of changing the definition of permitted to state the sequence v forms a suffix of the array.

Let us now consider the iteration over the elements of a finite set s, in a non-deterministic way. Such an iteration can be specified as follows:

permitted(v, s) distinct(v) ∧ ∀x. x ∈ v =⇒ x ∈ s complete(v, s) v = card(s)
The condition distinct(v) means that the elements of sequence v are pair-wise distinct. This is needed to reflect the fact s is a set (and not a bag), so no element can be visited twice. We require, as well, the elements of v to be elements of s. Since we do not require any additional property, we have a non-deterministic iteration. The iteration is completed whenever the length of v is equal to the cardinal of s. Let us now specify, instead, a deterministic iteration over the elements of s. One way to do so is to introduce some oracle function elements that returns a sequence containing the elements of s in the order they will be visited. Then, permitted merely says that we have already visited a prefix of this sequence, that is,

permitted(v, s) prefix(v, elements(s))
with a natural definition for prefix:

prefix(s 1 , s 2 ) s 1 ≤ s 2 ∧ ∀i. 0 ≤ i < s 1 =⇒ s 1 [i] = s 2 [i]
With such a specification, the behavior of the enumeration is determined from the beginning. For instance, if the elements of s are totally ordered, then elements(s) could be the sorted sequence of the elements of s.

Let us switch to examples of iteration that are not traversals of a data structure. Consider for instance an iteration obtained by the repeated application of a function f starting with some initial value x 0 , that is, the infinite sequence

x 0 , f(x 0 ), f(f(x 0 )), f(f(f(x 0 ))), . . .
One way to specify it is as follows:

permitted(v, x 0 , f) ∀i. 0 ≤ i < v =⇒ v[i] = f i (x 0 )
assuming f i is defined as the i-th functional power of f. To account for the fact that this iteration never halts, we simply define complete as follows:

complete(v, x 0 , f) false Note that, in this case, both permitted and complete are parameterized by the value x 0 .

The next example is the specification of a scanner for a possibly infinite channel c. The elements of v are characters and a special character EOF marks the end of the channel. The specification looks like:

permitted(v, c) • • • ∧ ∀i. 0 ≤ i < v -1 =⇒ v[i] = EOF complete(v, c) v > 0 ∧ v[ v -1] = EOF
The first part of the definition of permitted, marked with . . . , specifies that the channel, for instance, is only composed of alpha-numeric characters. The given specification covers both the case of a finite channel, with a terminal EOF, and the case of an infinite channel, where EOF never shows up.

Our last example is that of a symbol generator, i.e., a program that generates fresh symbols on demand. Its output is an infinite iteration of distinct symbols, which we specify as follows:

permitted(v) distinct(v) complete(v) false
In this case, the specification does not depend on any information other than the sequence v itself.

Cursors

A cursor [START_REF] Coplien | Advanced C++ Programming Styles and Idioms[END_REF] is a data structure used to traverse collections, where the consumer is in control of the iteration process. The word "collection" is to be taken here broadly, as it does not necessarily means a physical data structure. Each time a new element is needed, one needs to explicitly interact with the cursor. Cursors are broadly used in C++ and Java, for instance. We adopt a model where we interact with the cursor via two functions: has_next returns a Boolean indicating the existence of a next element in the iteration; and next, which advances to the next element and returns it. The latter operation updates the cursor via a side effect. A typical client code looks like Syntax c <-create_cursor(...) while has_next(c) do

x <-next(c) ...

In Java, the "for each" loop construct for (E x: ...) is nothing more than syntactic sugar for the above.

In this section we describe the use of predicates permitted and complete to formally specify what is a cursor (Sec. 6.2.1), to verify a cursor implementation (Sec. 6.2.2), and to verify client code (Sec. 6.2.3). Finally, we apply our approach to specify and formally verify several case studies (Sec. 6.2.5 and 6.2.6).

Cursor Specification

In this section, we detail on a generic specification for the cursor data structure. We introduce two types: type elt of the elements enumerated by a cursor, and type t for the collection whose elements we enumerate, as follows: This is a private type, with two fields: ghost field visited stands for the sequence of already enumerated elements by the cursor; field collection represents the collection for which the cursor is defined. Being exclusively composed of ghost fields, the cursor type has no physical representation. For every new instance of a cursor we are supposed to refine type cursor with new, possibly regular, fields. One can think of a private type as a type for which we are not yet aware of the full definition. Let us note that we equip type cursor with the permitted property as the type invariant. This follows the idea that permitted acts as an invariant of the iteration process. Why3 generates a verification condition of the form ∃ v, t. permitted t v to ensure there is an inhabitant of type cursor that satisfies the type invariant. To prove such a formula, we add to our formalization the following axiom: WhyML axiom permitted_empty: forall t. permitted t empty It simply states that permitted always holds for an empty sequence of visited elements. When we shall provide concrete definitions for types t and elt, as well as for predicates permitted and complete, we can then turn such an axiom into a provable lemma.

Using predicates complete and permitted, we can provide suitable contracts for functions has_next and next. The first one, is declared as the following unimplemented function: Following the pre-condition of this function, we can only call next whenever the iteration is not complete. The postcondition ensures the returned element is append to the end of the visited sequence. This is done via a side effect, which we state in the writes clause of the function.

To be able to use cursors, we also need an operation to create a new value of type cursor. We name such an operation create, and declare it as follows: Let us note that, at any moment, we refer to predicate permitted in the contract of the cursor operations. Thanks to the cursor type invariant, any operation manipulating a value of type cursor must preserve the permitted property. In other words, the cursor operations assume the invariant as a pre-condition and that it is re-established at the postcondition. For instance, the writing operation of function next must not invalidate the cursor invariant. Whenever we call next, Why3 generates verification conditions including the permitted condition in the premises of the proof context.

Example: cursor specification for arrays. We detail on a specification for a cursor over arrays. We encapsulate the WhyML code within a module named CursorArraySpec, and we begin by declaring types elt and t as follows The permitted sequences are those that form a prefix of the collection, and the iteration is complete when the length of the visited sequence is the same as the length of the collection. We conclude module CursorArraySpec with the declaration of the cursor operations:

Cursor Implementation

A specification is nothing without a corresponding implementation. In this section, we present the actual implementation of a cursor to traverse an array. We shall equip type cursor with some regular fields, and give concrete implementations to the cursor operations. At end, we shall prove that the chosen implementation actually refines the cursor specification given in the previous section.

We encapsulate the WhyML implementation in a module named CursorArrayImpl, which begins with the definition of the cursor type, as follows: We traverse an array by keeping in the mutable field index an integer value that corresponds to the index of the next element to be enumerated. Such a property is expressed in the first line of permitted. The next two lines express the fact that visited is always a prefix of collection.

To define predicate complete, we keep the same definition as in the previous section: The visited sequence is initially empty, the field collection is initialized to the array a, and the field index starts at 0.

To conclude module CursorArrayImpl, we show that this module is a valid implementation of module CursorArraySpec, via the following clone expression: We do not need to include predicates permitted and complete in the substitution since these are already defined in module CursorArraySpec. Such a refinement includes a proof that the type invariant of cursor in module CursorArrayImpl implies the type invariant of cursor in CursorArraySpec, as well as specification inclusion for operations has_next, next, and create. All the generated verification conditions, either for the implementation of cursor operations and the module refinement, are automatically discharged in no time.

Cursor Client

To illustrate the use of cursors, we give the WhyML implementation of program sum_array, which uses a cursor to traverse an array of integer to compute the sum of its elements. We begin by importing an instance of the CursorArraySpec module, where occurrences of type elt are replaced by type int, as follows: The loop invariant states that reference s contains the sum of already enumerated elements. The termination of function sum_array is easily proved by annotating the while loop with the termination measure length c.collection -length c.visited. The type invariant of cursor guarantees that c is kept in a valid state, which allows to correctly call function next and has_next. All the generated verification conditions for function sum_array are easily discharged.

The relation between the cursor specification, concrete implementation and the client code can be depicted as follows:

Cursor Array Spec Cursor Array Impl

Client

The vertical arrow represents the refinement relation between modules CursorArrayImpl and CursorArraySpec. On the other hand, the horizontal arrow stands for the use of CursorArraySpec in the implementation of the client code.

Collection Modification

If we consider mutable data structures, nothing prevents us from modifying its contents while a cursor is traversing it. We could imagine the following code:

WhyML let c = array_cursor a in let x = next c in a[0] <-42; let y = next c in that modifies array a after creating the cursor c. This is a typical example where we do not want to be able to prove that, at moment of the the second call to next, the cursor c respects its invariant.

Outside the context of program verification, checking if a data structure is coherent with an associated cursor is typically done via dynamic tests, which inspect a version number included in both the cursor and the data structure. In case the two numbers differ, an exception is raised. This is the case of the Java standard library. In our case, on the contrary, we statically ensure the coherence between a cursor and a collection through the set of generated verification conditions. There is no need for dynamic tests or exception raising which, additionally, has the benefit of producing a more efficient code.

Case Studies

In this section, we present several examples of cursors and associated client code, as well as their formal correctness proof. One key idea is to show that cursors are not limited to data structures traversal. All the given examples are proved correct, with respect to the given specification, in a completely automatic way.

Gensym

The first example we present is that of a fresh symbol generator, i.e., a cursor that returns a sequence of values without any repetition. The permitted predicate is easily defined, as follows:

WhyML predicate permitted (v: seq elt) = distinct v
where distinct is a predicate of the Why3 standard library which specifies that the elements of a sequence are pair-wise distinct. The predicate complete is even simpler, since this is the case of a non-terminating iteration:

WhyML predicate complete (v: seq elt) = false
We suppose here that the type elt is infinitely inhabited, otherwise it would not be possible to implement such a cursor. If we use the example of a generator for natural numbers, a possible implementation of the cursor type is as follows: We omit here the function specification as this does not change with respect to the one we presented in Sec. 6.2.1. The implementation of function has_next is trivial, as it always returns true, and so we do not show it here.

Depth-first Search

The next example is that of a cursor to perform a depth-first search (DFS) traversal of a graph. We represent here a graph through a type vertex for the nodes, and the succ function that associates to each node the set of its successors: The statement path v1 s v2 means that there is a path from node v1 to v2, following the nodes contained in sequence s. We chose to exclude v1 from the sequence. Let us note that the definition of path is given entirely by means of universal quantification and arithmetic, instead of declaring an inductive or recursive predicate. This is intentionally done, as it leads to a much more automated proof, similarly to what we discussed in Sec. 5.2.2. From the predicate path we can deduce the following reachability predicate:

WhyML predicate reachable (v1 v2: vertex) = exists s. path v1 s v2

Expressing that we reach vertex v2 from v1 is as simple as stating that there is a path between the two, i.e., there exists a sequence s for which the statement path v1 s v2 holds.

As we did for the previous cursors, we start the definition of the DFS cursor by introducing a type elt for the elements being enumerated, and a type t for the collection: The type of the collection is of no interest here, since we represent the graph through the succ function. The clone clause is used here to import an instance of the Graph module (which assembles the graph-related definitions, i.e., type vertex, function succ, predicate path, etc.) where every occurrence of vertex is replace by elt.

To perform the DFS traversal, we follow a traditional approach: we keep a stack of nodes, together with a set of marked nodes that have already been reached by the traversal. We provide the following cursor type: The source field stores the starting node of the traversal, for specification purposes only. The permitted property of this cursor type is a 5-place predicate, as it is parameterized by the stack, the source of the traversal, and the set of marked nodes, besides the collection and the sequence of visited elements. The definition of the permitted predicate is actually the conjunction of two other predicates, as follows:

WhyML predicate permitted (collection: t) (visited: seq elt) (stack: list elt) (source: elt) (marked: set elt) = permitted1 visited stack source marked /\ permitted2 visited marked

The reason to split such a definition between predicates permitted1 and permitted2 shall become evident when we present the proof of function next. For now, let us give the definition of each of these predicates, step-by-step. First, the permitted1 predicate states that the elements of stack are pair-wise distinct, WhyML predicate permitted1 (collection: t) (visited: seq elt) (stack: list elt) (source: elt) (marked: set elt) = distinct stack /\ that the source is always marked, WhyML mem source marked /\ that the source is the head of the stack when the traversal starts, As to the permitted2 predicate, this is much simpler defined, as follows:

Cursor specification. The polymorphic type of binary trees is defined in Why3 standard library, as follows:

WhyML type tree 'a = Empty | Node (tree 'a) 'a (tree 'a)

The tree 'a type is defined together with the following logical function elements WhyML function elements (t: tree 'a) : seq 'a = match t with | Empty -> empty | Node l x r -> elements l ++ cons x (elements r) end which specifies the sequence of the tree elements when traversed in-order.

We begin with the logical specification and declaration of the in-order cursor operations. We encapsulate the WhyML code in the InorderCursorSpec, which starts with the declaration of the elt and t types: Note that we equip the elements of type elt with an equality operation, defined via the abstract function eq. We need to introduce such an operation since polymorphic equality is, in WhyML, exclusively used for specification purposes. Next, we add to the InorderCursorSpec module the definitions of predicates permitted and complete, as follows:

WhyML predicate permitted (t: t) (v: seq elt) = length v <= length t /\ forall i. 0 <= i < length v -> v[i] = t[i] predicate complete (t: t) (v: seq elt) = length t = length v
Function elements is declared in the Why3 standard library as a coercion, which allows us to write very clean and compact definitions for the above predicates. Every application of the t symbol to a logical function is automatically translated to (elements t), the infix sequence of the tree elements. The permitted relation simply states the visited sequence is a prefix of the in-order sequence of the collection; the traversal is complete once the length of the elements sequence of t is the same as the length of the visited sequence.

The type of the in-order cursor keeps the two ghost fields visited and collection, where we know that collection is of type tree elt, as follows: The proof that the updated cursor respects the type invariant is automatically done by SMT solvers using, once again, the zipper_spec lemma. The remaining verification conditions for the next function are easily discharged by SMT solvers. Finally, we give the definition of function has_next. This is implemented as follows: The enumeration of the tree elements is complete once the zipper is equal to Done. Module InorderCursorImpl terminates with the proof that this particular implementation of a in-order cursor is a refinement of the module InorderCursorSpec. This is achieved via the following clone clause: Cursor client. We present now a client program using the in order cursor. We chose the classical algorithmic problem known as same fringe, which consists in deciding whether two binary trees present the same sequence of elements, when traversed in-order. In other words, we seek here to write a function with the following contract:

WhyML let same_fringe (t1 t2: tree elt) : bool ensures { result <-> elements t1 = elements t2 } A possible implementation for the same_fringe function would be to explicitly build the in-order sequence of each tree and then compare them. This is, however, an inefficient solution. Let us consider, for instance, the two following binary trees:

x huge sub-tree y huge sub-tree

The two trees differ in the leftmost element, as we have x for the left-hand side tree, and y for the right-hand side tree. Following the solution proposed above, we would build two (huge) sequences, which is a shame. Even tough this is a linear solution on the number of elements of the trees, this has the pitfall of building in memory the two sequences, even if the two trees differ directly on the first element issued from the in-order traversal. Another solution would be to build just the sequence of one of the trees, then traverse the second one and comparing each new element issued from the traversal with the head of the sequence. One would still have to explicitly build in memory a sequence of elements, other than the two trees, which can be prohibitive under some circumstances. We propose to explore here a different approach: we build a cursor for the two trees, and we enumerate the elements of each tree while both are equal, with respect to the eq operation. Therefore, we stop the traversal as soon as two elements are different. This is both an elegant and an efficient solution. In the eq_cursors function, we ask each cursor for a new element and compare them. If the two elements differ from each other, we immediately return false and stop the traversal (&& is a lazy operand in WhyML). Otherwise, we keep comparing the two cursors just until one of two situations happens: either the has_next signals the end of the traversal for both cursors, in which case we return True; or one of the cursors stops before the other, in which case we return False. We encapsulate our solution to the same-fringe problem in the WhyML module SameFringe. Note that we import the definitions of the InorderCursorSpec module into the SameFringe module.

This means that we establish here an abstraction barrier: we implement and formally prove a client code for our in-order cursor knowing only about the specification of such a cursor. This follows our approach of modular proofs that we discuss in Chap. 5. Using function eq_cursors, we can easily deduce the implementation of same_fringe, as follows: Putting all together. We complete our presentation of the specification, implementation, and use of a cursor to traverse a binary tree in-order by showing how we can link the client code with a specific implementation of such a cursor. We follow the approach we presented in Sec. 6.2.3: we use the clone instruction to create an instance of the SameFringe module, where each operation and declaration of the InorderCursorSpec module is replaced by the counter-part of the InorderCursorImpl. We write the following WhyML module: Thanks to this linking operation, we are able to extract an executable OCaml implementation of the same-fringe function, using the in order cursor. Supposing the WhyML code is contained in a Why3 file named inorder_cursor, the following command can be used to extract the code of the Linking module: The result of running such a command is given in Fig. 6.1. It is worth pointing out that there is currently no mechanism in Why3 to verify that a clone instruction replaces all the undefined symbols of a WhyML module. This prevents us from extracting OCaml code containing functor applications. Indeed, the code in Fig. 6.1 represents the defunctorized code of an hypothetical application of functor SameFringe to its argument. Let us note the use of the custom extraction driver sf.drv in the extract command line. Such a driver simply defines that every occurrence of the eq operation is to be replaced by the OCaml polymorphic equality, as follows:

Driver module inorder_cursor.InorderCursorImpl syntax val eq "%1 = %2" end We note, as well, that similarly to the example of the array cursor (Sec. 6.2.3, page 145), we pass the --recursive option to the extract command, in order to extract all the dependencies of the Linking module. Without such an option, we would end up with only the extracted code of the eq_cursors and same_fringe functions.

Other Case Studies

Table 6.1a shows the lines of code, the lines of specification (functions contracts, invariants, and auxiliary lemmas), and the total verification time (in seconds) for each cursor.

We have also implemented and verified a number of client programs that use cursors. We always keep the proof of client code modular in respect to the cursor implementation, i.e., the client programs are only using the cursor interface and have no access to the underlying implementation. Our programs include summing the elements of an array, computing the length of a list, searching for a particular element in some abstract collection, checking for the existence of a path in a graph using a DFS cursor, merging two ordered sequences, and implementing Boyer & Moore's "mjrty" algorithm [START_REF] Boyer | Mjrty: A fast majority vote algorithm[END_REF] using array cursors. Table 6.1b shows the lines of code, the lines of specification, and the total verification time (in seconds) for each program.

For all the listed examples, both implementations of cursors and client codes, all generated verification conditions are discharged automatically, using a combination of the SMT solvers.

Higher-Order Iteration

In programming languages featuring first-class functions, iteration is frequently implemented as a higher-order function that takes as argument a function to be applied to each element of the enumerated sequence. We refer to such function as the consumer function. A common example is the fold function [START_REF] Hutton | A tutorial on the universality and expressiveness of fold[END_REF]. We consider here fold to be a function of three arguments, with the following type: fold : (acc → elt → acc) → collection → acc → acc

The second argument is the collection to be iterated over, of type collection. The first argument is the function to be applied in turn to each element of the collection. This function takes two arguments, an accumulator of type acc and an element of type elt, and returns an updated value for the accumulator. The accumulator stands for the value being computed by fold and its initial value is the third argument of fold. Thus, if the elements of a collection c are traversed in order x 1 , . . . , x n , a call to fold f c a amounts to evaluating f(. . . (f (f a x 1 ) x 2 ) . . .) x n . For instance, assuming the elements of c are integers, we can sum them as simply as fold (λ s x. s + x) c 0 where λ introduces an anonymous function.

The concept of fold is ubiquitous in functional programming languages. The OCaml standard library, for instance, offers many different instances of fold iterators for data structures such as lists, arrays, or maps. The recent introduction of closures in languages such as C++ and Java eases this style of programming. In these languages, higher-order iterators coexist with cursors, allowing the user to choose the paradigm that suits best. The main difference between the two is that control is given to the producer in the case of a higher-order iterator, while it is given to the consumer in the case of a cursor.

In this section, we use predicates permitted and complete in order to specify and verify higherorder iterators. As we did with cursors, we intend to verify both implementations of fold functions (Sec. 6.3.1) and client code (Sec. 6.3.2). One way to tackle the verification of higher-order functions is to use a higher-order (program) logic, in such a way that one can quantify over the specification of function arguments. There exist already several systems in which we can do so; we will discuss those in Sec. 5.5. We consider here a different approach, which only requires first-order logic. This is possible thanks to the abstraction barrier provided by the permitted/complete predicates. On both sides of this interface, we are making distinct first-order program proofs, one for the implementation of fold and one for each call to fold. We present several case studies for our methodology (Sec. 6.3.3 and 6.3.4).

Fold Implementation

We use the fold_left function over lists, from the OCaml standard library, as the guiding line to the presentation of our proposition for the specification and proof of fold-like iterators. This is implemented as follows: We scan the list l from left to right, applying the consumer function f to acc, the current value of the accumulator, and the next element in the iteration.

Similarly to what we did for cursors, we want to specify iteration performed by fold iterators by means of the permitted and complete predicates. For the case of the fold_left function, it is easy to derive the definition of such predicates: it is valid to iterate while the visited sequence forms a prefix of the whole list, and we are done as soon as the length of the visited sequence is equal to the length of the list being iterated, i.e., let rec fold_left (ghost v: seq elt) (ghost inv: acc -> seq elt -> bool) (ghost l0: list elt) (f: acc -> elt -> acc) (acc: acc) (l: list elt) : (ghost vres: seq elt, accu: acc) ... requires { inv acc v } requires { forall v acc x. inv acc v -> permitted (snoc v x) l0 -> not (complete v l0) -> inv (f acc x) (snoc v x) } ...

ensures { inv accu vres }

The first new pre-condition and the new postcondition ensure that inv is, indeed, and invariant property of the fold_left function. The other added pre-condition specifies the behavior of the consumer function f over the accumulator value. For every sequence v, accumulator value acc, and an element x of the list, if inv holds for acc and v, the element x is the next in the iteration (permitted holds for snoc v x), and the traversal is not yet complete, then a call to f preserves the inv property for the sequence snoc v x. We have now a complete specification of the fold_left iterator. To take into account the extra argument inv, we update the implementation as follows:

WhyML = match l with | Nil -> (v, acc) | Cons x r -> fold_left (snoc v x) inv l0 f (f acc x) r end
All the generated verification conditions for this program are automatically discharged, which completes the proof of fold_left in terms of the permitted, complete, and inv predicates.

A first-order approach. Even if the described implementation gives us a complete specification of the behavior of a fold-like iterator, we argue that this introduces a cumbersome WhyML development. The fold_implementation gets difficult to handle, since we have introduced several ghost arguments (which we have to instantiate in every client code), and the function contract might be hard to follow. We believe that, from a WhyML programmer perspective, it would be more natural and amenable to abstract out some of the fold_left elements and create an interface to be cloned by clients of the iterator, just as we did for cursors. Let us precise our statement: the fold_left implementations always acts on the same values of the inv predicate, the list l0, and the consumer function f. These symbols are defined once and for all, which makes them good candidates to be lifted to top-level symbols of a WhyML module. These can be later instantiated to match the needs of a particular client code of fold_left. Following such an approach, we devise module FoldLeft and introduce the l0 constant, as follows: and the consumer function, using the same definitions for permitted and complete, as follows:

Fold Client

In this section, we detail on a client program to our implementation of the fold_left iterator. Our use case is the list_length function, an implementation that calls fold_left to compute the number of elements in the list.

We begin a FoldLeftClient module by defining a function f, to be used as the consumer function, as follows: This states that any moment, during the iteration, the value of acc, the accumulator, is equal to the number of elements in v, i.e., we count each element of the list one and only once.

We can now instantiate the FoldLeft module with the elements we have just defined. This is done via the following clone expression: WhyML clone FoldLeft with type elt, type acc, predicate inv, val f Let us note that there is no need to introduce and then instantiate constant l0. For every symbol missing in the clone substitution, Why3 automatically uses the one defined in the FoldLeft module in the remaining of the WhyML development. To instantiate function f, Why3 generates verification conditions that stand for the specification inclusion of such a function. These are easily discharged, since the inferred postcondition for the implementation of f straightforwardly implies the postcondition given to f in the FoldLeft module.

We can, finally, implement and specify function list_length, as follows:

WhyML let list_length () : (ghost vres: seq elt, accu: int) ensures { accu = length l0 } = fold_left empty 0 l0 end This function takes no argument, as every needed element is a top-level symbol of the same WhyML module. It is worth pointing out that, even if we know the definition of function f, after module cloning Why3 only uses the specification of f from the FoldLeft module to generate verification conditions for list_length. Thanks to the specification declared for f, the definitions of permitted, complete, and the inv predicates, SMT solvers are able to discharge in no time the verification conditions generated for list_length.

end end

Using these extra preconditions, SMT solvers are able to prove every verification condition related to the permitted property. The remaining of the verification conditions generated for the fold function are also automatically discharged. This completes the definition of module FoldTree.

Fold client. The client program of fold uses this iterator to compute the number of elements of a given binary tree. This is very similar to function list_length from the previous section. We devise the following WhyML module: The regular use (as a coercion) of logical function elements to reason about the elements of a binary tree allows us to specify tree_length exactly as we did for the list_length function. This is, indeed, a very interesting aspect about our modular WhyML development of interfaces and clients of the fold iterators. As expected, all the generated verification conditions for the above module are discharged in no time.

Horner Method

The last example we present is that of a program computing the value of a polynomial. We represent the polynomial c 0 + c 1 x + . . . + c n x n by the list of [c 0 , c 1 , . . . , c n ] of its coefficients, which we assume to be integer coefficients.

Fold implementation.

The fold_right function is the iterator used to traverse a list from right to left. This is implemented in WhyML as follows: Function reverse comes from the Why3 standard library. We can finally give the definitions of permitted and complete using the reversed sequence of a list, as follows:

WhyML predicate permitted (v: seq elt) (l: list elt) = length v <= length (elements l) /\ forall i. 0 <= i < length v -> v[i] = (elements l)[i]
predicate complete (v: seq elt) (l: list elt) = length v = length (elements l)

Predicate permitted states that the visited sequence forms a reversed suffix of list l.

The implementation of fold_right is given in Fig. 6.2. The consumer function f, declared in lines 5-9, presents exactly the same contract as in the previous fold examples. Function fold_right is given three arguments: the sequence v of visited elements, the value acc which corresponds to the current value of the accumulator, and finally list l, the list being iterated over. It returns the ghost sequence vr, the final sequence of visited elements, and the final computed value for the accumulator, which we name accu. The contract given in lines 15-20 expresses the total correctness of the fold_right implementation. The first pre-condition states that, at each call of fold_right, the sequence v remains empty. Contrarily to the fold_left implementation, the sequence of visited elements remains empty until the end of all recursive calls. It is only when the fold_right returns that we feed function f with elements from the list, and consequently appending those to the sequence v. The pre-condition in line 16 and the postcondition in line 19 express that the sequence of recursive calls preserve the inv and permitted predicates. The precondition in line 17 and the postcondition in line 20 stand for some auxiliary specification, specific to fold_right traversal. This pre-condition states that at entry of each recursive call, l forms a prefix of the whole l0 list; the postcondition ensures that a recursive call enumerates the elements of l, and only those. All the verification conditions generated from the given implementation and specification are automatically proved.

Fold client.

We can evaluate a polynomial using the Horner form c 0 + x(c This uses the verified implementation of the fold_right iterator, showed previously in this section. The proof of val_of_pol uses only the definitions of permitted, complete, and the inv predicate. The implementation of the consumer function is hidden by the refinement and is never used in the proof of val_of_pol. All the generated verification conditions for this program are easily proved by SMT solvers.

Other Case Studies

Using our proof methodology, we have implemented and verified several other instances of the fold function. These include a fold_left over lists, a fold_left over arrays, a fold over sets, an inorder_fold over binary tree, and finally a fold that enumerates the integers in a given range. Table 6.2a shows the lines of code, the lines of specification, and the verification time (in seconds) for each fold implementation. All of these examples are proved automatically.

From a client code perspective, we have used our approach to verify several examples. These include computing the length of a list (both using fold_left and fold_right), converting a list into a set, computing the cardinal of a set, making a copy of a set, and computing the sum of all integers within a given interval. Table 6.2b shows the lines of code, the lines of specification, and the total verification time (in seconds) for each program. The whole set of verification conditions generated for these examples are proved fully automatically.

Discussion and Related Work

Tool-independent approach. As we have mentioned throughout this chapter, our approach to specify iteration based on predicates permitted and complete is not tied to any particular verification technology. We conducted all of our experiments within the Why3 framework, but we argue that it would not be a major task to translate these to other verification tools, e.g., Dafny or Viper [START_REF] Müller | Viper: A verification infrastructure for permission-based reasoning[END_REF].

It is even more interesting to observe that our approach also combines well with the use of interactive proof assistants and very rich specification languages. The proof, by François Pottier of the OCaml hash table implementation [START_REF] Pottier | Verifying a hash table and its iterators in higher-order separation logic[END_REF] supports this claim. In this work, the OCaml code is translated to Coq by the CFML tool and is verified using higher-order separation logic. The iteration operations (fold and cascades) over hash tables are specified in terms of the sequence of elements the consumer may observe. Such a sequence is characterized using predicates permitted and complete. In fact, the name used to refer to the two predicates are inspired by this work (originally, we were using enumerated and completed). Contrarily to our experiments in Why3, the setting of a CFML and Coq propose a very rich specification logic and type system, which enables reasoning about imperative higher-order functions.

Catherine Dubois and Alain Giorgetti use our specification in terms of permitted/complete predicates to provide WhyML implementations of generators. Then, a correct-by-construction implementation of these generators is extracted to OCaml code, which can later be used to test Coq developments, via the QuickChick plugin.

Specifying and proving cursors. The idea of formally specifying and proving cursors is not new. Weide presents a formal specification for the cursors' behavior [START_REF] Weide | SAVCBS 2006 challenge: Specification of iterators[END_REF] using the RESOLVE language [START_REF] Kulczycki | A language for building verified software components[END_REF]. A collection is modeled as a finite set (in the mathematical sense) and a cursor is specified using a past sequence corresponding to our visited and another future sequence corresponding to remaining elements. A third sequence, original, contains the set of elements of the collection. Under such formalization, a cursor can only be used with finite collections and the traversal is necessarily deterministic. The author also presents a mechanism to ensure coherence, by means of extra operations over cursors, Start_Iterator and Finish_Iterator, that should limit all the cursor uses. In this way, and contrary to our approach, the validity of a cursor can only be verified once the traversal is finished.

Catherine Dubois and Renaud Rioboo provide a FoCaLiZe implementation of functional iterators, addressing the Specification and Verification of Component Based Systems 2006 challenge [START_REF] Dubois | Verified functional iterators using the focalize environment[END_REF].

Jacobs et al. [START_REF] Jacobs | VC generation for functional behavior and non-interference of iterators[END_REF] describe a methodology to specify and verify both implementations and clients of the Iterator pattern, in the context of the Spec# programming system. In their formalism, methods are separated into regular methods and enumerator methods, so the latter can present an invariant clause in their contract. In the contract of such methods it is possible to refer to the keyword values, which represents the set of elements enumerated so far. This resembles our use of the sequence of visited elements. The client programs are for-each loops whose loop invariants can also refer to values. These programs are then translated into regular for loops to be verified. This methodology is only applied to first-order programs and does not support enumeration with side-effects, or the creation of objects performing iteration.

Cursors in future Why3. In the short term, it would be interesting to explore an extension of Why3 with a for loop à la Java, based on cursors. For instance, we could imagine a client code of the form As it happens in Java, this would be nothing more than syntactic sugar for a while loop invoking next at each time a new element is needed. We would have the following WhyML program:

WhyML let s = ref 0 in let c = create s in while has_next c do variant { length c.collection -length c.visited } invariant { !s = sum (fun i -> a[i]) 0 (length c.visited) } let x = next c in s += x done; !s
which is exactly the client program we can find in Sec. 6.2.3. An important difference between directly writing the client code with a while loop and using the (imaginary) for loop is that we can optimize the set of verification conditions that are generated. In fact, since the loop is guarded by the result of has_next, its postcondition result <-> not (complete c.collection c.visited) trivially implies the pre-condition of function next.

Persistent Cursors. We adopt a model where cursors are treated as mutable data structures, with function next updating the cursor via a side effect. This is not mandatory. A cursor can be implemented as a persistent structure [START_REF] Filliâtre | Backtracking iterators[END_REF]. In such a case, function next no longer updates the internal state of the cursor but, instead, returns the next element in the enumeration together with a new cursor. From a point of view of WhyML verified implementations, reasoning with persistent cursors would not imply a drastic reformulation of our development. We would completely remove the mutable character out of our cursor type Specification and proof of higher-order iterators. Many tools exist that tackle the verification of higher-order effectful programs, in particular of higher-order iterators. These are normally based on rich specification logics and type systems. Liquid Types [START_REF] Maxim Rondon | Liquid types[END_REF] is a type system with refinement types extracted from a decidable logic. This type system is used to infer simple "loop invariants" from a given code. In our case, the user supplies the loop invariant and, contrary to the Liquid Types approach, we apply and prove an iterator client without access to the iterator implementation, in a modular way.

Yann Régis-Gianas and François Pottier [START_REF] Régis | A Hoare logic for call-by-value functional programs[END_REF] propose a Hoare Logic for call-by-value pure programs. One cannot use their framework to reason about side effects, but the user can, nonetheless, write effectful programs, for instance divergent ones. The authors detail on a verified implementation of sets as binary trees. This includes a fold-like iterator, as well as persistent iterators to traverse the elements of the data structure. They use both paradigms to implement and verify client programs that compute the number of elements in a set. The given specification can be easily adapted to the framework of permitted/complete, as we show in Sec. 6.3.3.1.

Dependent types and monad structures are used in the F tool [START_REF] Swamy | Dependent types and multimonadic effects in F*[END_REF] as the theoretical basis to tackle the proof of higher-order programs with effects. F can be used both as a programming language and as a proof assistant, featuring a higher-order specification and programming language. This tool has been used to verify many complex effectful programs including cryptographic protocols and the mechanization of lambda calculi metatheory. Even though F* is able to use SMT solvers during the proving process, it seems that the verification of nontrivial (effectful) higher-order programs is out of the realm of automatic provers. In particular, the specification of a higher-order iterator is very similar to what one would write in a general-purpose proof assistant like Coq.

The CFML tool [START_REF] Charguéraud | Characteristic formulae for the verification of imperative programs[END_REF] uses characteristic formulas to verify OCaml code within the Coq proof assistant. A characteristic formula is a higher-order formula that is generated from a source code and that describes its semantics. Using a proof assistant based on higher-order logic, the characteristic formula can be exploited to prove complex properties about that program. Up to now, CFML has been used to verify several nontrivial higher-order imperative programs, including higher-order iterators over mutable data structures. However, the specification used to describe a higher-order iterator is always tied to a specific collection data type.

Higher-order iterators in Why3. We propose an approach to the verification of higher-order iterators in Why3 based exclusively on first-order specification and programming, and on the use of the Why3 refinement mechanism to link client programs with iterator providers. Even if this approach works well in practice, this might be a bit puzzling for a common OCaml programmer, who is used to higher-order iterators. Additionally, our approach demands some knowledge on WhyML modules and associated clone operations. Considering the client program of Sec. 6.3.3.2, it would be much more natural to write a WhyML program as follows: This is close to what an OCaml programmer would expect, with only a little overhead of specification elements. The user-provided invariant characterizes the whole iteration performed by the fold_right function. We have began the development of a prototype tool that reads both implementations and clients (as the above one) of higher-order iterators, together with a specification, and turns them into first-order Why3 programs to be verified. For the case of fold-like implementations, our prototype tool accepts a specification of the form When it comes to effectful higher-order iterators, we can only present a partial answer to this problem. We are able to specify the sequence of elements produced by such an iterator, via the permitted and complete predicates, which we can use to interface the client with the iterator provider. However, we cannot establish strong guarantees about the implementation of the iterator itself. For instance, nothing prevents us from applying the consumer function twice to the third element in the iteration, thrice to the 42nd element and not at all to the last one. This is problematic in the context of a consumer function with side effects. Currently, our prototype tools translates an effectful iterator into a first-order program that uses a cursor to perform the same traversal. Using this approach, we have verified a program that removes duplicate elements from a list. The consumer function updates a hash table with the elements encountered during iteration, which is done via a side effect.

Integration with the VOCaL project. The fact that we do not dispose of a satisfying treatment of stateful higher-order iterators is a major pitfall of our approach and makes it hardly applicable, at least in the immediate, to the VOCaL project. As explained in Sec. 5.3.2, we use the equivalent clause of OSL to introduce an operationally equivalent program to a higher-order function. We could imagine, for instance, to add to OSL a foldspec clause, like we propose for our prototype tool, and to replace all equivalent statements. We would give a precise semantic meaning to the foldspec clause: the consumer function is applied once and only once to each element in the iteration, while the sequence of visited elements should respect the permitted/complete pair of predicates. This approach could be used in the proof of either iterators and client programs. Nonetheless, it remains unclear if the use of foldspec could be smoothly integrated in our toolchain for verified OCaml programs.

Un peu de programmation éloigne de la logique mathématique ; beaucoup de programmation y ramène.

Xavier Leroy

7

Conclusion

As we arrive to the terminus of this work, it is now time to draw some conclusions. If we get back to the title of this document, it is now easy to understand the careful choice of words and why we present them in this order. In this thesis, we extended the Why3 ecosystem with a variety of tools, e.g., the new extraction mechanism and the OSL translation plug-in, and developed techniques, e.g., a general specification of iteration and the mini-heaps approach, that allow us to conduct the proof of modular stateful programs within Why3. Examples range from priority queues implemented as Pairing Heaps, where modularity scales up to the level of OCaml functors, to arbitrary pointer-based data structures, such as our verified union-find implementation. On a more fundamental side, we formalized a representative part of the Why3 extraction mechanism on top of the KidML language, a sub-set of WhyML.

Contributions

Why3 extraction mechanism. The new Why3 extraction mechanism is the core of our work. We have re-implemented from scratch this part of the framework into a robust, modular (scaling up to OCaml functors) extraction machinery. We have been able to successfully use extraction on several verified WhyML programs, in order to produce correct-by-construction executable code. We have mainly used extraction to produce verified OCaml programs. This new extraction mechanism has also been used with success within a verified version of the GMP library [START_REF] Rieu-Helft | How to get an efficient yet verified arbitrary-precision integer library[END_REF], and also to produce correct-by-construction CakeML programs. Another interesting use of extraction is described by Gillaume Melquiond and Raphaël Rieu-Helft [START_REF] Melquiond | A Why3 framework for reflection proofs and its application to GMP's algorithms[END_REF]. In this work, the idea of proof by reflection inside Why3 is pushed forward and is applied to the proof of challenging arithmetic algorithms. In order use the whole expressive power of the WhyML language, the authors add an interpreter to Why3 to recover the result of a decision procedure written as an arbitrary WhyML program. This interpreters operates on the intermediate representation resulting from our extraction.

Our formalization of the extraction mechanism is done in a pen-and-paper style. Since the rise of the POPLmark challenge [START_REF] Aydemir | Mechanized metatheory for the masses: The POPLmark challenge[END_REF], it has become customary to develop such forms of meta-theory within a proof assistant. We know that, in principle, it is possible to develop the formalization of the KidML language and the extraction algorithm, for instance using the Coq proof assistant. In this thesis, we focused on the careful construction of a working extraction mechanism, that is now fully available to the Why3 users. Nonetheless, it is definitely an exciting line of future work to seek a machine version of our meta-theory, as demonstrated by the CakeML [START_REF] Kumar | Software verification with itps should use binary code extraction to reduce the TCB -(short paper)[END_REF] and OEuf [START_REF] Mullen | OEuf: Minimizing the coq extraction tcb[END_REF] projects.

Mini-heaps.

OCaml It is our purpose to keep extending Why3 with means to prove a larger set of OCaml programs. Given the strong constraints of the Why3 type system, some pointer-based data structures cannot be directly encoded in the WhyML language. The solution is to introduce an explicit memory model, i.e., to declare a type for pointers and memory, together with a set of functions to read and write memory. This resembles very much to the approach followed by the Jessie plug-in [START_REF] Marché | Jessie: an intermediate language for Java and C verification[END_REF] of the Frama-C platform.

Our approach to deal with memory models is slightly different from the one that is normally employed by tools that use Why3 as an intermediate verification language. We build memory models targeting only the part of the code that cannot be expressed in WhyML, instead of building a huge memory model to deal with an existing programming language in its entirety. Additionally, we do not declare memory as a global mutable value. Instead, we pass it as a ghost argument to each function in our memory model, which, by the properties of the WhyML type system, directly guarantees some degree of separation between different fragments of the memory. We refer to this approach as the mini-heaps.

Using the described approach, we have been able to verify in Why3 non-trivial pointer-based implementation of data structures. The union-find data structure and the mergesort algorithm over singly-linked lists, both presented in Chap. 5, are two examples of successful application of the mini-heaps approach in the verification of programs manipulating memory in an arbitrary fashion. When it comes to extraction, it only requires a custom driver of very few lines, mapping the memory model operations to OCaml counterparts. Such a mapping is normally straightforward, which makes it very easy to convince ourselves that the driver does not introduce any bug in the extracted code. This approach has reveled to work very well in practice, making it rather pleasant to conduct the proof of complex data-structures within Why3.

Modular specification of iteration. Since iteration is a pervasive concept in programming, we addressed the challenge of specifying and verifying iteration providers, independently of the underlying implementation. This led us to a modular and generic approach to the specification of iteration processes, presented in Chap. 6. The idea is to use a pair of predicates permitted and complete to characterize the finite sequence of elements already enumerated at a given moment of the iteration. Using such an approach, we are able to deal with both finite and infinite iterations, deterministic and non-deterministic, and also with the case when mutable data is changed during iteration. Our approach is not limited to the traversal of data structures; it can be used to specify the result of an iterative algorithm, for instance.

An important aspect about our approach is that it can be employed by different verification tools. We have already mentioned the work of François Pottier in the proof of the OCaml hash table implementation in the CFML tool [START_REF] Pottier | Verifying a hash table and its iterators in higher-order separation logic[END_REF], where the (higher-order) iterators on such structure are specified using the permitted/complete approach. Given the successful use of our approach within CFML, a tool with a very different verification philosophy when compared with Why3, we are confident that our specification proposal can be easily integrated in verification platforms closely related to Why3, e.g., Dafny or Viper.

We applied our specification to the paradigms of cursors and higher-order fold-like functions. We proved several providers of iteration, as well as client applications. An extremely important aspect about these proofs is that we always impose an abstraction barrier with the purpose of hiding implementation details to the client code. This makes echo to the modular word in the title of the this thesis.

Verification of an OCaml library. The ultimate contribution of this work is the successful use of Why3 in the development of VOCaL, the Verified OCaml Library. Using the methodology we propose in Chap. 5, we were able to build verified implementations of several data structures and algorithms in the WhyML language, which are then automatically translated into compilable, correct-by-construction OCaml modules. Other than its functional correctness, we also prove that the obtained OCaml code refines to the specification we provide in the associated interface file. All of these examples are ready to be distributed and used by OCaml programmers.

From a personal point of view this is, perhaps, the most rewarding outcome of this thesis. The excitement about the possibility of seeing our work being using by a potentially large base of OCaml programmers is a most delightful way to conclude these years of research. As a passionate programmer, it is my deepest honor to be able to contribute to the OCaml community and ecosystem, as I have enjoyed some many good moments using this language and the associated tools.

Discussion and Perspectives

With the conclusion of this work, we anticipate some future work that could be of great interest. We describe those here, undertaking a much more research agenda approach, rather than a direct continuation of this thesis.

Modules refinement. Within our toolchain for verified OCaml programs, we make extensive use of the WhyML module system and of the Why3 refinement mechanism to prove that the obtained OCaml code conforms to the specified interface. Nonetheless, the use of the clone command every time we wish to refine the components of a module, together with an explicit substitution for each component in the module, makes proofs of refinement tedious routines. It would be of utmost practical interest to extend Why3 with a simpler module refinement mechanism. For instance, instead of providing a clone expression the user would write a program of the form WhyML module Spec ... end module Impl: Spec ... end where module Impl refines module Spec. The advantages of such an approach are two-fold: first, the WhyML development would result in a much more compact code; second, this would prevent situations in which the user forgets about including some symbol of Spec in the substitution given to clone, since all the symbols of the refined module would be automatically mapped to symbols of Impl, as long as these have the same name. Support for higher-order stateful programs. When programming in the OCaml language, we are very frequently exposed to the combination of higher-order functions with side effects. On the other hand, the use of higher-order in WhyML is restricted to stateless functions, which fatally limits the set of OCaml programs that we might expect to prove within Why3. We believe it is worth to strike for a better support of higher-order computations with effects within the realm of automated verification tools. A major challenge in extending such tools with higher-order traits is to keep the specification logic and generated verification conditions simple enough, as to keep using SMT solvers at the backstage. In this context, we started an exploratory study on the use of the defunctionalization technique as a first step in the proof of higher-order programs with effects [START_REF] Pereira | Défonctionnaliser pour prouver[END_REF]. The idea is to translate annotated higher-order programs into a first-order counterpart, which can then be fed to off-the-shelf verification tools. Using such a technique, we were able to verify some stateful higher-order programs in Why3. It remains, however, very difficult to apply such a technique in a modular setting, since defunctionalization is known to be a whole-program transformation, and it is not clear if we could smoothly integrate this approach within Why3 and scale up to the proof of significant size higher-order programs.

Growing strong and healthy. As mentioned throughout this thesis, we have applied our methodology to the proof of several OCaml modules, which are integrated into the VOCaL library. This shows that Why3 is a now a mature tool, and that it is possible to use our code extraction modus operandi in a larger scale programming project. To contribute to a verified OCaml library is certainly a first good sign that the proof of realistic software is possible within Why3.

However, the answer to the question "can Why3 scale up to the proof industrial-sized programming projects" remains unclear. In particular, is the approach of extracting correct-by-construction code practical enough to be included in a large software development? In the literature, we can find some successful cases of very large verified programs, that are later extracted into executable code. The most shining example in this category is CompCert [START_REF] Leroy | A formally verified compiler back-end[END_REF], the verified C compiler. Such a compiler is specified and proved correct using the Coq proof assistant. The executable OCaml sources of this development are obtained via the Coq extraction mechanism. As tempting as it might be, it remains to demonstrate if, in practice, Why3 could be used to verify and generate a correct-by-construction implementation of such a large piece of software.

A discussion that often arises is the duality of code extraction versus verification of existing code. Let us take the example of Mirage OS [START_REF] Madhavapeddy | Unikernels: Library operating systems for the cloud[END_REF], an operating system written in the OCaml language. If we decide tomorrow to engage in a project to prove the code of Mirage, what would the best approach be? Certainly, we do not want to duplicate the existing code base, just to provide a verified WhyML implementation of the OS modules and then extract an OCaml implementation that would mostly resemble to the already existing code. Additionally, WhyML lacks some important features, such as treatment of Input/Output operations, required for the proof of an OS source code. A much realistic approach would perhaps be to use a dedicated tool to the verification of existing OCaml code, while using Why3 to verify and extract correct-by-construction implementations of new modules, which would then be included inside Mirage. Exploring how extracted code can be smoothly linked with hand-written code is certainly an interesting research line. CompCert is a successful example of this approach, since some modules of the compiler are still hand-written.

Another interesting question that is worth to be investigated is the collaborative use of deductive verification with other forms of formal methods. Let us get back to the example of Mirage OS. We could imagine using Why3 to verify and extract critical parts of the system, while other less critical modules would be analyzed by more light-weighted formal methods. These could include, for instance, model checking [START_REF] Baier | Principles of Model Checking (Representation and Mind Series)[END_REF], information flow inference [START_REF] Pottier | Information flow inference for ML[END_REF], or some form of abstract interpretation [START_REF] Calcagno | Moving fast with software verification[END_REF].

Finally, there has been a recent growing trend around the integration of formal methods in continuous reasoning of industrial software code repositories. Peter O'Hearn [START_REF] O'hearn | Continuous reasoning: Scaling the impact of formal methods[END_REF] describes how formal methods are being used inside big software companies, such as Facebook and Amazon Web Services. The idea is to use verification tools to (almost) immediately inform development teams that some commit breaks the expected behavior of the system. One key to the success of applying formal methods in such industrial contexts is to decide when verification tools must be called to action. After each commit? Before a major release? To be integrated in a pipeline of such industrial size would certainly be a tour de force for Why3. One could very well imagine that, in a first moment, someone proves a property using Why3 and a combination of SMT solvers. Then, after each commit to the code repository, we would invoke the why3replay command to replay the proof in the same conditions, in order to check if the introduce updates in the code do not break the established properties. In case of failure, a report could be immediately sent to the author of the commit, whom would either repair the proof or alter the commited files. Abstract : This thesis is set in the field of formal methods, more precisely in the domain of deductive program verification. Our working context is the Why3 framework, a set of tools to implement, formally specify, and prove programs using off-the-shelf theorem provers. Why3 features a programming language, called WhyML, designed with verification in mind. An important feature of WhyML is ghost code: portions of the program that are introduced for the sole purpose of specification and verification. When it comes to get an executable implementation, ghost code is removed by an automatic process called extraction. One of the main contributions of this thesis is the formalization and implementation of Why3's extraction. The formalization consists in showing that the extracted program preserves the same operational behavior as the ori-ginal source code, based on a type and effect system. The new extraction mechanism has been successfully used to get correct-by-construction OCaml modules, which are part of a verified OCaml library of data structures and algorithms. This verification effort led to two other contributions of this thesis. The first is a systematic approach to the verification of pointer-based data structures using ghost models of fragments of the heap. A fully automatic verification of a union-find data structure was achieved using this technique. The second contribution is a modular way to reason about iteration, independently of the underlying implementation. Several cursors and higherorder iterators have been specified and verified with this approach.

Universit é Paris-Saclay

Espace Technologique / Immeuble Discovery Route de l'Orme aux Merisiers RD 128 / 91190 Saint-Aubin, France

Figure 1 . 1 :

 11 Figure 1.1: The Why3 Graphical User Interface.

WhyML

  type array63 = private { mutable ghost elts: seq int; ghost size: int; } invariant { 0 <= size = length elts <= max_int } invariant { forall i. 0 <= i < length elts -> in_bounds elts[i] }

Figure 2 . 2 :

 22 Figure 2.2: Automatically Extracted OCaml Code.

e

  ::= ā | let βx = e 1 in e 2 | ghost e | if a then e else e

Figure 3 . 1 :

 31 Figure 3.1: Masks order relation.

Figure 3 . 3 :

 33 Figure 3.3: Type Union.

WhyML

  let ghost foo (x: int) = ... or by introducing a lemma function of the form WhyML let lemma foo (x: int) = ... The pigeonhole function falls in the latter category. The application WhyML pigeonhole (Seq.length !s) t.size (get !s)

  foo (x: reg int) : reg int = try if x > 42 then raise A else 89 with A -> ghost raise B in foo[START_REF] Hardin | [END_REF]

  OCaml let isqrt (x: int) : int = ... while true do ... done; assert false with Sqrt y -> y in ... In WhyML, we would add to the body of the isqrt function an absurd expression, as follows: WhyML let isqrt (x: int) : int = ... while true do ... done; absurd with Sqrt y -> y end in ...

Figure 3 . 5 :

 35 Figure 3.5: KidML Syntax.

  ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalApp) δ[f → (x : π, e 1 )] • µ • e 2 ⇓ co µ • r δ • µ • fun f α (x : π) : σ = e 1 in e 2 ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalFun)

Figure 3 . 7 :

 37 Figure 3.7: Co-inductive Evaluation Rules (1/2).

  try e 0 with Ex ⇒ e ⇓ co µ • r = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

  Figure 3.8: Co-inductive Evaluation Rules (2/2).

  r holds, then r = div and ¬(δ• µ • e ⇓ µ • r ),for any other result r such that r = r .

Figure 3 . 9 :

 39 Figure 3.9: Progress Judgment.

Lemma 3 . 2 . 6 (

 326 Substitution). If ∆ • Γ + [x : βτ] • Σ e : (β e τ e , , γ) and ∆ • Γ • Σ ā : (β τ, ∅, ∅), where β β if e is a regular effectful expression, and (β e τ e , , γ)[x → ā] does not compromise the global assumption of admissible effects, then ∆ • Γ • Σ e[x → ā] : (β e τ e , , γ)[x → ā] for some β e . Moreover, if β β then β e β e .

Figure 3 . 11 :

 311 Figure 3.11: Typing rules for expressions (2/2).

Lemma 3 . 2 . 8 (

 328 Preservation). If ∆ • ∅ • Σ e : (βτ, , γ) and δ • µ • e ⇓ µ • r, where Σ µ and ∆ δ, then ∆ • ∅ • Σ r : (β τ, , γ ) where β β, ⊆ , γ ⊆ γ, Σ ⊆ Σ , and Σ µ . Proof. By induction on the derivation δ • µ • e ⇓ µ • r and by case analysis on the typing rule used at the bottom of the derivation. cases (EvalVBar), (EvalRaise), and (EvalAbsurd). Trivial. case (EvalRecord). Using lemma 3.2.7.

  e , γ e ) We know that ∆ • [x : βτ] • Σ e : (β e τ e , e ) is derivable. By the substitution lemma ∆ • ∅ • Σ e[x → ν] : (β e τ e , e , γ e ) where β e β e , since β i β i . By IH ∆ • ∅ • Σ r : (β e τ e , e , γ e )

Definition 4 . 1 . 4 (

 414 Extraction of top-level declarations).E β (type T α = {f : β τ}) type T α = {f : β τ β } E β (exception E : π) exception E : E(π)When it comes to extract top-level declarations, the argument mask is meaningless. We keep it in the above definition just for completeness purposes. The extraction of a record-type declaration removes from the type definition all the ghost fields. The notation f : β τ β stands for the filter of the record fields with respect to mask β. Let us consider, for instance, the following type declaration:KidML type c α = { x: reg Int; y: ghost α }The result of extracting such a type is as follows:KidML type c α = { x: reg Int }Note that we do not remove type variables from the type definition, even if these are no longer used after extraction. The unused variables types act as ghost types after extraction 1 . In the following, we always use notation β to refer to the filtering of some sequence with respect to mask β. Finally, in the extraction of exception declarations, E(π) stands for the removal of all β i τ i pairs such that β i = ghost. For instance, the exception declaration of page 44 is extracted to the following: KidML exception Return : reg Int If all arguments of an exception E are declared ghost, then E has no arguments after extraction.

KidMLexception

  Return : reg Int, ghost Bool raise Return (42, false) is the following one: KidML exception Return : reg Int raise Return 42

KidML

  fun f (x: reg Int) : (ghost Int, ∅, ∅) = x + 1 in fun g (y: ghost Int) : ((ghost Int, reg Int), ∅, ∅) = (42, 73) in e gets extracted into KidML fun g () : (reg Int, ∅, ∅) = 73 in E β (e)

Figure 4 . 1 :

 41 Figure 4.1: Extraction of Expressions.

KidML

  let ghost x = let reg _ = a.c <-42 in 0 in 73 where a is a regular expression, and c a regular field, gets extracted into KidML let reg _ = a.c <-42 in 73

KidML

  let ghost x = ghost (let reg _ = a.c <-42 in 0) in 73 would be rejected by the KidML type system. The case in which e 2 is a regular expression, e 1 is stateless expression, and the sequence x of locally bound variables does not occur in the result of extracting e 2 , is an optimization of our extraction function. Let us consider the following KidML program: KidML fun f () : ((ghost Int, reg Int), ∅, ∅) = let reg x = ... (* terminating, but very long computation *) in (x, 0) in f () We could extract such a program in the following one: KidML fun f () : (reg Int, ∅, ∅) = let reg x = ... (* terminating, but very long computation *) in 0 in f () which is a well-typed KidML program. However, we would be paying the price of executing a long computation whose value is only used within ghost code. In order to avoid such a situation, we extract the original program into KidML fun f () : (reg Int, ∅, ∅) = 0 in f ()

  KidML fun f () : ((reg Int, ghost Int), ∅, ∅) = let reg x, ghost y = 42, 0 in (x, y) in f () This is extracted into the following one: KidML let f () : (reg Int, ∅, ∅) = let reg x = 42 in x in f () Extraction of applications. The extraction rule of applications, although somewhat cryptic, is very easily explained using an example. Let us consider, for instance, the following KidML program: KidML fun f (y: ghost Int, x: reg Int) : ((reg Int, reg Int), ∅, ∅) = (x, x) in fun g (z: reg Int) : ((ghost Int, reg Int), ∅, ∅) = f (z, 42) in ... Extracting the definition of function f is not particularly challenging: KidML fun f (x: reg Int) : ((reg Int, reg Int), ∅, ∅) = (x, x) The signature of function g is also straightforwardly translated into KidML let g (z: reg Int) : (reg Int, ∅, ∅)

KidML

  let g (z: reg Int) : (reg Int, ∅, ∅) = let reg x, reg y = f (42) in y Extraction of loops.

KidML

  type t = { a: ghost Int; b: reg Int; c: ghost Int } { a = 2; b = 3; c = 5 } is translated into KidML type t = { b: reg int } { b = 3 }

  KidML exception E : ghost Int type ref α = { c: α } fun f (r: reg (ref Int)) : (ghost Int, ∅+writes r.c, ∅) = try r.c <-42; 89 with E x => x in f ({ c = 0 })

1 . 4 . 1 . 1 (

 1411 KidML exception E type ref α = { c: α } fun f (r: reg (ref Int)) : (ghost Int, ∅+writes r.c, ∅) = try r.c <-42 with E -> () in f ({ c = 0 }) Recursive calls to the extraction function respect pre-conditions. We complete our presentation of the extraction function by showing that, for every recursive call in the definition of E β (•), we respect the pre-conditions given in the top-left corner of Fig. 4.Lemma Extraction function respects pre-conditions). In the definition of Figure 4.1, for each recursive call E β (e) we have that ∆ • Γ • Σ e : (β τ, , γ) s.t. G(β , ) = reg and β β. Proof. Supposing the pre-conditions holds at entry, by case analysis on the structure of e, following the definition of the extraction function. cases (e ≡ {f = a}), (e ≡ a.f), (e ≡ ā), (e ≡ absurd), (e ≡ f(ā)), (e ≡ a r .f ← a) , and (e ≡ raise Eā). Nothing to prove. case (e ≡ fun f(x : π) : (β τ, ) = e 1 in e 2 ). The typing derivation ends up with

Figure 4 . 2 :

 42 Figure 4.2: Preservation of Convergent Evaluation.

Figure 4 . 3 :

 43 Figure 4.3: Preservation of Divergent Evaluation.

Theorem 4 . 3 . 3 (

 433 Divergent behavior preservation). If ∆ • Γ • Σ e : (βτ , , γ) and δ • µ • e ⇓ co div, such that Σ µ, and ∆ δ, then E(δ) • E(µ) • E β (e) ⇓ co div, for all β such that β β.

  case (CoEvalLoop). Semantics co-evaluation ends up with δ • µ • let reg _ = e in loop e ⇓ co div δ • µ • loop e ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

  loop e ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLetAbort) δ • µ • loop e ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLoop)

  e) in loop E(e) ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLetAbort) E(δ) • E(µ) • loop E(e) ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLoop)

  loop e ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLet) δ • µ • loop e ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLoop)

  e) in loop E(e) ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLet) E(δ) • E(µ) • loop E(e) ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = (CoEvalLoop)which completes this case.case (CoEvalLetAbort). Semantics co-evaluation ends up withδ • µ • e 1 ⇓ co div abort div δ • µ • let β x =e 1 in e 2 ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = In this case, expression e 1 diverges causing the whole let..in expression to diverge. The typing derivation ends up with

  in E β (e 2 ) ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = case (CoEvalLet). Semantics co-evaluation ends up withδ • µ • e 1 ⇓ µ • ν δ • µ • e 2 [x → ν] ⇓ co div δ • µ • let βx = e1 in e 2 ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

  δ[f → (x : π, e 1 )] • µ • e 2 ⇓ co div δ • µ • rec f α (x : π) : σ = e1 in e 2 ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

  in E β (e 2 ) ⇓ co div = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = which concludes this case.

4 . 1 .

 41 Taking a regular WhyML expression and a mask as arguments, it produces an expression in the new intermediate representation. The following is an excerpt of the extraction function implementation: OCaml let rec extract mask e = assert (not (e_ghost e)); assert (mask_sub e.e_mask mask)); match e.e_node with | Econst _ | Evar _ when mask = MaskGhost -> e_unit | Econst c -> e_const c | Evar pv -> e_var pv | Elet (LDvar (_, e1), e2) when e_ghost e1 -> extract mask e2 | Elet (LDvar (_, e1), e2) when e_ghost e2 -> extract MaskGhost e1 ...

  WhyML (internal) let conflict_assign () : unit = let f (x y: int63) : int63 = assert { y = 42 }; y in let i = ref 42 in let _ = let o = !i in let o1 = i := 0; !i in f o1 o in assert { !i = 0 } In particular, we are able to prove the two given intermediate assertions. Naively, we could have extracted this program into the following OCaml code: OCaml (extracted) let conflict_assign () : unit = let f (x: int) (y: int) : int = y in let i = ref 42 in ignore (f (i := 0; !i) !i)

  syntax predicate is_nil "%1 = []" end In the example above, we show we translate the WhyML List module into the corresponding OCaml syntax. The use of constructors Nil and Cons are replaced, respectively, by the OCaml syntactic sugar [] and _::_. The Boolean function is_nil is directly replaced by a test against the empty list. Each %i correspond to a place-holder for the symbol's arguments. Since list is a polymorphic type, %1 corresponds to the type arguments.

Figure 5 . 1 :

 51 Figure 5.1: Methodology diagram.

  OCaml type 'a elem (* type of the elements *) val make : 'a -> 'a elem (* a singleton class *) val get : 'a elem -> 'a (* access the image *) val set : 'a elem -> 'a -> unit (* update the image *) val find : 'a elem -> 'a elem (* the representative *) val eq : 'a elem -> 'a elem -> bool (* in the same class? *) val union : 'a elem -> 'a elem -> unit (* merge two classes *)

  OCaml (annotated)val make : 'a -> 'a elem (*@ e = make [uf: 'a uf] v modifies uf ensures not (mem e (old (dom uf))) ensures dom uf = old (dom uf) 'union' {e} ensures rep uf = (old (rep uf))[e <-e] ensures img uf = (old (img uf))[e <-v] *)

  val find : 'a elem -> 'a elem (*@ r = find [uf: 'a uf] e requires mem e (dom uf) modifies uf ensures dom uf = old (dom uf) ensures rep uf = old (rep uf) ensures img uf = old (img uf) ensures r = rep uf e *)

  OCaml (annotated) (*@ predicate equiv (uf: 'a uf) (x: 'a elem) (y: 'a elem) := rep uf x = rep uf y *) val union : 'a elem -> 'a elem -> unit (*@ union [uf: 'a uf] e1 e2 requires mem e1 (dom uf) requires mem e2 (dom uf) modifies uf ensures dom uf = old (dom uf) ensures exists r. (r = old (rep uf e1) || r = old (rep uf e2)) && forall x. mem x uf.dom -> rep uf x = (if old (equiv uf x e1 || equiv uf x e2) then r else old (rep uf x)) && img uf x = if old (equiv uf x e1 || equiv uf x e2) then img uf r else old (img uf x) *)

  OCaml type 'a content = Link of 'a elem | Root of int * 'a and 'a elem = 'a content ref Each element is either a representative element (Root), containing a value of type int, which we refer to as the rank of the class, and a value of type 'a, or a pointer (Link) to another element in the same equivalence class.

WhyML

  type mem_ref 'a = private { ghost mutable refs : loc_ref 'a -> option 'a; } where loc_ref is an abstract type to represent locations of OCaml's heap-allocated references of type ref.

WhyML

  val ghost empty_memory () : mem_ref 'a ensures { forall r. not (mem r result.dom) }

WhyML

  val get_ref (ghost memory: mem_ref 'a) (l: loc_ref 'a) : 'a requires { mem l memory.dom } ensures { Some result = memory.refs[l] } val set_ref (ghost memory: mem_ref 'a) (l: loc_ref 'a) (c: 'a) : unit requires { mem l memory.dom } writes { memory } ensures { memory.refs = (old memory.refs)[l <-Some c] }

  WhyML val (==) (x y: loc_ref 'a) : bool ensures { result <-> x=y } Different flavors of machine arithmetic. Having defined a particular memory model to our union-find implementation, we can now give a WhyML definition for types content and elem. This is as follows: WhyML type rank = int63 type content 'a = Link (elem 'a) | Root rank 'a type elem 'a = loc_ref (content 'a)

  operation over such equivalence classes, results in the following configuration of the union-find universe:

  WhyML module Peano type t = abstract { v: int } val constant zero : t ensures { result.v = 0 } val succ (x: t) : t ensures { result.v = x.v + 1 } val lt (x y: t) : bool ensures { result <-> x.v < y.v } val eq (x y: t) : bool ensures { result <-> x.v = y.v } end

WhyML

  type rank = Peano.t and change the implementation of union accordingly: WhyML let union (ghost memo: mem_ref (content 'a)) (x y: elem 'a) = let x, y = find memo x, find memo y in if x == y then x else match get_ref memo x, get_ref memo y with | Root rx vx, Root ry vy -> if rx = ry then begin set_ref memo y (Link x); set_ref memo x (Root (Peano.succ rx) vx) end

WhyML

  predicate allocated (memory: mem_ref (content 'a)) (x: elem 'a) = mem x memory.dom Next, we say that representative values are only allocated as Root elements: WhyML invariant { forall x. mem x dom -> match memo.refs (rep x) with | Some (Root _ _) -> true | _ -> false end } Finally, every allocated element is part of a union-find chain: WhyML invariant { forall x. match memo.refs x with | Some (Link y) -> x <> y /\ allocated memo y /\ rep x = rep y | Some (Root _ v) -> img x = v /\ rep x = x | None -> true end }

WhyML

  let make (ghost uf: uf 'a) (v: 'a) : elem 'a ensures { not (mem result (old(dom uf))) } ensures { dom uf = add result (old (dom uf)) } ensures { rep uf = (old (rep uf))[result <-result] } ensures { img uf = (old (img uf))[result <-v] } = let x = alloc_ref uf.memo (Root Peano.zero v) in uf.dom <-add x uf.dom; uf.rep <-uf.rep[x <-x]; uf.img <-uf.img[x <-v]; xWe keep the same specification as given in the interface. The implementation of make is straightforward. More interestingly, we implement and specify function find as follows: WhyML let rec find (ghost uf: uf 'a) (x: elem 'a) : elem 'a requires { mem x uf.dom } ensures { result = uf.rep x } = match get_ref uf.memo x with | Root _ _ -> x | Link y -> let rx = find uf y in set_ref uf.memo x (Link rx); rx end

  WhyML inductive path (memo: memo 'a) (x y: elem 'a) (n: int) = | Path_nil : forall mem: memo 'a, x: elem 'a, v: 'a, r, n. 0 <= n -> memo.refs x = Some (Root r v) -> path memo x x n | Path_cons: forall mem: memo 'a, x y z: elem 'a, n. 0 <= n -> mem.refs x = Some (Link y) -> path mem y z n -> path mem x z (n+1)

Figure 5 . 2 :

 52 Figure 5.2: Union-find Extracted Code.

  val resize: 'a t -> int -> unit (*@ resize a n checks 0 <= n <= Sys.max_array_length modifies a ensures length a.view = n ensures forall i. 0 <= i < min (length (old a.view)) n -> a.view[i] = (old a.view)[i] *)

WhyML

  let resize (a: t 'a) (n: int63) : unit ensures { n = a.size } ensures { forall i. 0 <= i < min ((old a).size) n -> a.view[i] = (old a).view[i] } raises { Invalid_argument -> not (0 <= n <= max_array_length) } = if not (0 <= n <= max_array_length) then raise Invalid_argument; unsafe_resize a n If we pass the test of the checks clause, then we are sure the pre-condition of unsafe_resize holds and so we can safely call it. As for the implementation of resize, this keeps the pre-condition as a requires clause: WhyML let unsafe_resize (a: t 'a) (n: int63) : unit requires { 0 <= n <= max_array_length } ensures { n = a.size } ensures { forall i. 0 <= i < min ((old a).size) n -> a.view[i] = (old a).view[i] } = let n_old = length a.data in if n <= a.size then ... (* shrink *) ... else ... (* grow *) ...; a.size <-n All the verification conditions generated for resize and unsafe_resize are automatically discharged. Providing a defensive and an unsafe version of the same function is already a usual practice in existing OCaml libraries, e.g., the standard library functions Array.get and Array.unsafe_get. When it comes to implement push and pop, these are verified functions and so we can call the unsafe version of resize. The implementation of push is as follows: WhyML let push (a: t 'a) (x: 'a) : unit requires { a.size < max_array_length } ensures { a.size = (old a).size + 1 } ensures { a.view[a.size -1] = x } ensures { forall i. 0 <= i < (old a).size -> a.view[i] = (old a).view[i] } = let n = a.size in unsafe_resize a (n + 1);

OCaml

  let get_next = function Nil -> assert false | Cons { next } -> next let split l = ... let merge cmp l1 l2 = ... let rec mergesort cmp l = if l1 == Nil || get_next l == Nil then l else let l2 = split l in let l1 = mergesort cmp l in let l2 = mergesort cmp l2 in merge cmp l1 l2

WhyML

  predicate is_list (mem: mem 'a) (from : cell 'a) (s: view 'a) (to: cell 'a) = let n = length s in n = 0 /\ from = to \/ n > 0 /\ from = Cons s[0] /\ mem.next s[n-1] = Some to /\ (forall i. 0 <= i < n -> Cons s[i] <> to) /\ distinct s /\ forall i. 0 <= i < n -1 -> mem.next s[i] = Some (Cons s[i+1]) predicate frame_mem (m1 m2: mem 'a) (s: view 'a) = forall i. 0 <= i < length s -> m1.next s[i] = m2.next s[i] /\ m1.contents s[i] = m2.contents s[i] predicate frame (m1 m2: mem 'a) (s: view 'a) = forall ss: view 'a. disjoint s ss -> frame_mem m1 m2 ss let split (ghost mem: mem 'a) (l1: cell 'a) (ghost s: view 'a) : (s1: ghost view 'a, l2: cell 'a, s2: ghost view 'a) requires { is_list mem l1 s } requires { length s >= 2 } ensures { is_list mem l1 s1 /\ is_list mem l2 s2 } ensures { s = s1 ++ s2 } ensures { frame (old mem) mem s } = ... let merge (ghost mem: mem 'a) (cmp: 'a -> 'a -> int63) (l1 l2: cell 'a) (ghost s1 s2: view 'a) : (l: cell 'a, s: ghost view 'a) requires { is_pre_order cmp } requires { is_list mem l1 s1 /\ sorted mem cmp s1 } requires { is_list mem l2 s2 /\ sorted mem cmp s2 } requires { disjoint s1 s2 } ensures { is_list mem l s } ensures { sorted mem cmp s /\ permut_all (s1 ++ s2) s } ensures { frame (old mem) mem s } = ... let rec mergesort (ghost mem: mem 'a) (cmp: 'a -> 'a -> int63) (l: cell 'a) (ghost s: view 'a) : (r: cell 'a, s': ghost view 'a) requires { is_pre_order cmp } requires { is_list mem l s } variant { length s } ensures { is_list mem r s'} ensures { permut_all s s'/\ sorted mem cmp s' } ensures { frame (old mem) mem s } = if l == Nil || get_next mem l == Nil then l, s else let s1, l2, s2 = split mem l s in let l1, s1 = mergesort mem cmp l s1 in let l2, s2 = mergesort mem cmp l2 s2 in merge mem cmp l1 l2 s1 s2

Figure 5 . 3 :

 53 Figure 5.3: WhyML Implementation of Function mergesort.

Fig. 5 .

 5 4 presents the complete specification of a priority queue interface file, which we name PairingHeap.mli. When we feed our plugin with the PairingHeap.mli file, it generates the following WhyML module: WhyML module Sig scope Make scope X type t function cmp t t : int63 axiom is_pre_order : is_pre_order (fun (y0:t) (y1:t) -> cmp y0 y1) val compare (x:t) (y:t) : int63 returns { r -> r = cmp x y } end type elt = t type heap = private { ghost bag : bag elt } val empty (us:()) : heap returns { h -> card (bag h) = 0 } returns { h -> forall x:t. nb_occ x (bag h) = 0 } ... end OCaml (annotated) module Make type t (*@ function cmp: t -> t -> int *) (*@ axiom is_pre_order: Order.is_pre_order cmp *) val compare : t -> t -> int (*@ r = compare x y ensures r = cmp x y *) end) : sig type elt = X.t type heap (*@ model bag : elt bag *) val empty : unit -> heap (*@ h = empty () ensures card h.bag = 0 ensures forall x. nb_occ x h.bag = 0 *) val is_empty : heap -> bool (*@ b = is_empty h ensures b <-> h.bag = empty_bag *) val merge : heap -> heap -> heap (*@ h = merge h1 h2 ensures card h.bag = card h1.bag + card h2.bag ensures forall x. nb_occ x h.bag = nb_occ x h1.bag + nb_occ x h2.bag *) val insert : elt -> heap -> heap (*@ h' = insert x h ensures nb_occ x h'.bag = nb_occ x h.bag + 1 ensures forall y. y <> x -> nb_occ y h'.bag = nb_occ y h.bag ensures card h'.bag = card h.bag + 1 *) (*@ predicate mem (x: elt) (h: heap) := nb_occ x h.bag > 0 *) (*@ predicate is_minimum (x: elt) (h: heap) := mem x h /\ forall e. mem e h -> X.cmp x e <= 0 *) (*@ function minimum: heap -> elt *) (*@ axiom min_def: forall h. 0 < card h.bag -> is_minimum (minimum h) h *) val find_min : heap -> elt (*@ x = find_min h requires card h.bag > 0 ensures x = minimum h *) val delete_min : heap -> heap (*@ h' = delete_min h requires card h.bag > 0 ensures let x = minimum h in nb_occ x h'.bag = nb_occ x h.bag -1 ensures forall y. y <> minimum h -> nb_occ y h'.bag = nb_occ y h.bag ensures card h'.bag = card h.bag -1 *) end

Figure 5 . 4 :

 54 Figure 5.4: Pairing Heaps Specification.

  function compare elt elt : int63 axiom is_pre_order: is_pre_order compare end type tree = E | T X.t (list tree) type heap = { data : tree; ghost bag: bag X.t } let merge (h1 h2: heap) : heap = ... let delete_min (h: heap) : heap end end

WhyML

  val has_next (c: cursor) : bool ensures { result <-> not (complete c.collection c.visited) } Function has_next decides whether predicate complete holds, as expressed in its postcondition. The second operation, next, is introduced as follows: WhyML val next (c: cursor) : elt requires { not (complete c.collection c.visited) } writes { c.visited } ensures { c.visited = snoc (old c).visited result }

WhyML

  val next (c: cursor) : elt requires { not (complete c.collection c.visited) } writes { c.visited } ensures { c.visited = snoc (old c).visited result } val has_next (c: cursor) : bool ensures { result <-> not (complete c.collection c.visited) } val create (a: array elt) : cursor ensures { result.visited = empty } ensures { result.collection = a }

  type t = array elt predicate permitted (collection: t) (visited: seq elt) (index: int) = index = length visited /\ length visited <= length collection /\ forall i. 0 <= i < index -> visited[i] = collection[i] type cursor = { ghost mutable visited: seq elt; mutable index: int; collection: t; } invariant { permitted collection visited index }

WhyML

  predicate complete (c: cursor) = length c.visited = length c.collectionFor convenience, we define complete over the cursor type. We now focus on the implementation of the cursor operations. Function has_next decides if enumeration is complete by comparing the value of index with the length of collection: WhyML let has_next (c: cursor) : bool ensures { result <-> not (complete c) } = c.index <> length c.collection Function next takes the element x on the index position, increments the value of index and returns x: WhyML let next (c: cursor) : elt requires { not (complete c) } ensures { c.visited = snoc (old c).visited result } = let x = c.collection[c.index] in ghost c.visited <-snoc c.visited x; c.index <-c.index + 1; x In the body of function next, we update the visited sequence, via a ghost writing. Giving the regular status of fields index and collection, function next is type-checked by the Why3 type system as a regular function, even tough there is an assignment to a ghost field. Finally, in function create we initialize the cursor as follows: WhyML let create (a: array elt) : cursor ensures { result.visited = empty } ensures { result.collection = a } = { visited = empty; collection = a; index = 0 }

WhyML

  clone CursorArraySpec with type elt, type cursor, val has_next, val next, val create end

WhyMLclone

  CursorArraySpec with type elt = intThis imposes an abstraction barrier between implementation and client code: the client code uses only the specification of the array cursor, instead of the actual implementation given by the CursorArrayImpl module.We specify sum_array with the following contract: WhyML let sum_array (a: array int) : int ensures { result = sum (fun i -> a[i]) 0 (length a) } Logical function sum, of type (int → int) → int → int → int, is defined in the Why3 standard library, and stands for the summation of integers values in a given range, i.e., the result of sum f a b is equal to a≤i<b f(i). The code begins by creating a reference s to hold the sum, and the cursor c. For each new element returned by the cursor, we update the value of s, as follows: WhyML = let s = ref 0 in let c = create a in while has_next c do variant { length c.collection -length c.visited } invariant { !s = sum (get a) 0 (length c.visited) } let x = next c in s := !s + x done; !s

WhyML type vertex val function succ

  vertex : set vertex Note that we declare succ as both a logical and programming function, as we shall use it in the implementation and to reason about the cursor's behavior.The notion of path plays an important role in the specification of such a cursor. We define it as follows: WhyML predicate edge (v1 v2: vertex) = mem v2 (succ v1) predicate path (v1: vertex) (s: seq vertex) (v2: vertex) = if length s = 0 then v1 = v2 else edge v1 s[0] /\ s[length s -1] = v2 /\ forall i. 0 <= i < length s-1 -> edge s[i] s[i+1]

  WhyML type elt val eq (x1 x2: elt) : bool ensures { result <-> x1 = x2 } type t = unit clone Graph with type vertex = elt

WhyML(

  mem source stack -> stack = Cons source Nil) /\ that the elements in the stack are always disjoint from the already visited elements, WhyML inter (elements stack) (to_set visited) = empty /\ that the set marked elements is the union of the already visited elements and those of the stack, WhyML marked = union (elements stack) (to_set visited) /\ and finally that all marked nodes are reachable from the source, WhyML forall v. mem v marked -> reachable source v

  eq (x1 x2: elt) : bool ensures { result <-> x1 = x2 } type t = tree elt

WhyML

  type cursor = private { ghost mutable visited: seq elt; ghost collection: t; } invariant { permitted collection visited } by { visited = empty; collection = Empty } We use predicate permitted as the type invariant of cursor and the invariant witness is simply the record where fields visited and collection are assigned, respectively, to the empty sequence ensures { result.visited = empty } = { visited = empty; zipper = zipper_build t Done; collection = t } All the generated verification conditions for function iterator are automatically discharged. This includes a proof that the new value of type cursor respects the type invariant, which is possible thanks to the zipper_spec lemma. For the next function, each time we return a new element we need to rebuild the zipper from the left-hand sub-tree of the returned element, together with the remaining of the zipper: WhyML let next (c: cursor) : elt requires { not (complete c) } ensures { c.visited == snoc (old c).visited result } = match c.zipper with | Done -> absurd | Next x r e -> c.visited <-snoc c.visited x; c.zipper <-zipper_build r e; x end

WhyML

  let has_next (c: cursor) : bool ensures { result <-> not (complete c) } = match c.zipper with | Done -> false | _ -> true end

WhyML

  let same_fringe (t1 t2: tree CI.elt) : bool ensures { result <-> elements t1 = elements t2 } = eq_cursors (CI.iterator t1) (CI.iterator t2) end All the generated verification conditions for functions eq_cursors and same_fringe are easily discharged by SMT solvers.

  type CI.elt = CImpl.elt, type CI.cursor = CImpl.cursor, val CI.iterator = CImpl.iterator, val CI.has_next = CImpl.has_next, val CI.next = CImpl.next, val CI.eq = CImpl.eq end This use of WhyML clone clause feels much like OCaml functors application. Indeed, we can view module InorderCursorSpec as the argument (named CI) of the SameFringe functor, i.e., OCaml module SameFringe (CI: sig type elt type cursor val iterator ... end) = struct let eq_cursors (c1 c2: CI.cursor) : bool = ... let same_fringe (t1 t2: tree CI.elt) : bool = eq_cursors (CI.iterator t1) (CI.iterator t2) end and the clone SameFringe with line would correspond to OCaml module Linking = SameFringe (CImpl)

Figure 6 . 1 :

 61 Figure 6.1: In-Order Cursor and Same-fringe Extracted Code.

OCaml

  let rec fold_left f acc l = match l with | [] -> acc | e :: r -> fold_left f (f acc e) r

  constant l0 : list elt Next, we declare inv as the following undefined predicate: WhyML predicate inv (seq elt) acc

  let f (ghost v: seq elt) (accu: acc) (x: elt) : acc = accu + 1There is no need to specify function f: Why3 can automatically infer a specification that corresponds exactly to the body of the function. Next, we give the following definition to the inv predicate:WhyML predicate inv (acc: acc) (v: seq elt) = acc = length v

  WhyML module TreeLength use seq.Seq, bintree.Tree, int.Int, Elements type elt type acc = int predicate inv (ghost v: seq elt) (acc: acc) = acc = length v let f (ghost v: seq elt) (acc: acc) (x: elt) = acc + 1 clone FoldTree with type elt, type acc, predicate inv, val f let tree_length () : (ghost v: seq elt, accu: acc) ensures { accu = length t0 } = fold empty 0 t0 end

WhyML

  let rec fold_right (f: acc -> elt -> acc) (l: list elt) (a: acc) : acc = match l with | Nil -> acc | Cons x r -> f (fold_right f r acc) x end It works by calling itself recursively over the tail of a list, and only then by applying the consumer function to the head of the list and the value computed recursively. In order to formally specify fold_right, we need to give appropriate definitions to permitted and complete properties. We first introduce the following logical function to convert a list into a sequence: WhyML function elements_left (l: list 'a) : seq 'a = match l with | Nil -> empty | Cons x r -> cons x (elements_left r) end We then derive a function elements to reverse the sequence computed by elements_left, as follows: WhyML function elements (l: list 'a) : seq 'a = reverse (elements_left l)

  c + sum 0 (length v) (multf (mult_power v x) x) = (* lemma sum_mult_constant from Why3 standard library *) c + (sum 0 (length v) (mult_power v x)) * x = (* definition of eval_sequence *) c + eval_sequence v x * x }where multf is the following logical function:WhyML function multf (f: int -> int) (x: int) : int -> int = fun i -> f i * xAll the generated verification conditions for eval_sequence_cons are automatically discharged. This completes the proof of refinement of the FoldRight module.We complete the presentation of this case study with the proof of functional correctness of the val_of_pol function. After having refined the FoldRight module, we devise the following WhyML implementation: WhyML let val_of_pol () : (vres: seq elt, accu: acc) ensures { accu = eval l0 x } = FR.fold_right empty 0 l0

WhyML

  let s = ref 0 in for x in a with c do variant { length c.collection -length c.visited } invariant { !s = sum (fun i -> a[i]) 0 (length c.visited) } s += x done; !s

WhyML

  type cursor = private { ghost visited : seq elt; ghost collection : t; } invariant { permitted collection visited } and would change the return type and specification of the next function, accordingly: WhyML val next (c: cursor) : option (elt, cursor) ensures { match result with | None -> complete c.collection c.visited | Some (e, cn) -> cn.visited = snoc c.visited e /\ cn.collection = c.collection end } We no longer need to use a has_next function, since we know that the iteration is complete as soon as next returns None. This would suit particularly well client code that is implemented as a recursive function, e.g., WhyML let rec traversal (c: cursor) (acc: int) = match next c with | None -> acc | Some (e, cn) -> traversal cn (acc + e) end

WhyML

  let val_of_pol (p: list int) (x: int) : int ensures { result = eval p x } = fold_right (fun (ghost v) acc c -> invariant { acc = eval_sequence (reverse v) x } c + acc * x) p 0

WhyML

  let rec fold_right (ghost inv: seq elt -> acc -> bool) (f: acc -> elt -> acc) (l: list elt) (acc: acc) : acc foldspec { (permitted, complete) } ... which is then translated into the WhyML program of Fig. 6.2.

IndexSymbols∆ 137 B 7 C 6 D 8 FF 131 G 138 L 22 M

 13776813113822 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 ⇓ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28, 33, 38, 42 ⇓ co . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Γ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 Σ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 β . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 ∪ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31, 37 δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 div . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35, 44 γ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .30 E . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30, 37, 46 µ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 ν . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28, 32 π . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 σ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 τ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28, 35, 39 r . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 abort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 adm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37, 44, 46 err . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 A A-normal form . . . . . . . . . . . . . . . . . . . . . . . 25, 30 abstract interpretation . . . . . . . . . . . . . . . . . . . . 3 abstract domain . . . . . . . . . . . . . . . . . . . . . . . . . . 3 absurd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Agda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 algebraic data types . . . . . . . . . . . . . . . . . . . . . 67 aliasing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Alt-Ergo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 arithmetic overflow . . . . . . . . . . . . . . . . . . . . . . 21 Astrée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 atomic expression . . . . . . . . . . . . . . . . . . . . . . . . 28 AutoProof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 bag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133, 140 Beckert, Bernhard . . . . . . . . . . . . . . . . . . . . . . 137 binary search . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 Boogie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 bounded model checking . . . . . . . . . . . . . . . . . . 3 Breitner, Joachim . . . . . . . . . . . . . . . . . . . . . . 137 bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1, CakeML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Cauderlier, Raphael . . . . . . . . . . . . . . . . . . . . 137 CFML . . . . . . . . . . . . . . . . . . . . . 9, 107, 108, 178 Charguéraud, Arthur . . . . . . . . 43, 53, 57, 109 checks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 Church, Alonzo . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 client invariant . . . . . . . . . . . . . . . . . . . . . . . . . 163 Clochard, Martin . . . . . . . . . . . . . . . . . . 109, 114 code extraction . . . . . . . . . . . . . . . . . . . . . . . . . . 11 coercion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 coevaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 coinductive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 CompCert . . . . . . . . . . . . . . . . . . 6, 104, 108, 180 completeness . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 components ghost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 constant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 consumer function . . . . . . . . . . . . . . . . . . . . . . 162 Coq . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4, 107, 108 correct-by-construction . . . . . . . . . . . 6, 11, 108 Crespo, Juan Manuel . . . . . . . . . . . . . . . . . . . 138 cursor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 CVC4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dafny . . . . . . . . . . . . . . . . . . 6, 66, 108, 173, 178 Damas, Luis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 de Boer, Frank . . . . . . . . . . . . . . . . . . . . . . . . . 137 de Gouw, Stijn . . . . . . . . . . . . . . . . . . . . . . . . . 137 INDEX deductive program verification . . . . . . . . . . 3, 4 Dedukti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 defunctorization . . . . . . . . . . . . . . . . . . . . . . . . 160 depth-first search . . . . . . . . . . . . . . . . . . . . . . . 148 derivation tree . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Dijkstra, Edsger . . . . . . . . . . . . . . . . . . . . . . . . . . 2 diverges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 Dubois, Catherine . . . . . . . . . . . . . . . . . . . . . . 173 E EasyCrypt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 effects admissible . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 instantiation . . . . . . . . . . . . . . . . . . . . . . . . . 40 writing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 EiffelBase2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 expression effects . . . . . . . . . . . . . . . . . . . . . . . . 35 extract . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25, 103 extraction code extraction . . . . . . . . . . . . . . . . . . . . . . 13 driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 mechanism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 Felleisen, Matthias . . . . . . . . . . . . . . . . . . . . . . . 58 Filliâtre, Jean-Christophe . . 51, 66, 104, 109, 114 Floyd, Robert . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 FoCaLiZe . . . . . . . . . . . . . . . . . . . . . . . . . . 104, 173 formal specification . . . . . . . . . . . . . . . . . . . . . . . 2 forward simulation . . . . . . . . . . . . . . . . . . . 87, 97 Frama-C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 François Pottier . . . . . . . . . . . . . . . 167, 173, 178 function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 non-defined . . . . . . . . . . . . . . . . . . . . . . . . . . 26 functor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . garbage collector . . . . . . . . . . . . . . . . . . . . . . . . .15 generic error rule . . . . . . . . . . . . . . . . . . . . . . . . 57 ghost code . . . . . . . . . . . . . . . . . . . . . . . . . 13, 28, 29 contamination . . . . . . . . . . . . . . . . . . . . . . . 19 field . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 interference . . . . . . . . . . . . . . . . . . . . . . 17, 36 status . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 ghostification . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Gillaume Melquiond . . . . . . . . . . . . . . . . . . . . 177 Gilles Barthe . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Giorgetti, Alain . . . . . . . . . . . . . . . . . . . . . . . . 173 GNATprove . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 Gondelman, Léon . . . . . . . . . . . . . . . 51, 66, 104 Grall, Hervé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148H Hindley, J. Roger . . . . . . . . . . . . . . . . . . . . . . . . . 4 Hoare Logic . . . . . . . . . . . . . . . . . . . . . . . . . . 5, 120 consequence rule . . . . . . . . . . . . . . . . . . . . . . 5 consequence rule . . . . . . . . . . . . . . . . . . . . . . . .120 Hoare triple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5 Hoare, Tony . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Horner method . . . . . . . . . . . . . . . . . . . . . . . . . 169 I injectivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Isabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 iteration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 iterator invariant . . . . . . . . . . . . . . . . . . . . . . . 163 K KeY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 KidML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Kunz, César . . . . . . . . . . . . . . . . . . . . . . . . . . . . Löf, Martin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 lemma function . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Leroy, Xavier . . . . . . . . . . . . . . . . . . . . . . . . 43, 53 linking invariant . . . . . . . . . . . . . . . . . . . . . . . . 156 Liquid Types . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 local invariants . . . . . . . . . . . . . . . . . . . . . . . . . 120 loop invariant . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 variant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . mask . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 union . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 memory location . . . . . . . . . . . . . . . . . . . . . . . . . 32 Milner, Robin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Mirage OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .180 model checking . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 multiple assignment . . . . . . . . . . . . . . . . . . . . 119 mutability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 mutable field . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 mutable record fields . . . . . . . . . . . . . . . . . . . . .34 filles de priorité, à savoir des pairing heaps et une variante impérative, où la fille est codée dans un tableau redimensionnable. Finalement, la dernière contribution est un moyen de spécifier un algorithme d'itération indépendamment de son implémentation. Plusieurs curseurs et itérateurs d'ordre supérieur ont été spécifiés et vérifiés en utilisant cette approche. Le point en commun, et les important, sur ces contributions est le fait qu'aucun des ces trois aspects idiomatiques de la programmation en OCaml est supporté directement par le langage WhyML et la preuve de programmes avec Why3. Titre : Outils et techniques pour la v érification de programmes imp ératives modulaires Mots cl és : V érification d éductive, Why3, Effets, Biblioth èque OCaml, Modulaire R ésum é : Cette th èse se place dans le cadre des m éthodes formelles et plus pr écis ément dans celui de la v érification d éductive et du syst ème Why3. Ce dernier fournit un ensemble d'outils pour la sp écification, l'impl émentation et la v érification à l'aide de d émonstrateurs externes. Why3 propose en particulier un langage de programmation adapt é à la preuve, appel é WhyML. Un aspect important de ce langage est le code fant ôme, à savoir des él éments de programme introduits exclusivement pour les besoins de la sp écification et de la preuve. Pour obtenir un code ex écutable, le code fant ôme est élimin é par un processus automatique appel é extraction. L'une des contributions principales de cette th èse est la formalisation et l'impl émentation du m écanisme d'extraction de Why3. La formalisation consiste à montrer que le programme extrait pr éserve la s émantique du programme de d épart, en s'appuyant notamment sur un syst ème de types avec effets. Ce m écanisme d'extraction a ét é utilis é avec succ ès pour obtenir plusieurs modules OCaml corrects par construction, dans le cadre d'une biblioth èque v érifi ée de structures de donn ées et d'algorithmes. Cet effort de preuve a conduit à deux autres contributions de cette th èse. La premi ère est une technique syst ématique pour la v érification de structures avec pointeurs, à l'aide de mod èles du tas d élimit és. Une preuve enti èrement automatique d'une structure union-find a pu être obtenue gr âce à cette technique. La seconde contribution est un moyen de sp écifier un algorithme d'it ération ind épendamment de son impl émentation. Plusieurs curseurs et it érateurs d'ordre sup érieur ont ét é sp écifi és et v érifi és en utilisant cette approche. Title : Tools and Techniques for the Verification of Modular Stateful Code Keywords : Deductive verification, Why3, Effects, OCaml library, Modular

  Our definition of a record is standard: a record represents a collection of values stored together as one, where each component is identified by a different field name. Records are a useful mechanism to introduce new data types, which we refer to as composite data types, since they aggregate values of different types. The concept of record is fairly common in computer programming, and we can find it outside the realm of functional languages. For instance, in the C language we use structures to group items of different types under a single type name; in object-oriented languages, objects can be used for the same purpose.

  Notation µ[l → r] stands for a new store obtained by replacing the binding of l in µ to the record r, leaving other bindings unchanged. It creates a new biding if l is not in the domain of µ. We update operational semantics rules given in Sec. 3.1.1 to cope with the changes made on the evaluation relation: ν (EvalIfFalse)

  Judgment properties. Drawing inspiration from the works of Arthur Charguéraud[START_REF] Charguéraud | Pretty-big-step semantics[END_REF] Sec.2.4]

and Xavier Leroy

[98, lemmas 5-8]

, let us prove some properties about our two judgments. First, we want to limit the use of our judgments to derivations of the form δ•µ•e ⇓ µ •r and δ•µ•e ⇓ co div. We can only derive δ • µ • e ⇓ µ • div if e contains a sub-expression that diverges, as stated via the following lemma:

  and Σ µ . By auxiliary lemma 3.2.7 we have that ∆ • ∅ • Σ e 2 : (β 2 τ 2 , 2 , γ 2 ). By the substitution lemma we have

  To allocate a new reference, we use the following function:

	val alloc_ref (ghost memory: mem_ref 'a) (v: 'a) : loc_ref 'a	WhyML
	writes { memory }
	ensures { not (mem result (old memory).dom) }
	ensures { memory.refs = Map.set (old memory.refs) result (Some v) }

Let us note that, contrarily to empty_memory, function alloc_ref is not declared as a ghost function. This is due to the fact that empty_memory is used exclusively in ghost code, while we need to use the alloc_ref function within regular code of our union-find implementation. At extraction time, an expression of the form alloc_ref m v is replaced by ref v, the OCaml standard library function to create new references. Sec. 5.2.4 presents the details on how to the translate our memory model and type of locations to executable OCaml code.

  Next, we declare predicates permitted and complete, as follows: These are undefined predicates, which we must instantiate for each new kind of cursor. A cursor is modeled by the following record type:

	type t		WhyML
	type elt
	predicate permitted t (seq elt)	WhyML
	predicate complete t (seq elt)
	type cursor = private {	WhyML
	ghost mutable	visited: seq elt;
	ghost	collection: t;
	} invariant { permitted collection visited }

  Given a collection t, function create returns a new cursor with an empty visited sequence, as well as with field collection assigned to t. This completes the definition of module Cursor.

	val create (t: t) : cursor	WhyML
	ensures { result.visited = empty }
	ensures { result.collection = t }
	end

  We define the cursor type as follows:We keep permitted as the type invariant of cursor. We can provide a concrete definition for predicates permitted and complete, as follows 2 :

	forall i. 0 <= i < length v -> t[i] = v[i]
	predicate complete (t: t) (v: seq elt) =
	length v = length t
	module CursorArraySpec	WhyML
	type elt
	type t = seq elt
	type cursor = private {	WhyML
	ghost mutable	visited: seq elt;
	collection: t;
	} invariant { permitted collection visited }
	predicate permitted (t: t) (v: seq elt) =	WhyML
	length v <= length t /\

1 :

  where field n contains the next element in the enumeration. The field collection has no meaning here; we keep it just for coherence with our presentation. The function next is, thus, easily implemented. It is enough to increment the value of n:

	let next (c: cursor) : elt	WhyML
	...
	= let x = c.n in
	c.n <-c.n + 1;
	ghost c.visited <-snoc c.visited x;
	x
	type elt = int	WhyML
	type	t = unit
	type cursor = {
	ghost mutable visited	: seq elt;
	ghost	collection : t;
		mutable n	: int;
	} invariant { permitted visited }

invariant { forall i. 0 <= i < length c.visited -> n > visited[i] }

  All the generated verification conditions are automatically discharged, which include the refinement of type cursor, i.e., the type invariant of InorderCursorImpl.cursor implies the type invariant of InorderCursorSpec.cursor, as well the proof of specification inclusion for operations next, has_next, and createa.

WhyML clone InorderCursorSpec with type elt, type cursor, val next, val has_next, val create end

  The following eq_cursors function implements such a comparison:

	module SameFringe	WhyML
	clone InorderCursorSpec as CI
	predicate permitted (c: CI.cursor) =
	CI.permitted c.CI.collection c.CI.visited
	let rec eq_cursors (c1 c2: CI.cursor) : bool
	requires { c1.CI.visited == c2.CI.visited }
	ensures { result <-> c1.CI.collection == c2.CI.collection }
	variant { length c1.CI.collection -length c1.CI.visited }
	= match CI.has_next c1, CI.has_next c2 with
	| False, False -> True
	| True, True	-> CI.eq (CI.next c1) (CI.next c2) && eq_cursors c1 c2
	| _	-> False
	end

Table 6 .

 6 1: Experimental Results.

  1 + x(. . . + xc n ) . . .), which is easily implemented as a fold_right client program:

	iterator list fold_right list fold_left array fold_left set fold tree inorder_fold 12 loc los time (sec) 11 38 0.10 9 32 0.17 23 45 0.02 9 26 1.67 30 0.56 interval fold 14 39 0.27 total 78 210	program list length set of list value of polynomial tree size set cardinal set copy sigma total	loc los time (sec) 12 4 0.01 29 17 0.18 15 38 0.49 4 12 0.02 4 12 0.02 4 12 0.02 4 9 1.11 109 147
	(a) Fold Implementation	(b) Fold Clients
	let val_of_pol (p: list int) (x: int) : int	OCaml
	= fold_right (fun acc c -> c + acc * x) 0 p

Table 6 .

 6 2: Experimental Results (fold iterators).

Bugs in specification are also possible, but these are more trickier to notice and repair. For instance, if we

It is a bit of a shame that after extraction we get a declaration of a record type with a single field. The Why3 extraction mechanism, described in Chap.[START_REF] Bacelar Almeida | Rigorous Software Development: An Introduction to Program Verification[END_REF], is able to optimize such singleton types.

The Princess Bride, 1987.

The ArrayInt63 module is part of the Why3 standard library, contained in the file array.mlw of sub-directory mach.

Our module system, mixing defined and undefined symbols, is actually close to that of Mixins[START_REF] Ancona | An algebraic approach to mixins and modularity[END_REF]

Why3 follows the OCaml convention, where the parentheses around the name of a function introduce an infix operation.

The numeral 73 is Sheldon Copper's, a fictional character from the The Big Bang Theory show, favorite number. It is introduced in the beginning of season 4, episode 10 (the 73th episode of the entire show), which we invite the reader to watch for a very entertaining moment around number theory.

let..in to respect the use of A-normal formal. However, such program transformation can very easily be mechanized, and one could imagine it to be part of a pre-processor that converts expressions to the equivalent A-normal form ones.In order to type an if..then..else expression, we distinguish two different cases, according to the ghost status of the test expression. We give thus two different typing rules, as follows:Γ a : ghost Bool Γ e 1 : π 1 Γ e 2 : π 2 Γ if a then e 1 else e 2 : (π 1 ∪ π 2 ) (TIfGhost)

(EvalGet)We take a location l, retrieve its binding on µ, and return the value ν i associated with field f i . We recall that we only define the semantics of closed programs, thus expression a in a.f must be a location. Assigning a type to an expression of the form a.f is rather intuitive: having defined a type T α containing a field named f, we check if expression a is of type T τ. If so, the type of a.f is the type defined for f. The following two rules implement this intuition:type T α = {. . . , f : β f τ f , . . .} Γ • Σ a : reg T τ Γ • Σ a.f : β f τ f [α → τ]

The OCaml array type is a special case of mutable data structure. The Array module of the OCaml standard library is actually linked with external C code defining the type of arrays, an allocated block in the heap, and functions over arrays (length, get, or set, for instance).

Anyone who has ever programmed on a Unix system knows very well the frustration of a segmentation fault error.

In Sec.

[START_REF] Ancona | An algebraic approach to mixins and modularity[END_REF].2, we describe an approach that allows us to prove a union-find data structure in the WhyML language.

It is an interesting aspect that ghost code is generating ghost types.

https://vocal.lri.fr/

OCaml (annotated)

In Chap.[START_REF] Assaf | Expressing theories in the λΠ-calculus modulo theory and in the Dedukti system[END_REF] we propose a way to tackle the specification and verification of effect-free iterators, e.g., fold-like functions.

The code of Array.iter uses Array.unsafe_get to access the elements of the array, since it is safe given the limits of the for loop.

https://caml.inria.fr/pub/docs/manual-ocaml/extn.html#sec272

Whenever we introduce a new WhyML module, for the sake of readability, we do not present the external dependencies on other WhyML modules.

For the sake of clarity, we authorize ourselves to surcharge operations length and [] to apply these both to sequences and arrays, even if Why3 does not (yet) support operations overloading.

We could, very well, imagine a creation function which would place the cursor in a specific position of tree, instead of always placing it on the leftmost element.

Remerciements

2. β 1 β 2 = β 2 β 1 .

β β = β.

Proof.

1. by case analysis on β 1 .

2. by case analysis on β 1 and sub-cases on β 2 .

by the definition of .

The following lemma establishes that for all masks β and β such that β β , then the result of β β is always equal to the β mask: Proof. By case analysis on β and using the definitions of and .

Finally, we show that the order relation is stable under the operator: Lemma 4.2.4 ( is stable under ). If β β then ∀β. β β β β.

Proof. By case analysis on β.

• case β β : β β holds (by the transitivity of ) and so β β β β ⇔ β β (by auxiliary lemma 4.2.3).

• case β β β : β β β β ⇔ β β (by auxiliary lemma 4.2.3).

• case β β: β β β β ⇔ β β (by auxiliary lemma 4.2.3), where the last relation holds by the reflexive property of . Lemma 4.2.5 (Extraction preserves mask union). E β (β 1 β 2 ) = E β (β 1 ) E β (β 2 ).

Proof. By case analysis.

• β = ghost: E ghost (β 1 β 2 ) = ghost = (ghost β 1 ) (ghost β 2 )

Proof. By induction on the structure of β and then by case analysis.

• case β is a singleton mask: by lemma 4.2.5.

• case β = β 1 β 2 . . . β n , hence β 1 and β 2 are also singleton masks: by IH sub-case β 1 = ghost: we conclude using the IH.

WhyML uf.dst <-uf.dst[x <-0]} to its body. This creates a new singleton equivalence class, with a zero-distance element. This trivially respects the uf type invariant, since we state maxd to be a non-negative value.

We can now specify and prove the termination of the find operation. This is as simple as follows:

WhyML let rec find (ghost uf: uf 'a) (x: elem 'a) : elem 'a ... variant { uf.maxd -uf.dst x } ensures { uf.dst result >= uf.dst x } = ... The variant uf.maxd -uf.dst x decreases at each recursive call due to the increasing property of dst. The implementation requires absolutely no change. When we do path compression, we do not change the dst value. Every element in the chain of x is now linked to the representative element, which respects the invariant, as all distances are still strictly increasing.

Using find, we can provide an implementation for the union operation. This demands some attention when it comes to update the values of dst and maxd, in order to respect the invariant of type uf. First, we implement the union function as follows: We keep the exact same specification as in the UnionFind.mli interface file. The code of union first calls find to retrieve the representative elements a and b of the classes of x and y. Then, the auxiliary function link merges the classes of elements a and b. The implementation of link is where "all the fun happens". This function is responsible for updating the values of the logical models in uf, as well as the pointers in the heap. We give the following specification to such a function: where le_roots_list x l states that x is no greater than the roots of the trees in l. Finally, we impose that there is no empty tree E in the middle of the child list of a T node. Predicate no_middle_empty_tree is straightforwardly defined.

Pairing Heaps are very simple to implement, while performing very well in practice [START_REF] Daniel | A Back-to-Basics Empirical Study of Priority Queues[END_REF]. The merge function simply takes the heap with the larger root, with respect to the X.compare order, and makes it the first child of the heap with the smaller root. We give the following WhyML implementation: Proving that merge and merge_term conform to their specification is rather easy, as SMT solvers are able to discharge all the generated verification conditions in no time. The delete minimum operation works in two steps: first, we remove the root of the heap and merge the children in pairs, i.e., the first child with second, the third with the fourth one, and so on; the second step merges all the impaired heaps to get the final heap. We implement these two steps as the following WhyML function: It states that, if a node is already visited, then all of its successors are already marked.

The other element of our specification is the complete predicate. From a logical point of view, the traversal terminates when all reachable nodes from the source have been visited. We materialize such an idea in the definition of the complete predicate, as follows:

From a programming point of view, we stop the traversal as soon as the stack is empty. The implementation of the has_next function follows exactly this explanation:

The proof that function has_next respects the given specification is harder than it might suggest. To better understand why, let us do a case analysis on the proof of the postcondition:

1. if the stack is not empty, then there are yet reachable nodes from the source to be enumerated;

2. if the stack is empty, then all of the reachable elements from the source have been visited during the traversal.

The first property stands for the correction of the traversal termination, while the second one represents completeness. The correction part is easily established, as follows:

• the stack is not empty, and the value c respects the permitted predicate which gives us, in particular, that all the elements in the stack belong to the marked set (using property that marked is the union of the stack and the visited sequence);

• also from the definition of permitted, we get that for every node v in the marked set, v is reachable from the source;

• so, by the fact that the stack and the visited sequence share no common elements (given, again, by the permitted property), we get that there reachable elements from the source that are not yet in the visited sequence, which corresponds exactly to the statement not (complete c).

SMT solvers are able to follow a similar reasoning to what we have just described, and so this part of the proof is completed fully automatically. The second part, completeness, is much harder. In fact, we need to introduce some extra information in our proof context in order to help provers to succeed. We introduce the following auxiliary lemma:

WhyML lemma path_stack : forall c v s. path c.source s v -> not (mem v c.visited) -> exists w. mem w c.stack /\ reachable w v Such a lemma ensures that every path from the source to a not yet visited vertex must contain a vertex from the stack. To see how the path_stack lemma can be used to complete the proof of completeness of function has_next, we reason by contradiction:

• let us suppose that not (complete c) holds;

• we get that there exists a node vv and a sequence ss, such that path c.source ss vv and not (mem vv c.visited) hold;

• when we instantiate lemma path_stack with c, v, and s, we get in our proof context WhyML exists w. mem w c.stack /\ reachable w v

• we know that stack is equal to Nil;

• so, we get that WhyML exists w. mem w Nil /\ reachable w v

• this is a contradiction (predicate mem always returns false for an empty list), resulting from assuming not (complete c).

With auxiliary lemma path_stack, SMT solvers are able to discharge the verification conditions stating the completeness of the has_next function. Now, we turn our attention to the proof the path_stack lemma itself. To obtain a fully automatic proof, we write the following lemma function, for which the contract is automatically translated to the above statement:

source v s in w end

The proof of path_stack is done by case analysis:

1. if v is in the stack, then it is the vertex we are looking for; 2. if v is not in the stack but the source is the stack, then the source is the vertex we are looking for;

3. if neither v or the source are in the stack, we then know that the source is in visited and so there is a path from the inside of visited to the outside of visited (vertex v). Such a path takes an edge v → w such that v' is in visited but not w. Using the permitted property, we know that w is necessarily in the stack, and so this is the vertex we are looking for.

We use function intermediate_value to find vertex w in the third case. This is a ghost function used to retrieve the first vertex of a path between the vertices u and v that does not verify a given property P, knowing that P(u) and ¬P(v) hold. In the case of path_stack, we call intermediate_value with the property to be in the visited sequence, defined as follows:

It is worth point out that intermediate_value takes as the last argument a sequence s to establish property path u s v, instead of using the reachable predicate. Using such a sequence we are able to implement the intermediate_value function by recursion over the structure of s. All the verification conditions generated for path_stack and intermediate_value are automatically discharged, which concludes the proof of function has_next.

We now focus on the proof of function next. This function follows a much traditional approach: we take the head element v of the stack (the pre-condition assures that stack is not empty), then we iterate over the successors of v, adding them to the stack and the set of marked vertices. We give the following WhyML implementation:

It is worth pointing out that the absurd point is provable thanks to the path_stack lemma. The most interesting part of the proof of next is to provide the invariant to the while loop. Let us go through in detail over such an invariant: the source vertex is not in the stack, WhyML invariant { not (mem c.source c.stack) } during the iteration, the cursor c respects only the permitted1 property, since we cannot state that all successors of elements in visited are marked as, in fact, we are using the while loop to insert all successors of v in the stack and the marked set, and, as well, all vertices already taken from the initial set !s of successors of v is marked,

and finally all vertices in !s are reachable from the source,

All the generated verification conditions are proved in no time by a combination of SMT solvers. It is interesting to note how we are able to frame the set of successor vertices that are incrementally included in the marked set. After the loop, as we know that !s is empty, the provided invariant is sufficient to prove that all the successors of v are included in the marked set. This proves the definition of the permitted predicate, which re-establishes the invariant of type cursor.

As an example of a client code to the DFS cursor, we give the following WhyML program that decides whether there is a path between two given vertices:

w then return True done; False

We keep iterating as long as function has_next returns True, i.e., while the cursor can provide new elements. In the case of an infinite graph, we can indeed iterate forever, which prevents us from proving termination of the is_path function. The diverges keyword accounts for the possibility of divergence. The loop invariant assures that, as long as we iterate, vertex w is not visited. If the loop terminates, then we can use the invariant to prove that vertex w is not reachable from v. On the other hand, if we reach vertex w during the traversal, then we use the return instruction to interrupt iteration and return True.

In-order Traversal of Binary Trees

The last example we give in this section is a cursor to perform an in-order traversal of a binary tree. This example comes with a bonus: we show how to use the WhyML modules system not only to prove refinements between cursors specification and implementation, but also to get, at the end, an executable code linking a client and a specific implementation of the cursor, even tough the WhyML client code knows only about the cursor specification. and to the Empty tree. We are now in position to declare the cursor operations next and has_next, whose contracts follow exactly those of the general specification of a cursor: Cursor Implementation. We now turn our attention to the actual implementation of an inorder cursor. We encapsulate the implementation of the in-order cursor in a module named InorderCursorImpl, which begins as follows: To implement the cursor operations performing an in-order traversal, we use the zipper [START_REF] Huet | The Zipper[END_REF] structure. Since we only perform descents towards the left sub-tree, we specialize the zipper data type as follows:

An element of type zipper forms a list where each element is a pair composed of an element and the corresponding right-hand sub-tree. We build such a list bottom-up, which represents the left part of the tree that is still to be traversed. Together with the zipper data type, we introduce the following logical function to convert from a zipper to a sequence: Function zipper_build simply traverses the tree all the way down to the left, collecting each element and its right-hand sub-tree, using argument z as an accumulator. The use of the function keyword instructs Why3 to automatically generate the logical counter-part of zipper_build. The advantage of such automatic encoding is two-fold: first, there is no need for us to write a formal contract for function zipper_build, as Why3 infers the strongest postcondition of such a function; second, it allows to use and reason about the definition of zipper_build in the logic of WhyML. For instance, the following lemma relates the result of zipper_build t z with the sequence formed by the arguments t and z: WhyML let rec lemma zipper_spec (t: tree elt) (z: zipper)

This lemma states that if we convert the zipper zipper_build t z to a sequence (function zipper_elements is applied as a coercion in the postcondition of the lemma) we get exactly the sequence t ++ z (functions elements and zipper_elements are here used as coercions). The proof is done by straightforward induction on the tree structure. Lemma zipper_spec is crucial to prove that the type invariant of a cursor containing a newly created zipper holds indeed. This is exactly the case for the function that creates a new in-order cursor: Let us note that in the above implementation we assume f to be a pure function, since Why3 does not feature support for higher-order stateful computations. We add to the regular arguments of fold_left two extra ghost arguments: sequence v represents the sequence of visited elements; list l0 is the whole list being traversed, as we need it as an argument of the permitted property. From the first pre-condition and postcondition we deduce that permitted acts as an invariant of the traversal. On the other hand, the second pre-condition states that at the entry of every recursive call to the fold_left function, sequence v forms a prefix of l0. Finally, the last postcondition ensures that this is a deterministic traversal: we enumerate the elements of l, and only those.

When we feed Why3 with the above implementation and specification of fold_left, all the generated verification conditions are automatically discharged by SMT solvers. However, the given specification is of no use when we write a client program of fold_left. We must characterize the returned value of the accumulator, which in turn leads us to the specification of the consumer function f. This is where we need to introduce the notion of client invariant: as a fold-like function encapsulates an iteration, we must provide a loop invariant to fold_left, which represents the evolution of the iteration from the point of view of a client code. In fact, we manipulate here two different sorts of invariants: the permitted property, which we name the iterator invariant, and the client invariant. We add to fold_left the extra argument inv, a function of type acc -> seq elt -> bool, as well as the following specification over f, acc, and inv: We note that it is possible to turn f into a global function of the program, since every recursive call to fold_left is done on the same argument f. We note, as well, that this leads us to an entirely first-order proof. The contract of function f is, in our opinion, easier to understand with respect to when it was embedded in the contract of the fold_left function itself. From the point of view of fold_left, function f and predicate inv remain undefined, as it is up to the client to provide particular instances according to each use case. Finally, we can derive the following, much simpler, WhyML implementation of fold_left: Such an implementation features only three arguments (with only the sequence v as a ghost argument), which is much closer to its OCaml counterpart. The contract is simplified, as well, since there is no need to reason about invariant inv and with the postcondition almost mimicking the pre-condition. When fed to Why3, this program generates a set of verification conditions that are easily proved by SMT solvers.

In order to test our verified implementation of fold_left, we can use the following fold_correct function:

WhyML let fold_correct (acc: acc) : (ghost vres: seq elt, accu: acc) requires { permitted empty l0 /\ inv empty acc } ensures { permitted vres l0 /\ inv vres accu /\ complete vres l0 } = fold_left empty acc l0 end This simply calls fold_left to perform an iteration over list l0. When the iteration halts, we can prove that the complete property holds. This completes the definition of module FoldLeft.

Case Studies

We present here some more case studies, employing different data structures and fold iterators, in order to experimentally validate our proposal based on the permitted/complete pair, as well as on the Why3 modules system to build a clearer proof.

Binary trees

Fold implementation. The first case study we present is the use of a fold-like function to traverse and compute the number of elements of a binary tree. This example is inspired by the work of Yann Régis-Gianas and François Pottier [START_REF] Régis | A Hoare logic for call-by-value functional programs[END_REF].

We introduce module FoldTree to encapsulate the WhyML implementation of the fold iterator over binary trees. The structure of this module is very similar to that of FoldLeft, presented in the previous section. We introduce a constant t0, the whole tree being iterated over, predicate inv, and the consumer function f, which presents the very same contract as the consumer function of fold_left: When it comes to specification, there are some subtle differences between the two iterators. In the case of fold_left, at any moment we can rebuild the entire list from the elements of sequence v and the portion of the list that remains in the iteration. This is not the case when we iterate over a binary tree: the sequence of visited elements and the tree t are only a prefix of the entire sequence of the elements in t0. The following first two pre-conditions account for this behavior: where p i represents the i-th element of polynomial p.

In order to devise a proper definition to eval, let us introduce some auxiliary functions. We begin by defining the result of the i-th value of a sequence v to the i-th power of a value x, as the following logical function: This is as simple as turning the polynomial p into a sequence, using the previously defined function elements_left, and then traverse it using the eval_sequence function.

We implement and specify the consumer function to be applied to the fold_right iterator as follows: Following the Horner method, the consumer function takes the next element in the enumeration, c, and adds it to the product of the accumulator value, acc, by x, the value assigned to the polynomial variable. Next, we provide the following suitable client invariant to the traversal:

We can finally instantiate the non-defined elements of the FoldRight module:

WhyML clone FoldRight as FR with type elt, type acc, val l0, predicate inv, val fun_f

The above clone expression generates verification conditions that state the specification inclusion of the implemented fun_f function with respect to the fun_f function declared in the FoldRight module. The proof of specification inclusion amounts to proof that the postcondition of the implementation of fun_f implies the postcondition given to the fun_f val in the FoldRight module, i.e., the following property holds:

To prove such a statement, we need two auxiliary lemmas. The first one is a simple property about the composition of reverse, cons, and snoc:

WhyML lemma snoc_reverse_cons : forall v: seq 'a, c: 'a. reverse (snoc v c) = cons c (reverse v)

The second states exactly the implication we wish to prove:

The proof of lemma eval_sequence_cons proceeds as follows:

swap t.a i j; swap t.inv i j; t let compose (p q: t) : t requires { p.size = q.size } ensures { result.size = p.size } ensures { forall i. 0 <= i < p.size -> result.a[i] = p.a[q.a[i]] } = let n = p.size in let res = make n 0 in let ghost ires = make n 0 in for i = 0 to n -

) in pigeonhole (n -1) (m -1) g; return end done; pigeonhole n (m -1) f let cycle_length (t: t) (i: int63) : (n: int63, ghost s: seq int)

Synthèse

Cette thèse se place dans le cadre des méthodes formelles et plus précisément dans celui de la vérification déductive. La vérification déductive propose un ensemble d'approches pour transformer la correction d'un code d'un logiciel en un énoncé mathématique et après prouver sa validité. À fin de générer un tel énoncé, le programme doit être munis d'une description mathématique de son comportement pendant l'exécution, ce qui est traditionnellement nommée comme la spécification fonctionnel. En étant capable de montrer la correction de l'énoncé généré, on peut affirmer que le programme est correct vis-à-vis de sa spécification. Plusieurs outils existants mettent en place l'approche de la vérification déductive pour vérifier la correction des logiciels. On peut citer les assistants de preuve manuels tels que Coq, PVS, Isabelle ou KeY, mais aussi les démonstrateurs automatiques tels que les solveurs SMT comme Alt-Ergo, CVC4, Z3 et aussi les démonstrateurs de la famille TPT, comme SPASS ou Vampire. Par ailleurs, ils existent des outils qui proposent une interface entre l'utilisateur et les démonstrateurs de théorèmes. Ces outils proposent ce qui est traditionnellement appelé un langage dédié à la preuve, aussi bien qu'un mécanisme automatique pour la génération de l'énoncé de correction d'un programme. Dans cette classe d'outils on peut citer Dafny, Viper ou encore Why3.

Tout au long de cette thèse, le système Why3 a été utilisé, d'une part, comme l'environnement pour expérimenter les idées développées, et d'autre part comme la cible de certaines contributions de cette thèse. Why3 fournit un ensemble d'outils pour la spécification, l'implémentation et la vérification à l'aide de démonstrateurs externes. Why3 propose en particulier un langage de programmation adapté à la preuve, appelé WhyML. Un aspect important de ce langage est le code fantôme, à savoir des éléments de programme introduits exclusivement pour les besoins de la spécification et de la preuve. Pour obtenir un code exécutable, le code fantôme est éliminé par un processus automatique appelé extraction. L'une des contributions principales de cette thèse est la formalisation et l'implémentation du mécanisme d'extraction de Why3. La formalisation consiste à montrer que le programme extrait préserve la sémantique du programme de départ, en s'appuyant notamment sur un système de types avec effets.

Le nouveau mécanisme d'extraction de Why3 a été utilisé avec succès pour obtenir plusieurs modules OCaml corrects par construction, dans le cadre d'un projet de recherche plus large, le projet VOCaL. Le but ce projet est de construire une bibliothèque formellement vérifiée de structures de données et d'algorithmes, écrits dans le langage OCaml. Dans le cadre du projet VOCaL, les travaux de cette thèse ont contribué au développement d'un langage de spécification pour OCaml. Un tel langage à été utilisé pour spécifier formellement les éléments introduits dans des fichiers .mli, les fichiers interfaces du langage OCaml. L'utilisation de ce langage permet, notamment, de montrer qu'une structure de données ou algorithme implémenté et vérifié en WhyML est bien un raffinement de la spécification donnée dans l'interface.

Cet effort de preuve de programmes OCaml a conduit à trois autres contributions de cette thèse. La première est une technique systématique pour la vérification de structures avec pointeurs, à l'aide de modèles du tas délimités. Une preuve entièrement automatique d'une structure unionfind a pu être obtenue grâce à cette technique. La seconde est un mécanisme systématique pour extraire du code fonctoriel à partir d'un programme vérifié avec Why3. Cela a permis notamment d'extraire des foncteurs OCaml, correctes par construction, pour deux implantations différentes de 1