Je veux tout d'abord exprimer ma gratitude envers mes deux encadrants Thomas Genet et Thomas Jensen, qui m'ont supportés et guidés tout au long de ces trois années de thèse. Ils ont su me faire confiance dans les longs moments d'incertitudes et j'ai toujours pu trouver la porte de leur bureau ouverte pour y trouver conseil. Je n'aurai pas pu imaginer meilleurs encadrants pour diriger ma thèse.

Je tiens aussi à remercier mes rapporteurs Naoki Kobayashi et Mihaela Sighireanu pour leur intérêt porté à mes travaux ainsi que leur retours de qualité. Je remercie aussi Jasmin Blanchette d'avoir accepté sa place d'examinateur au sein de mon jury de thèse, et Sophie Pinchinat d'avoir présidé ce jury. Merci pour leur intérêt porté à ma présentation, et leur retours.

Merci aussi à David Pichardie et Delphine Demange pour m'avoir ouvert les portes de l'ENS de Rennes et dirigé vers la recherche à l'époque ou je cherchai encore ma voie. J'ai passé au sein de l'ENS, puis de l'IRISA, des année passionnantes.

Je remercie aussi toute l'équipe Celtique qui m'a accueilli pendant ma thèse, en particulier mes différents co-bureaux, Alix Trieux d'abord et Thomas Rubiano enfin mon compagnon d'escalade. Merci aussi à Lydie Mabil puis Stephanie Gosselin Lemaile pour leur aide dans mes démarches administratives.

Merci enfin à ma famille et mes amis qui ont eu la force de rester confinés chez eux pendant la pandémie, qui auraient tant voulu venir m'encourager pendant ma soutenance mais qui ont dû se contenter de m'observer derrière l'écran de leur ordinateur.

7

Nous proposons une implémentation pour chacun de ces deux composants. Pour l'étudiant, nous définissons une variation de notre algorithme d'inférence de langage définit dans le Chapitre 5 adapté aux relations régulières. Pour l'enseignant, nous définissons un algorithme de recherche de contre-exemples basé sur la recherche de « chemins synchronisés » dans des automates d'arbres. Nous montrons qu'en posant quelques hypothèses sur le système CHC considéré, cette procédure permet d'inférer n'importe quel modèle régulier.

1.1.

CONTENTS

Résumé en Français

L'objectif de cette thèse est d'étudier l'usage des langages d'arbres réguliers combinés aux systèmes de réécriture, appliqué à la vérification automatique de propriétés sur des programmes fonctionnels d'ordre supérieur dans le but de faciliter la détection et correction de bugs. Si les bugs sont souvent associés à des épisodes catastrophiques comme la destruction de la fusée Ariane 5 à cause d'un bug dans le programme de guidage, de tels événements ont tendance à cacher la présence massive des bugs dans les programmes communs, où leurs effets sont moins spectaculaires. En 1978, Lientz et al. [START_REF] Lientz | Characteristics of applications software maintenance[END_REF] ont montré que les programmeurs concentrent 17% de leur effort de développement à la correction de bugs. Une étude similaire réalisée par Amit et al. en 2020 [START_REF] Amit | The corrective commit probability code quality metric[END_REF] a montré que sur la plateforme de développement partagée GitHub, 20% des changement appliqués aux projets (« commits ») sont dédiés à la correction d'un ou plusieurs bugs. Bien que peu dangereux, il est estimé [START_REF]The economic impacts of inadequate infrastructure for software testing[END_REF] que ces bugs coûtent en moyenne 59 milliard de dollars par ans à l'économie Étasunienne seule (soit 0.6% de leur PIB), en plus d'être passablement agaçants pour les utilisateurs. De nombreux outils comme Nitpick [START_REF] Blanchette | Nitpick: A counterexample generator for higher-order logic based on a relational model finder[END_REF] ont été développés pour aider les développeur à tester leur programmes et traquer les bugs. Malheureusement sauf dans de rare cas, le test est insuffisant pour garantir l'absence de bugs. Le reste du temps, une approche plus formelle doit être utilisée pour prouver que le programme est correct. En pratique cependant, même en utilisant des assistants de preuve comme Coq [START_REF]The coq proof assistant[END_REF] ou Isabelle HOL [START_REF] Nipkow | Isabelle/HOL -A Proof Assistant for Higher-Order Logic[END_REF], construire la preuve de correction d'un programme demande beaucoup de temps et d'expertise, un investissement difficile à consentir dans l'industrie du développement. Cette thèse s'intéresse à la création de techniques de preuve complètement automatisées permettant de réduire cette investissement. En particulier nous nous intéressons à la vérification automatique de propriétés de sécurité sur des programmes purement fonctionnels d'ordre supérieur, famille de programmes plus naturellement adaptée à la vérification formelle. Plus précisément encore, nous nous intéressons à la famille des propriétés « régulières » (dont la nature est précisée plus bas) pour laquelle nous montrons qu'il est toujours possible de prouver ou d'improuver la propriété complètement automatiquement.

Il existes déjà de nombreuses techniques détaillées dans le Chapitre 3 permettant d'automatiser des parties ou l'entièreté de la vérification, mais sans garantie de résultat sur une famille de propriété particulières. La plupart de ces techniques reposent sur la recherche d'une abstraction de l'exécution du programme permettant de vérifier la propriété donnée. Parmi ces méthodes d'abstraction, Genet et al. [START_REF] Genet | Equational approximations for tree automata completion[END_REF][START_REF] Genet | Termination criteria for tree automata completion[END_REF] propose d'utiliser des systèmes de réécriture combinés à l'algorithme de complétion d'automate d'arbre pour générer des abstraction régulières. Cette méthode permet ainsi d'envisager le développement d'une technique de vérification complète sur les propriété régulières. C'est l'objectif de cette thèse dans laquelle Ce programme peut être représenté par le TRS R suivant (les variables sont soulignées) :

sort(nil) → nil sort(cons(x, l)) → insert(x, sort(l))

insert(x, nil) → cons(x, nil) insert(x, cons(y, l)) → ite(leq(x, y), cons(x, cons(y, l)), cons(y, insert(x, l)))

Ici l'ensemble des symboles Σ est par convention séparé en deux sous-ensembles F = {sort, insert} contenant les « symboles de fonctions » et C = {0, s, nil, cons} contenant les « symboles constructeurs » permettant de représenter les valeurs du programme.

En complément, let systèmes de réécritures peuvent êtres utilisés pour représenter des ensembles réguliers de termes, appelés « languages réguliers », à l'aide d'automates d'arbres. Un automate d'arbre, noté A, est un quadruplet Σ, Q, Q f , ∆ où Σ est un ensemble de symboles, Q un ensembles d'états de l'automate, Q f ⊆ Q un sous ensemble d'état finaux et ∆ un TRS défini sur les motifs T (Σ, Q) définissant les transitions de l'automate. Le langage reconnu par A, noté L(A) contient tous les termes t ∈ T (Σ) tel que t → * ∆ q où q est un étal final de l'automate. Par exemple, l'automate suivant peut être utilisé pour reconnaitre l'ensemble des listes d'entiers naturels :

nil → q list cons(q N , q list ) → q list 0 → q N s(q N ) → q N Ici l'état q list est un état final de l'automate, et q N un état intermédiaire reconnaissant tous les entiers naturels. Le termes cons(s(0), nil) est reconnu grâce à la séquence de réécriture suivante :

cons(s(0), nil) → cons(s(q N ), nil) → cons(q N , nil) → cons(q N , q list ) → q list La combinaison des termes de réécritures avec les automates d'arbre nous servent a représenter nos propriétés à vérifier sur les programmes d'ordre supérieur. Par exemple, si nous voulons vérifier la propriété suivante sur le programme de tri par insertion : « pour toute liste l, sort l retourne toujours une liste triée ». Cette propriété peut être exprimée comme suit à l'aide du système de réécriture R défini plus haut :

∀l. sorted(sort(l)) → * R false où sorted est défini à l'aide des nouvelles règles de réécriture suivantes :

Abstractions équationnelles

Une première idée pour calculer un tel ensemble O est d'utiliser l'Algorithme de Complétion d'Automate [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. En partant d'un automate A 0 reconnaissant I, cet algorithme est capable de calculer une série d'automates A 1 , A 2 , . . . convergeant vers A * reconnaissant R * (I). Tel quel, la terminaison de cet algorithme n'est pas garantie pour tout automate initial A 0 [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. De plus, l'abstraction obtenue via A * peut être trop large pour vérifier une propriété donnée. Par exemple dans notre cas, avoir false ∈ L(A * ) ne permet pas de vérifier notre propriété sur le tri par insertion. Un solution proposée par Genet [START_REF] Genet | Equational approximations for tree automata completion[END_REF] est de guider l'algorithme de complétion à l'aide d'équations sur les termes. Pour un ensemble d'équation E donné l'idée est la suivante : pour chaque équation u = v ∈ E (où u et v sont des motifs), si s = uσ CONTENTS et t = vσ alors s et t seront reconnus par le même état dans A * . Ce système permet effectivement de contrôler finement la qualité de l'abstraction réalisée. Pour en plus assurer la terminaison de l'algorithme, Genet propose de considérer E composé de trois ensembles d'équations [START_REF] Genet | Termination criteria for tree automata completion[END_REF] :

• E c un ensemble d'équations « contractantes » de la forme t = t| p (où t| p est le sous-terme de t à la position p). Ces équations permettent de contrôler l'abstraction réalisée par l'algorithme.

• E R = { u = v | u → v ∈ R } • E r = { f (x 1 , . . . , x n ) = f (x 1 , . . . , x n ) | f ∈ Σ } assurant le déterminisme de l'abstraction.
Genet montre que si T (C) a un nombre fini de formes normales par rapport à Pour cette raison, dans le Chapitre 4 de cette thèse, nous définissons une nouvelle classe de TRS représentant des programmes d'ordre supérieur et continuant d'assurer la terminaison de l'algorithme de complétion avec E c contractant sur les valeurs non fonctionnelles. En combinaison avec la définition d'une procédure de recherche d'équations implémentée dans Timbuk 3 [START_REF] Genet | Reachability Analysis and Tree Automata Calculations[END_REF], nous somme capable de résoudre automatiquement une variété de problèmes réguliers sur des programmes d'ordre supérieur [START_REF] Genet | Experiments with timbuk 3[END_REF]. Un résultat majeur de la formalisation de cette procédure et de nos expérience est la démonstration de l'incomplétude de cette technique sur les problèmes réguliers. La cause de cette incomplétude est l'introduction des ensembles d'équations E R et E r . D'un coté E r limite l'ensemble des abstractions atteignables aux abstractions « functionelles », dans lesquelles chaque terme est abstrait par un unique élément. De l'autre, E R limite l'ensemble des abstractions atteignables aux abstractions « éfondrantes », dans lesquelles chaque deux termes liés par la relation de réécriture → R sont abstraits par le même élément. Nous montrons que parce que les abstractions générées sont à la fois fonctionnelles et éffondrantes, cette procédure ne permet pas de résoudre n'importe quel problème régulier.

E c = { u → v | u = v ∈ E c },

Inférence de types réguliers

Pour résoudre les problèmes engendrée par notre première technique, nous introduisons dans le Chapitre 5 une nouvelle technique de vérification automatique, complète sur les problèmes réguliers. Cette nouvelle procédure est fondée sur l'interprétation abstraite du système de réécriture représentant le programme vérifié. Celle-ci est composée de deux éléments :

• Un domaine abstrait Λ = Σ # , ∆ # (un automate) définissant l'abstraction de T (Σ), où Σ # est un ensemble de valeur abstraites et ∆ # un système de réécriture composé de règles de la forme f (v # 1 , . . . , v # n ) → v # avec v, v 1 , . . . , v n ∈ Σ # . Un terme t de T (Σ) est abstrait par v # ∈ Σ # ssi t → * ∆ # v # .

• Un système de réécriture R # définissant l'abstraction de R, composé de règles de la forme

f (v # 1 , . . . , v # n ) → v # avec v, v 1 , . . . , v n ∈ Σ # .
En considérant une nouvelle fois notre propriété sur le tri par insertion :

∀l. sorted(sort(l)) → * R false

Dans notre contexte d'interprétation abstraite, prouver cette propriété revient à trouver Λ et R # tel que :

sorted(sort(l # )) → * R # ∪∆ # false #
où l # abstrait toutes les listes et false # abstrait la valeur false. Cette nouvelle formulation fait disparaitre la quantification universelle. Pour trouver automatiquement Λ et R # à partir de cette nouvelle formulation nous proposons une procédure d'inférence de type consistant à typer le terme d'intérêt (ici sorted(sort(l))) ainsi que le reste du TRS R avec des types réguliers. Cette procédure d'inférence de type est une analyse arrière, analysant chaque fonction du programme pour lui trouver une signature régulière. Par exemple, notre procédure d'inférence de type affecte la signature suivante aux fonction de notre programme de tri par insertion :

insert(ab # , sorted # ) → sorted # sort(l # ) → sorted # sorted(sorted # ) → true #
où sorted # est une nouvelle valeur abstraite (un langage régulier) inférée par notre analyse pour typer les liste triées. Cette valeur, ajoutée dans Λ, est « apprise » en analysant la fonction récursive sort avec l'aide de notre nouvelle procédure d'apprentissage d'invariants réguliers : une procédure de raffinement d'abstraction guidée par les contre exemples et assistée par un solveur SMT. Dans notre exemple, les signatures trouvées permettent d'affecter l'annotation de type suivante (et seulement cette annotation) au terme d'intérêt, prouvant ainsi la validité de notre propriété :

sorted(sort(l : l # ) : sorted # ) : true # À la fin de l'analyse, les types affectés au TRS constituent une abstraction Λ valide, tandis que les signatures de chaque fonction constituent une abstraction R # du programme validant la propriété. Les abstractions générées par cette technique sont toujours déformantes, mais ne sont pas fonctionnelles contrairement à notre précédente technique, permettant ainsi la résolution de n'importe quel problème régulier.

Relations régulières

Si la technique proposée au Chapitre 5 permet de traiter n'importe quel problème régulier, la portée de ceux-ci ne permet pas de vérifier des propriétés établissant des relations entre les variables du programme. Par exemple, considérons le langage Eq = { eq(s, t) |s ∈ N, t ∈ N, s = t } représentant la relation d'égalité sur les entiers naturels. Ce langage n'est pas régulier : il ne peut pas être représenté par un automate d'arbre. Par conséquent, un problème dont la résolution dépend de la représentation CONTENTS de ce langage n'est pas un problème régulier. De manière générale, si le problème considéré nécessite la vérification d'une relation inductive entre deux valeurs (ou plus), alors celui n'est pas régulier. Il est donc en dehors du champ des deux techniques de vérifications proposées jusqu'ici. Dans la Chapitre 6 nous proposons une technique de vérification automatique permettant de contourner cette limitation. Cette technique repose sur l'inférence automatique de « relations régulières » : des langages réguliers encodant des relations à l'aide d'un opérateur de convolution sur les arbres. Dans ce contexte, la relation Eq mentionnée plus haut peut-être indirectement représentée par le langage régulier Eq ⊕ = { s ⊕ t | eq(s, t) ∈ Eq } où ⊕ est un opérateur de convolution sur les arbres [Tata]. Celui-ci est défini comme la « superposition » des arbres s et t illustrée comme suit : eq(0, 0) ⇒ true ∀x. eq(s(x), 0) ⇒ false ∀x, y. eq(x, y) ⇒ eq(s(x), s(y)) ∀y. eq(0, s(y)) ⇒ false

Ici eq est une relation abstraite. La seule solution à ce problème est le modèle M tel que M (eq) = Eq ⊕ . Pour inféré automatiquement ce langage nous définissons la première procédure d'inférence de relations régulière, basée sur ICE [START_REF] Garg | Ice: a robust framework for learning invariants[END_REF] : une technique d'apprentissage par implications et contre exemples. Cette procédure est composée de deux algorithmes appelés en boucle jusqu'à ce que la procédure converge vers une solution :

• un « étudiant » chargé de proposer de nouveaux modèles candidats à partir d'un ensembles de contraintes d'apprentissages (initialement vide)

• un « enseignant » chargé d'évaluer les modèles proposés par l'étudiant et, tant que ceux-ci sont invalides, générer de nouvelles contraintes d'apprentissage.

Chapter 1

Introduction

This thesis studies how regular languages of trees combined with term rewriting systems can be used to automatically verify properties on higher-order purely functional programs to facilitate the detection and correction of software bugs. Many tools has been developed to help developers testing their programs and tracking down the bugs. Unfortunately, except in rare cases, testing is not sufficient to guarantee the absence of bugs. In most cases a more formal approach is required to prove the correctness of the program. In practice however, using proof assistants such as Coq [START_REF]The coq proof assistant[END_REF] or Isabelle/HOL [START_REF] Nipkow | Isabelle/HOL -A Proof Assistant for Higher-Order Logic[END_REF] to help the developer carry out the proof still requires a lot of time and expertize. Such an investment can be hard to consent in the software development world. Our goal in this thesis is to develop new techniques and tools for the programmers to develop safer programs while reducing the time and expertize needed to verify them. In particular, we focus on the automatic verification of regular safety properties, a family of properties for which completely automatic verification can be achieved; and on higher-order purely functional programs, a family of programs naturally suited to formal verification. There already exists many techniques detailed in Chapter 3 whose purpose is to automate parts of the verification process. However most of the time they do not provide any guarantees of completeness on any family of properties. All of these techniques rely on the construction and analysis of an abstraction of the program execution allowing the verification of the given property. Among these abstraction methods, Genet at al. [START_REF] Genet | Equational approximations for tree automata completion[END_REF][START_REF] Genet | Termination criteria for tree automata completion[END_REF] suggests to use term rewriting systems combined with the Tree Automata Completion algorithm to generate regular abstractions of the program execution, which allows us to foresee a complete verification procedure for regular properties. However the proposed method is not yet complete and cannot handle higher-order programs. This is the purpose of this thesis where:

• We extend the abstraction method proposed by Genet et al. in order to verify regular properties on higher-order programs. We then discuss the limits of this technique.

• We go beyonds these limits by developing a new abstraction procedure based on the Tree Automata Completion algorithm and SMT solving. This helps us design a entire verification procedure that is complete on regular safety properties and complete in refutation.

• We then extend this abstraction procedure to go beyond regular properties and verify relational properties, still using regular tree languages.

Motivation

Bugs are everywhere. They are often associated to catastrophic failures such as the destruction of the Ariane 5 rocket prototype due to a bug in the guidance program, but such major events often hide the massive presence of bugs in non-critical software where their effect is much less spectacular. In 1978, [START_REF] Lientz | Characteristics of applications software maintenance[END_REF] showed that programmers focus 17% of development effort in fixing bugs. On the GitHub development platform, 20% of program changes (commits) across projects are dedicated to fixing bugs [START_REF] Amit | The corrective commit probability code quality metric[END_REF].

Even though rarely deadly, it is estimated [START_REF]The economic impacts of inadequate infrastructure for software testing[END_REF] that software bugs cost $59 billion per year for the U.S. economy alone (0.6% GDP), in addition to being fairly annoying for users. Development methodologies are often used to try to avoid the apparition of bugs, without eradicating them. This motivates the development of methods to detect and fix bugs before they can create damage.

shows that it is not enough: the property on sort is verified for the edge case (the empty list []), and for A::B::A::[] for which every part of the code is visited. The property remains incorrect in general.

Testing can filter common mistake, such as syntax errors, divisions by zero, invalid bounds, etc. However unless the program is tested on every possible inputs (which is usually far from being practicable) this does not give any general warranties on the program's correctness. This is why bugs continue to be spotted even on heavily tested programs. Detecting those bugs requires a more formal approach.

Proof Assistants

The only way to ensure the absence of bugs in a program is to formally prove its correctness. This require being able to formally state the specification the program implements, and also being able to reason about the program itself. If historically such proof have mostly be done by hand on paper, proof assisting tools are now regularly used to help the user through the development of the formal proof. The assisting can take multiple forms, from the verification and diagnostic of program annotations to the verification of complete proof edifices.

Mechanized proof assistants are the most complete form of program verification. An interactive theorem prover such as Coq [START_REF]The coq proof assistant[END_REF] or Isabelle/HOL [START_REF] Nipkow | Isabelle/HOL -A Proof Assistant for Higher-Order Logic[END_REF] can collaborate with the user to build and check complex proof of a wide range of properties. In this settings, the development is usually staged in three phases: the program definition generally as a higher-order functional program, the specification and the proof. For instance, the following code defines the insert-sort algorithm (not buggy this time) in the Coq proof assistant using the Gallina language: command), simplifying the current proof goal (simpl) or applying definitions, lemma and theorems (apply), etc.

Proof.

intros T leq l. induction l. + simpl. apply sorted_nil. + simpl. apply insert_preserves_sorting. assumption. Qed.

Proof assistants have been successfully used to develop and prove the correctness of critical softwares such as the certified C compiler CompCert [Inr05] and seL4 micro-kernel [KEH + 09].

However powerful, proof assistants completely depend on the user to carry out the proof. A certain degree of automation is possible through the use of tactics, but even simple proofs still need to be detailed, in particular to explicit the needed invariants of every loop of the program. For instance the preceding proof requires the definition and proof of the intermediate lemma insert_preserves_sorting ensuring the needed invariant on the insert function that it preserves "sortedness". The proof of this auxiliary lemma is in fact the main difficulty of the overall proof:

Overall the high level of expertize and the amount of work needed to design the proofs and to master the interactions with the assistant may discourage users to use such proof assistants to verify non critical softwares. It is not always clear that the cost due to the presence of bugs is worth such an investment.

Automated Verification Techniques

To reduce the cost of the proof, a wide variety of techniques has been developed that introduce degrees of automation in the verification process. Of course, automation has a cost: the less manual work remains for the user, the narrower the range of verifiable properties is. On one side of this spectrum, proof assistants are polyvalent tools that can verify a wide range of properties but with little to no automation. The user needs to write most of the proof. One the other side of this spectrum, we can put for instance the Hindley-Milner type inference algorithm [START_REF] Hindley | The principal type-scheme of an object in combinatory logic[END_REF][START_REF] Milner | A theory of type polymorphism in programming[END_REF]. It is a completely automatic verification algorithm for higher-order functional programs that can verify only one simple property: type safety. Type safety consist in separating the values into exclusive families, types, and verifying that at all points in the programs, the input and output of each function call matches the expected types, so that the program never stops before reaching the result of the computation. Type safety does not verify that the sort function above returns a sorted list, but it can at least ensure that the output is a list.

We will see in Chapter 3 that there are numerous techniques developed in between, balancing automation and expressiveness. All of them are based on some abstraction procedure, simplifying the description of the program execution. The Hindley-Milner type inference algorithm itself can be seen as an abstraction procedure where each program execution state is abstracted into a type (the type of the value it returns). Many of the verification techniques such as [MI13, RKJ08] are based on more sophisticated type inference systems where types can describe more complex, non exclusive, family of values. But such techniques require the user to annotate the program in order to carry out the proof.

Regular Verification Problems

A verification problem is the conjunction of a program and a property. Solving a problem consists in proving or disproving that the program verifies the property. It is impossible to define the family of problems that can be automatically verified without requiring some hints of any form from the user. We know that such problem exists, type safety being one of them. In addition, there already exists some verification techniques explored in Chapter 3 such as [START_REF] Kobayashi | Types and higher-order recursion schemes for verification of higher-order programs[END_REF][START_REF] Kobayashi | Predicate abstraction and CEGAR for higher-order model checking[END_REF][START_REF] Sato | Modular verification of higher-order functional programs[END_REF] that can already solve problems automatically. Such techniques however focus on relational properties over numerical data-types which is already far more challenging than type safety properties. They are generally not good at verifying regular problems that involve nonrelational properties on algebraic data-types (trees). In some ways regular problems are simpler as they do not involve any relations between the variables of the program. Instead they can be solved with a regular abstraction of the program, in which each abstract state can be described by a regular language. Type safety is a good example of a regular problem where each state can be abstracted by its type usually defined as a regular language in the program itself. However in general, the abstraction depends on the property to verify and can be far more complex. This makes the verification of regular problems generally far more complicated than simple type safety checking. Our previous sort example is a typical regular problem that could easily and automatically be solved by such verification technique. This is because as long as the values in the lists are taken from a finite domain of values (for instance { A, B } in our initial example), there exists a regular abstraction of the program in which sort outputs sorted lists. Note that regular problems still represent a small fraction of all the verification problems needed to prove the correctness of a program. The development of automated regular verification techniques does not aim at replacing proof assistants. However it can benefits proof assistants by adding more degrees of automation. In addition the software development process can be sped up by reliably filtering more bugs without spending time writing exhaustive tests.

Our Verification Framework

In Chapter 3 we see that many theoretical frameworks have been used to tackle the verification of higher-order functional programs with various degrees of automation. Since this thesis focuses on regular problems where execution states are abstracted by regular tree languages, we naturally chose to focus on tree automata [Tata], themselves built on top of term rewriting system [START_REF] Baader | Term Rewriting and All That[END_REF]. Tree automata provide a simple representation of regular tree languages and, thus, of algebraic data types found in functional programs. Meanwhile, term rewriting systems provide a convenient way of representing higher-order functional program execution in interaction with tree automata.

Term Rewriting Systems

TRSs are a widely used theoretical representation of computational systems [START_REF] Baader | Term Rewriting and All That[END_REF] that is especially convenient to model features found in modern functional programming languages, such as pattern matching.

Example 1.2.1 (From OCaml to TRS). Here is an example of the translation of the insert-sort algorithm written in OCaml (on the top) to a term rewriting system (on the bottom). This time for the sake of readability the comparison function leq is not a parameter of the functions, but is defined elsewhere in the program. sort(nil) → nil sort(cons(x, l)) → insert(x, sort(l)) insert(x) → nil insert(cons(x, l)) → ite(leq(x, y), cons(x, cons(y, l)), cons(y, insert(x l))) Each case of each function is translated into one rewriting rule.

In this framework, terms appearing on both sides of each rule represent states of the execution, and rewriting rules represent the logic, or semantics, of the program. If we name R the TRS modeling the insertion sort algorithm along with the sorted predicate function, our previous property on the sort function can then be restated as a rewriting problem as follows:

∀l. sorted(sort(l)) → * R false
where → * R is the reflexive, transitive closure of the rewriting relation with regard to R. We want to check that for any list l, the term sorted(sort(l)) never rewrites to false using R (it is a short way of ensuring it always either diverges or rewrites to true assuming the program is type safe). Another way to express our problem is by using sets of terms later called languages of terms. If we name L the language of all lists (of As and Bs), and I the language of input states { sorted(sort(l)) | l ∈ L } then we want to check:

false ∈ R * (I)
where R * (I) is the set of terms that are reachable from I using the TRS R defined as

{ u | t ∈ I ∧ t → * R u }.
Solving this problem can then be done by computing R * (I). In our setting, we require I to be a regular language which means it can be represented using a Tree Automaton.

Tree Automata and Regular Languages

Tree automata [Tata] are a convenient way of representing regular languages of terms. A tree automaton is defined as a rewriting system rewriting terms into states, representing the nature of the term. We say that a state "recognizes" a term when the term rewrites into this state.

Example 1.2.2. We previously named L the language of all lists of As and Bs. Here is a tree automaton recognizing L:

A → ab nil → list B → ab cons(ab, list) → list
Here ab and list are states of the automaton recognizing the languages {A, B} and L respectively. For instance, the term cons(A, cons(B, nil)) of L is recognized by list using the following rewriting sequence:

cons(A, cons(B, nil)) → cons(A, cons(B, list)) → cons(A, cons(ab, list)) → cons(A, list) → cons(ab, list) → list.
By definition, any regular language can be represented using a tree automaton. In addition, the combination of tree automata and rewriting systems has already lead to several algorithms designed for the computation of R * (I) when I is regular and R satisfies some properties. One of them is the Tree Automata Completion (TAC) algorithm [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. Remember that computing R * (I) where I is the set of initial states of a program is the key to the verification of safety properties. If I is recognized by a tree automaton A, then the TAC algorithm aims at computing a new automaton A * that recognizes R * (I) by completing A with the right transitions. Of course, computing such A * is not possible in general, in particular when R * (I) is not regular. In this case, instead the TAC algorithm will compute an over-approximation of R * (I). In [START_REF] Genet | Equational approximations for tree automata completion[END_REF], this is done by abstracting the program execution using a set of equations over terms. A safety problem is regular when there exists a regular over-approximation of R * (I) precise enough to solve it. In theory then, given the right set of equations any regular problem can be solved using the TAC algorithm [START_REF] Genet | Completeness of tree automata completion[END_REF]. However there exists no automatic method to find the correct equations given a regular problem. Some leads are given by Genet at al. [START_REF] Genet | Termination criteria for tree automata completion[END_REF] to facilitate the search of equations for first-order programs using "contracting" equations, but this does not guarantee the termination of the TAC algorithm while analyzing higher-order programs.

Contributions

In this thesis we explore new automatic verification techniques dedicated to the solving of regular problems on higher-order functional programs, using term rewriting systems and tree automata.

Equational Abstractions for Higher-Order Programs

We pursue the development of the equations-based abstraction procedure defined in [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. To go beyond the lack of termination guaranties for higher-order functional programs, we state and prove a general termination theorem for the Tree Automata Completion algorithm using contracting equations. From the conditions of the theorem we characterize a class of higher-order functional programs for which the completion algorithm terminates using contracting equations. In addition we formally define and discuss the completeness of the abstraction procedure based on contracting equations and show its functional collapsing regular completeness and completeness in refutation. In other words, if there exists a set of contracting equations able to solve the given safety problem, the procedure will eventually find it, and if there exists a counter-example to the verified property, the procedure will eventually find it. Through our OCaml implementation of the procedure in Timbuk 3 [START_REF] Genet | Reachability Analysis and Tree Automata Calculations[END_REF], we show how contracting equations allows us to solve regular safety problems even with such a simple procedure. However we also see the limitations of contracting equations, the lack of modularity and their inability to capture many regular abstractions.

Regular Type Inference

To solve these problems we present a new fully automatic verification technique for higher-order functional programs targeting regular safety problems. We solve the incompleteness arising from the use of contracting equations by defining a regular abstract interpretation framework where abstractions are directly and more precisely defined as non-deterministic tree automata. We solve the scalability issue by modularizing the analysis of each function of the program. To do that we formulate the abstraction procedure as regular type inference procedure, where each type is a regular language represented by a state of the tree automaton abstraction. Finally we improve the abstraction inference procedure itself by designing an actual regular invariant learning procedure, able to iteratively learn regular languages from examples and counter examples. We show that the resulting abstraction procedure is regularly complete and complete in refutation. We implemented this new verification technique in a new version of Timbuk (4) and compared it against Timbuk 3. The results show that it can handle more regular problems in comparable times while greatly improving the memory usage.

Regular Relations

In a final chapter we show how to extend our abstraction inference procedure to verify non-regular relational properties with regular tree languages. This is done by using tree automatic relations [Tata], a representation of relations where each item of the relation is stored as a convoluted term in a regular language. We propose an extension of the convolution operator to capture more relations, and design a regular Constrained Horn Clauses (CHC) solver able to automatically infer such relations based on the ICE inference procedure [START_REF] Garg | Ice: a robust framework for learning invariants[END_REF]. We provide a Rust implementation of this regular CHC solver tested on several case studies.

Summary

The rest of the thesis is structured as follows. Chapter 2 gives the required definitions of terms, rewriting systems and tree automata used throughout the document. It also gives an entry point to automated verification techniques from the point of view of model checking, and show how it can be used to formally define different family of problems (such as regular problems) in terms of program abstractions. Chapter 3 gives an overview of the literature focusing on automatic verifications techniques on higher-order functional programs. Chapter 4 explores the use of contracting equations to define regular abstraction while guaranteeing the termination of the Tree Automaton Algorithm. In this chapter we define a general termination criteria for the TAC algorithm and a class of higher-order functional programs for which contracting equations can be used without diverging. In Chapter 5 we tackle the shortcomings encountered at the end of the previous chapter with the definition of a novel verification technique based on the inference of regular languages types. Chapter 6 moves away from regular problems to discuss how regular languages can be used to verify relational properties that are out of the scope of the previously defined techniques. Finally, Chapter 7 concludes this thesis.

Chapter 2

Preliminaries

This chapter introduces general notions of terms, rewriting systems and tree languages that we use throughout this document. We show how terms can be used to model program states, how term rewriting systems can model program semantics, and how tree grammars can model program execution. We describe the automatic verification of higher-functional programs from a model checking point of view as the abstraction of individual states into tree languages.

Trees, Term and Patterns

Definition 2.1.1 (Tree). A finite ordered tree over a set of labels E is a mapping P → E where P ⊆ N * is a prefix-closed set of positions which represent a path from the root of the tree to the target node.

The rest of the section defines a special family of trees, called terms, that are particularly well suited to the modeling of higher-order functional program states, and algebraic data types values. Terms are labeled by ranked alphabets.

Definition 2.1.2 (Ranked Alphabet). A ranked alphabet Σ is a finite set of symbols (labels) attached to an arity function ar : Σ → N. For simplicity, we write f ∈ Σ n when f ∈ Σ and ar(f ) = n, and { f 1 : n 1 , . . . , f k : f k } to define the ranked alphabet { f 1 , . . . , f k } where ar(f i ) = n i for each i.

In the context of functional program modeling, symbols of the ranked alphabet are used to represent algebraic data type constructors such as nil or cons, function names and applications.

Definition 2.1.3 (Term). Let Σ be a ranked alphabet. A term over Σ has the form:

f (t 1 , . . . , t n )
where f is a symbol of Σ n and for each i, 1 ≤ i ≤ n, t i is also a (sub)term. The set of terms over Σ is written T (Σ).

Every state (expression, values) of higher-order functional programs can be encoded using terms.

Example 2.1.1 (Terms as Values). Consider the ranked alphabet Σ = { nil : 0, cons : 2, 0 : 0, s : 1 }. This alphabet can be used to build terms representing lists of natural numbers where nil and cons are list constructors, and 0 and s natural number 25 constructors using Peano's numbers. For instance, the OCaml list value 3::1::2::[] is modeled by the term cons(s(s(s(0))), cons(s(0), cons(s(s(0)), nil))). We sometimes use the following graphical tree representation to represent such terms: Definition 2.1.4 (Yield). Let Σ be a ranked alphabet. The yield of a term t of T (Σ), Y ield(t), is a word of Σ * that is the concatenation of every symbol of the tree from left to right. It can be inductively defined as follows:

Y ield(f ) = f if f ∈ Σ 0 Y ield(f (t 1 , . . . , t n )) = f . Y ield(t 1 ) . . . . . Y ield(t n )
where "." is the concatenation operator.

Definition 2.1.5 (Depth). The depth of a term t ∈ T (Σ), written |t| is inductively defined as

|f | = 0if f ∈ Σ 0 |f (t 1 , . . . , t n )| = 1 + max(|t 1 |, . . . , |t n |)
Definition 2.1.6 (Position). A position in a term t is a prefix-closed word of N * pointing to a subterm of t. We write t| p for the subterm of t at position p. It is defined by:

t| λ = t f (t 1 , . . . , t n )| i.p = t i | p
where λ is the empty word and "." in i.p is the concatenation operator. We name Pos(t) the set of valid positions p in t for which t| p is defined. A term can then be seen as a tree defined by the mapping Pos(t) → Σ giving the head symbol of the tree at a the given position. In addition we write t[s] p for the term t where the subterm at position p has been replaced by s.

t[s] λ = s f (t 1 , . . . , t n )[s] i.p = f (t 1 , . . . , t i [s] p , . . . , t n )
Example 2.1.3. Let Σ = {cons, nil}, t = cons(x, cons(y, nil)) and p the position p = 2.1. Then, t| λ = t, t| p = y and t[nil] p = cons(x, cons(nil, nil)).

Definition 2.1.7 (Subterm Ordering). We write s t or t s if there exists a position p ∈ Pos(s) such that s| p = t. In other words, t is a subterm of s. We write s t of t s when p is not λ (s = t). For all set of terms L ⊆ T (Σ), we write L for the smallest superset of L closed by subterm.

Definition 2.1.8 (Pattern). Let Σ and X be two disjoint ranked alphabets where X is a set of constants of arity 0 called variables. A term of T (Σ ∪ X ) is called a pattern.

We write Var (p) the set of variables occurring in the pattern p (Var (p) ⊆ X ). The set of patterns is also written T (Σ, X ) to clearly distinguish the symbols from the variables.

A term without variables is not a pattern and is called a closed term. To avoid any confusion we avoid the use of "term" to qualify patterns.

Definition 2.1.9 (Linearity). A pattern p is linear if the multiplicity of each variable in p is at most 1.

Definition 2.1.10 (Substitution). Let Σ be a ranked alphabet and X a set of variables.

A substitution σ is a partial application of X → T (Σ, X ), mapping variables to terms. We write Dom(σ) the set of variables for which σ(x) is defined (Dom(σ) ⊆ X ). We tacitly extend every substitution σ to the endomorphism σ :

T (Σ, X ) → T (Σ, X )
where pσ is the result of the application of the pattern p to the substitution σ.

Definition 2.1.11 (Context). Let Σ be a ranked alphabet. A context C[ ] is a term of T (Σ∪{ })
where is a special "hole" symbol. Here we consider contexts containing a unique symbol . We note

C[t] = C[ ][t] p where p is the position such that C[ ]| p = .

Rewriting Systems

We have seen that terms are useful to describe execution states of higher-order functional programs. However it does not describe how states are connected one to each other. In this section, we define Term Rewriting Systems, a theoretical framework to describe transition systems between terms using rewriting reduction rules. Most of the definitions in this section are found in Term Rewriting Systems [TeReSe] and Term Rewriting and All That [BN98] by Baader and Nipkow.

Definitions

Definition 2.2.1 (Rewriting rule). Let Σ be a ranked alphabet and X a set of variables. A rewriting rule defined over Σ and X is a pair l, r where l and r are patterns of T (Σ, X ). We write l → r for such rewriting rule. The pattern l must not be a variable, and each variable occurring in r must occur in l: Var (l) ⊇ Var (r).

Definition 2.2.2 (Term Rewriting System). Let Σ be a ranked alphabet and X a set of variables. A term rewriting system (TRS) R over Σ and X is a set of rewriting rules (over Σ and X ). It defines a rewriting relation → R in which for all terms s, t ∈ T (Σ) we have s → R t iff there exists a rule l → r ∈ R, a substitution σ : X → T (Σ) and a position p ∈ P os(s) such that s| p = lσ and t = s[rσ] p . In other words we have

→ R = { (C[lσ], C[rσ]) | l → r ∈ R }.
Definition 2.2.3 (Rewriting path). Let R be a TRS defined over Σ and X . For all n ∈ N we write → n R the smallest relation such that

s → 0 R t ⇐ s = t s → n+1 R t ⇐ s → n R u ∧ u → R t ∧ u = t
We write → * R for the transitive and reflexive closure of → R such that for any two terms s, t we have s → * R t iff there exists some n ∈ N such that s → n R t. We say that there exists a rewriting path between s and t using R iff s → * R t.

Definition 2.2.4 (Reachable Terms). If s → * R t we say that t is reachable from s. If L is a set of term (a tree language, as seen in Section 2.3), then R n (L) is the set of terms reachables from L in n steps or less: R n

(L) = { t | s ∈ L, t ∈ T (Σ), k ≤ n. s → k R t }.
By extension we write R * (L) for the set of terms reachables from L in any number of steps: R *

(L) = { t | s ∈ L, t ∈ T (Σ). s → * R t }.
Definition 2.2.5 (Irreducible Terms and Normal Forms). Let R be a TRS over Σ and X . A term s is irreducible w.r.t. R, if it cannot be rewritten: for all term t, if s → * R t then s = t. We write IRR(R) the set of terms that are irreducible w.r.t. R. For all terms s, any irreducible term t such that s → * R t is called a normal form of s. We then write s → ! t. If t is the unique normal form of s then we write -→ s ! = t.

Finally we say that the term s diverges iff it has no normal forms.

Properties of TRSs

We present here some special properties of term rewriting systems that will be useful throughout the document.

Definition 2.2.6 (Linearity). A rewriting rule l → r is left-linear (resp. right-linear) if l (resp. r) is linear. A TRS R is left-linear (resp. right-linear) if all its rewriting rules are left-linear (resp. right-linear).

Left linearity in particular is often found in functional programing languages where it is not possible to use the same name for multiple parameters of the same function (so as to express that they must be the same value) without hiding previous same-name bindings. For instance in the OCaml function definition let f x x = x, the first x parameter is hidden by the second x parameter, they are not the same. It is equivalent to let f x y = y. This implies that any TRS encoding an OCaml program will be left-linear. It is easy to show that a confluent term rewriting system must also be a deterministic rewriting system since two different normal forms could not be joined by confluence. Definition 2.2.10 (Orthogonality). A TRS R is orthogonal if it is left-linear and has no overlapping rules: for every two different rules l 1 → r 1 , l 2 → r 2 of R, there is no position p and substitution σ : X → T (Σ, X ) such that l 1 | p = l 2 σ. In other words, each rule application is independent.

Any orthogonal TRS is confluent, which in turns means it is deterministic. The study of orthogonal rewriting systems plays a great role in the study of functional programming languages. The term rewriting system extracted from a purely functional program is indeed orthogonal since each rule describes the application of an independent function on independent reduced values. This is not the case however for non-purely functional programs introducing side effects or "non-deterministic functions" such as a random number generator.

Usage in Functional Program Verification

We now explore in more details the definition and properties of TRSs encoding first-order and higher-order functional programs, and how it can be used to formalize program verification problems.

First-Order

A first-order functional program can be encoded using a first-order functional TRS defined as follows.

Definition 2.2.11 (First-Order Functional TRS). A TRS R is a first-order functional TRS iff it is a left-linear TRS defined over a ranked alphabet Σ = C ∪ F and each rule is of the form

f (p 1 , . . . , p n ) → r where f ∈ F n , p i ∈ T (C, X ) for each i ∈ [1, n] and r ∈ T (Σ, X ).
In this definition, F is a ranked alphabet of function symbols where the arity of the symbol is the arity of the corresponding function, while C is a ranked alphabet of constructor symbols building values. Each rule of a first-order functional TRS defines the application of a function represented by a function symbol on its value parameters.

Example 2.2.1 (From OCaml to first-order functional TRS). Consider the following OCaml program defining the insertion-sort algorithm on lists: This program can be translated into a first-order functional TRS as follows:

sort(nil) → nil sort(cons(x, l)) → insert(x, sort(l)) insert(x, nil) → cons(x, nil)
insert(x, cons(y, l)) → ite(leq(x, y), cons(x, cons(y, l)), cons(y, insert(x, l)))

ite(true, x, y) → x leq(0, y) → true ite(false, x, y) → y leq(s(x), 0) → false leq(s(x), s(y)) → leq(x, y)

The symbols ite and leq are introduced to encode the if-then-else control structure and built-in comparison operator <= (here on natural numbers only). The associated rules on those symbols are not extracted from the program but from the OCaml language specification.

Definition 2.2.12 (Values). Let R be a first-order functional TRS defined over Σ = C ∪ F. The set T (C) engendered by the constructor symbols alphabet C is called the set (or language) of values. It represents all the possible data values manipulated by the program. Note that no rule can apply on a value alone:

T (C) ⊆ IRR(R).
Definition 2.2.13 (Determinism). Because of the constraints applied on the rules, a first-order functional TRS is deterministic iff it is orthogonal, in which case it is also confluent as we have previously seen.

Definition 2.2.14 (Completeness). A first-order functional TRS R is complete iff IRR(R) = T (C): every non-value can be rewritten. In other words, every function of the program is exhaustive.

Completeness is a strong property that almost never applies to functional programs.

To ensure that the execution never stops before reaching a value, most functional languages provide a weaker completeness property on a subset of terms: well-typed terms, which notion usually depends on the type system employed by the language.

In our case, all we are interested in is completeness, we thus derive well-typedness from completeness.

Definition 2.2.15 (Well-Typed Terms). A term is well typed w.r.t. a TRS R if its normal forms (if any) are values. We write W(Σ) R the set of terms that are well-typed w.r.t. R, or just W(Σ) when there are no ambiguities on R. If R is defined over Σ = C ∪ F we have:

W(Σ) R = { s | s → ! R t ⇒ t ∈ T (C) }
Note that diverging terms that have no normal forms are well-typed.

Higher-Order

To encode higher-order functional programs we must be able to partially apply functions. However using our previous encoding for first-order programs, this leads to the apparition of malformed terms.

Example 2.2.2. Let us consider the following OCaml program defining the higherorder map function:

let rec map f = function | [] -> [] | x::l -> (f x)::(map f l)
One may be tempted to translate this program into the following TRS:

map(f, nil) → nil map(f, cons(x, l)) → cons(f(x), map(f, l))
However this is not a valid TRS. Since f is a variable we should have ar(f) = 0, it is hence impossible to write f(x).

This problem is solved by Reynolds [START_REF] Reynolds | Automatic computation of data set definitions[END_REF] by introducing a special application symbol @, where @(f, x) encodes the (partial) application of a function f on its first parameter x. Of course, applications can be stacked to fully apply a function: @(@(@(f, x), y), z).

Example 2.2.3. In the previous example, the map program can be translated into the following (valid) TRS using @: @(@(map, f), nil) → nil @(@(map, f), cons(x, l)) → cons(@(f, x), @(@(map, f), l))

This time every use of x is valid by encoding f(x) into @(f, x).

This leads to a more sophisticated definition for higher-order functional TRSs.

Definition 2.2.16 (Higher-Order Functional TRS). A TRS R is a higher-order functional TRS over a ranked alphabet Σ = C ∪ F if it is a left-linear TRS defined over Σ ∪ { @ } and such that for all rule l → r there exists some k > 0 such that l ∈ LHS k . The set LHS k is inductively defined for all k as the smallest set such that:

f (t 1 , . . . , t n ) ∈ LHS 0 ⇐ f ∈ F n , t 1 , . . . , t n ∈ T (C, X ) @(t 1 , t 2 ) ∈ LHS k+1 ⇐ t 1 ∈ LHS k , t 2 ∈ T (C, X )
The arity of a function symbolized by f , written Ar(f ) is defined as follows:

Ar(f ) = n ⇐⇒ ∀ @(@(. . . @(f, p 1 ), . . . ), p i ) → r ∈ R. i = n
Note that in general the arity of a symbol differs from the arity of the function it represents, Ar(f ) = ar(f ). For all symbols f appearing in a higher-order functional TRS, Ar(f ) must always be defined.

We still make the distinction between functional and constructor symbols, however the set of values is extended to include partially applied functions. In fact, any irreducible term can be considered as a valid value, which complicates the definition of completeness for higher-order functional TRSs. Definition 2.2.17 (Completeness). A higher-order functional TRS R defined over Σ = C ∪ F is complete iff for all functional symbol f ∈ F, for all terms t 1 , . . . , t n where n = Ar(f ) the term @(@(. . . @(f, t 1 ), . . . ), t i ) is reducible.

Higher-order functional TRS are often filled with @ symbols, which can make them hard to read. For the sake of readability in this document we use a lighter notation for higher-order functional TRSs where @(t 1 , t 2 ) is simply written t 1 t 2 .

Example 2.2.4 (Higher-order notation). The higher-order functional TRS defined in the previous example can be simplified into the following:

map f nil → nil map f cons(x, l) → cons(f x, map f l)
Remember that this is still a first-order term rewriting system as the special application symbol @ is used underneath.

Tree Languages, Grammars and Automata

A set of term is called a tree language. Tree languages can be possibly infinite, but even then can have a finite presentation: we can describe the whole set in a finite manner. The preferred representation for string language is through grammars. We hereby describe its extension to tree languages: tree grammars. Most of the definitions of this section can be found in Tree Automata Techniques and Applications [Tata].

Definition 2.3.1 (Tree Grammar). A tree grammar is a quadruple Σ, N, S, ∆ where Σ is a ranked alphabet of symbols called terminals, N a ranked alphabet of symbols called non-terminals (disjoint from Σ) with S ∈ N a special starting non-terminal such that ar(S) = 0. Finally, ∆ is a set of production rules, a term rewriting system defined over Σ ∪ N .

A term t is produced by a grammar G if there exists a rewriting path of the form S → * ∆ t, from the starting non-terminal S to the term. The language produced by G is written L(G) and defined as { t ∈ T (Σ) | S → * ∆ t }. Tree grammars are basically term rewriting systems producing the terms of the described language from a single starting symbol. The family of languages that can be produced using a tree grammar is called "recursively enumerable" tree languages. Such tree language can be recognized using a Turing machine. Recursively enumerable languages are closed under union, intersection and complement.

Example 2.3.1. The output of a functional program is always a recursively enumerable language (which also explains why a Turing machine is necessary to recognize such language). For instance consider again the TRS R defined in the previous Example 2.2.1 and extracted from the OCaml insert-sort program. We can describe the output of this program as a tree grammar G = Σ, N, S, ∆ where ∆ is the union of R with the following TRS:

S → sort(L) L → nil L → cons(N, L) N → 0 N → s(N )
The additional rules produce all the initial states of the program (in our case, it is any call to the sort function with any list). We can the show that

L(G) = R * ({ sort(L) | L a list }).
The outputs can be isolated by intersection with IRR(R).

Just as with string languages, we distinguish multiple families of tree languages by the complexity of the term rewriting system needed to define the grammar producing them.

Regular Tree Languages

The first and simplest family of tree languages are called "regular" tree languages. It is defined as the class of languages that can be produced by regular tree grammars. Definition 2.3.2 (Regular Tree Grammar). A regular tree grammar G is a tree grammar Σ, N, S, ∆ where for all non-terminal A ∈ N , ar(A) = 0 and each rule of ∆ is of the form A → β where A is a non-terminal and β a tree of T (Σ ∪ N ).

Example 2.3.2. Regular tree grammars are often found in modern functional programming languages as a mean to describe algebraic data types. For instance, consider the following OCaml type definition for lists of colors:

type color = Red | Green | Blue type list = | Nil | Cons of color * list
This in fact directly encodes the following tree grammar G = Σ, list, {color, list}, ∆ where Σ = { Red : 0, Green : 0, Blue : 0, N il : 0, Cons : 2} and ∆: → Cons(Red, Cons(Green, Cons(Blue, N il)))

color → Red list → N il color → Green list → Cons(color, list) color → Blue
Note that contrarily to the OCaml definition, we generally prefer to use lowercase for symbols (red, green, blue, nil, cons) and capitals for non-terminals (Color, List).

Regular tree grammar are more closely related to context-free string grammars than regular string grammars. In particular, if we note Y ield(L) the string language defined as { Y ield(t) | t ∈ L }, then Y ield(L) can be generated using a context-free string grammar, but may not by a regular string grammar (it is a context-free string language). One can see regular tree languages as a sub-family of well-parenthesized context-free string languages. Regular languages are closed under union (L 1 ∪ L 2 ), intersection (L 1 ∩ L 2 ) and complement (L).

Bottom-Up Tree Automata

Bottom-up tree automata are another structure for representing regular tree languages. Just as we can make a direct correspondence between regular string grammar defining regular string languages and nondeterministic finite string automata recognizing them, we can make a direct correspondence between regular tree grammars and nondeterministic finite tree automata. While tree grammars can define regular languages, tree automata are more suited to answer membership queries.

Definition 2.3.3 (Bottom-Up Tree Automaton). A (bottom-up) tree automaton A is a quadruplet Σ, Q, Q f , ∆ where Σ is a ranked alphabet of symbols, Q a set of states, Q f ⊆ Q a set of final states and ∆ a set of transitions (a rewriting system) of the form p → q where q ∈ Q is a state and p a pattern, called a configuration, of T (Σ, Q).

A term t is recognized by a tree automaton A in state q if there exists a rewriting path of the form t → * ∆ q. We write L(A, q) the language recognized by A in q: { t | t → * ∆ q }. We say that A is reduced if it has no state q such that L(A, q) = ∅. A term t is recognized by a tree automaton A if it is recognized by a final state. We write L(A) the language recognized by A. It is a regular tree language. To simplify, we write → A instead of → ∆ when the components of A are not explicitly defined.

A regular tree grammar can easily be translated into a tree automaton by using non-terminal as states, the starting non-terminal as final state and by inverting the direction of each production rule and use them as transitions.

Example 2.3.3. The regular tree grammar G defined in the previous example can be translated into the following tree automaton A = Σ, Q, Q f , ∆ where ∆ is defined as red → q color nil → q list green → q color cons(q list , q color ) → q list blue → q color with q list a final state in Q f . We have L(A) = L(G). For instance, the term cons(red, cons(green, cons(blue, nil))) is recognized by A (into the final state q list ) because of the following rewriting path in ∆:

cons(red, cons(green, cons(blue, nil))) → cons(red, cons(green, cons(blue, q list ))) → cons(red, cons(green, cons(q color , q list ))) → cons(red, cons(green, q list )) → cons(red, cons(q color , q list )) → cons(red, q list ) → cons(q color , q list ) →

q list
Using tree automata instead of tree grammars offers several technical advantages. For instance, a tree automaton can have a deterministic transition system while still representing an infinite language, which is not the case with regular tree grammars. Definition 2.3.4 (Determinism). We say that a tree automaton A = Σ, Q, Q f , Σ is deterministic if its transition system ∆ is deterministic: for all terms t ∈ T (Σ, Q), there is at most one state q ∈ Q such that t → * ∆ q. Definition 2.3.5 ( -Transitions). Let A = Σ, Q, Q f , ∆ a tree automaton. An -transition of A is a transition of the form q → q in ∆ where q and q are two states of Q. We write t → , * ∆ q when there is a ∆-rewriting path from t to q using only -transitions. Conversely, we write t → £ , * ∆ q when there is a ∆-rewriting path from t to q using no -transitions.

We say that A is -free if it has no -transitions. We say that A is ¡ -deterministic if for all terms t ∈ T (Σ, Q) there is at most one state q ∈ Q such that t → £ , * ∆ q. We use the abbreviation "REFD" for reduced, -free deterministic tree automata. Definition 2.3.6 (Normalized Transitions). Let A be a tree automaton. A transition f (p 1 , . . . , p n ) → q of A is normalized if p 1 , . . . , p n are states. We say that A is normalized if all its transitions are normalized.

It is always possible to normalize a tree automaton by adding new states recognizing sub-terms of non-normalized configuration. For this reason we often suppose that the automata considered in the proofs are normalized since it is easier to reason about normalized transitions.

Beyond Regularity

This thesis focus on the use regular languages to verify regular properties over higherorder functional programs. In order to understand the contour of regular problems (cf. Definition 2.4.6) this section discusses the limits of regular languages and what lies beyond them.

Non-Regular Languages

We have seen how regular languages are closely related to the algebraic data type definitions that can be found in modern functional programming languages. In addition, terms found in regular languages are sufficient to represent any state of a functional program execution. Combined with the apparent simplicity of the structure representing regular languages (grammars and automata), this motivates our interest in regular languages applied to program verification. However we can already discuss what regular languages are not good for: relations. Most infinite tree languages expressing relations between sub-terms of the same tree are not regular. This is particularly true for inductive relations. The simplest example of such inductive relation is the equality relation between trees.

Example 2.3.4. Consider the following TRS R defining the equality operator eq over natural numbers: eq(0, 0) → true eq(0, s(y)) → f alse eq(s(x), s(y)) → eq(x, y) eq(s(x), 0) → f alse

The tree language recognizing all and only terms of the form eq(s, t) rewriting to true is not regular. It is not possible to build a tree automaton recognizing such language. This is because rewriting steps at the core of tree automata and regular tree grammars are essentially local operation, whereas the equality decision is essentially a global operation. For the same reason, the comparison operators ≤ and ≥ cannot be captured by regular languages.

The direct consequence of this example is that any property whose proof relies on such relation is out of the scope of the techniques presented in Chapters 4 and 5. However an extension dealing with such relation will be discussed in Chapter 6. Note that comparison predicates defined over a finite domain are not inductive relations and can be represented using a regular language.

Example 2.3.5. Replacing natural numbers with a finite domain, such as { a, b } is sometimes sufficient to transform a non-regular problem into a regular one. With this domain, the equality predicate is no longer inductive and can be defined with a simple cases enumeration: eq(a, a) → true eq(a, b) → f alse eq(b, b) → true eq(b, a) → f alse Also note that in many cases, even when such relations are involved, they are not required in the proof. A regular approximation of the relation may be sufficient to verify the property.

Example 2.3.6. Consider the following TRS R:

f (x) → not(or(eq(0, x), eq(s(0), x)))

or(f alse, f alse) → f alse or(true, f alse) → true or(f alse, true) → true or(true, true) → true not(true) → f alse not(f alse) → true Assume we want to verify that for all n > 1, f (n) rewrites to true. This property involves eq, however it does not require to compute exactly the problematic irregular language { eq(s, t) | eq(s, t) → * R true }. This is because eq is only used in two special cases where one of the compared value is known. Instead, it is sufficient to compute { eq(0, t) | eq(0, t) → * R true } and { eq(s(0), t) | eq(s(0), t) → * R true } that are both regular.

Hierarchy of languages

In Three models for the description of language [Cho56], Chomsky define a hierarchy of string languages. It is possible to transpose this hierarchy to tree languages as pictured on Figure 2.1. We have already seen two member of this hierarchy: Recursively enumerable tree languages can be produced by non constrained tree grammars and recognized by Turing machines, while regular tree languages can be produced by regular tree grammars and recognized by tree automata. Between them we can distinguish the family of context-free tree languages that are recognized by context-free tree grammars.

Recursively enumerable

Context-free

Definition 2.3.7 (Context-Free Tree Grammar). A context-free tree grammar G is a grammar Σ, N, S, ∆ where each production rule in ∆ is of the form A(x 1 , . . . , x n ) → t where A is a non-terminal of arity n, x 1 , . . . , x n are variables of X and t a pattern of T (Σ ∪ N, X ).

The difference with regular grammars lies in the possibility for non-terminals to be defined with variables. Context-free tree grammars are strictly more powerful than regular grammars and can recognize non regular languages. However it is not yet powerful enough for instance to recognize the irregular languages { eq(s, t) | eq(s, t) → * R true } seen in the previous examples.

Automated Verification and Abstraction

In order to prove or disprove a given property on a program, automated verification techniques generally rely on the automatic abstraction of a representation of the program execution. The representation in itself and the abstraction method depend on the considered technique. For higher-order functional program, we have seen that representing the program execution using term rewriting systems and tree languages offer several advantages, but many other representations have been successfully used over the years in various techniques. To offer a point of comparison, in this section we chose to present the common principles underlying automated verification techniques from the point of view of model checking, which is general enough to be easily related to all the verification techniques presented in Chapter 3.

Formalization as Model Checking

Model Checking is a high-level program verification framework introduced in 1982 by Clarke and Emerson [START_REF] Clarke | Design and synthesis of synchronization skeletons using branching time temporal logic[END_REF], where a program execution, modeled as a directed graph1 , is searched for elaborate patterns in order to check its compliance to a given specification. All the techniques presented in Chapter 3 can be seen as some sort of Model Checking. This point of view is useful to compare the expressive power of each technique, but first requires a proper introduction to the theoretical instruments used in Model Checking.

Model

Formally, if P is a set of propositions, a model M is defined as triple S, →, V (called a Kripke structure) where S is a set of states, → the transition relation between the states (the program's logic) and V a function mapping each proposition to the set of states for which the proposition is true.

Example 2.4.1. In our case we consider programs represented as term rewriting systems. If R is a TRS defined over a ranked alphabet Σ and I ⊆ T (Σ) a set of initial terms, this can be viewed as a model R * (I), → R , V where an execution state is a reachable term (the expression currently evaluated), and the transition relation is the rewriting relation w.r.t. R. For instance, consider the following TRS R representing the even and odd predicates:

even 0 → true odd 0 → false even s(n) → odd n odd s(n) → even n
Using the initial language I = {even n | n ∈ N} it encodes the following (infinite) model R * (I), → R , V :

. . . / / odd s(0) / / even 0 / / true . . . / / even s(0) / / odd 0 / / f alse In order to perform any kind of verification on a model, one must first express the property to be checked against the execution graph. In Model Checking, this is traditionally done using modal logics, such as CTL * [EH83] an extension of Computational Tree Logic [START_REF] Clarke | Design and synthesis of synchronization skeletons using branching time temporal logic[END_REF], or Modal µ-Calculus [START_REF] Scott | A theory of programs[END_REF].

Modal Logics

Modal logics extend the propositional logic that can only be used to produce assertions about a single execution state by introducing modalities that can be used to produce assertions about whole execution paths. As an example, we explore in more details here the Modal µ-Calculus [SdB69] (L µ ) that will be useful later in Section 3.2.

If P is the set of propositions, and X a set of variables, the set F of valid modal µ-calculus formula is given by the smallest set including

x | p | ¬φ | φ ∧ ψ | φ ∨ ψ | φ | φ | νx. φ | µx. φ
where x is a variable of X , p a proposition of P and φ and ψ are formula. For a given model M and variable environment σ mapping a variable to a set of states, the semantics [[φ]] σ of a formula φ gives the set of states in which the formula is verified:

[[x]] σ = σ(x) [[φ ∧ ψ]] σ = [[φ]] σ ∩ [[ψ]] σ [[¬φ]] σ = S/[[φ]] σ [[p]] σ = V (p) [[φ ∨ ψ]] σ = [[φ]] σ ∪ [[ψ]] σ
The formula φ (and its dual φ) holds in any state such that φ holds in every (resp. some) of its successors:

[[ φ]] = { q ∈ S | ∀q . q → q ⇒ q ∈ [[φ]] } [[ φ]] = { q ∈ S | ∃q . q → q ⇒ q ∈ [[φ]] }
The formula νx. φ (and its dual µx. φ) gives the greatest (resp. least) fixed point of

[[φ]] σ[x →T ]
. It holds in any set of states T such that when x is bound to T , φ holds for T :

[[νx. φ]] = { T ⊆ S | T ⊆ [[φ]] σ[x →T ] } greatest fixed point [[µx. φ]] = { T ⊆ S | [[φ]] σ[x →T ] ⊆ T } least fixed point
Example 2.4.2. Consider the model R * (I), → R , V given in the previous example above representing the execution of the even/odd program. We want to show that for every even number 2k, (even 2k) never returns false. Note that in the previous we haven't explicitly defined V along with the set of propositions P . To verify this property, we now consider P with the only proposition F alse that only holds for the state false: V (F alse) = { false }. Our property can now be verified by checking that every term of the form (even 2k) is not contained in the semantics of the following formula: From the different existing families of problems, the verification techniques developed in this thesis all focus on the family of safety problems.

φ = µx. F alse ∨ x "f
Definition 2.4.2 (Safety Property and Problem). A formula φ describe a safety property if it can be stated as ¬(µx. ψ ∨ x)

A problem P = M, φ, I is a safety problem if φ describes a safety property. One can see ψ as a formula capturing "bad" states. A safety problem then consists in verifying that a bad state can never be reached from an initial state.

In our settings it is useful to restate this definition using term rewriting systems and tree languages.

Definition 2.4.3 (TRS Safety Problem). Let R be a term rewriting system and I, O two tree languages. We note R, I, O the safety problem over R that consists in verifying the following property: R * (I) ⊆ O (every reachable term is safely contained in O).

A TRS safety problem R, I, O can be expressed as a standard safety problem M, φ, I where M = R * (I), → R , V and φ = ¬(µx. unsafe ∨ x) with unsafe the proposition such that V (unsafe) = O.

Verification via Abstraction

In general, for a given model S, →, V the set of states S is infinite which makes the program's model checking difficult. This is the case in the previous example. Instead the common approach is to first abstract the model into a simplified model S , → , V depending on the property of interest that is easier to analyze. Definition 2.4.4 (Program Abstraction). Let P a program modeled by S, →, V . A program abstraction is defined by an abstraction relation ⊆ S × S and a (abstract) model S , → , V such that for all t ∈ S, t ∈ S such that t t then V (t) ⊆ V (t ) and if there exists u ∈ S such that t → u then there exists u ∈ S such that t → u with u u :

t u t u
We also say that defines a simulation of S, →, V in S , → , V .

This new model holds an abstraction of the program execution, where each node of the graph represent one or more possible execution state, and each edge a possible transition between those abstracted states. The first abstraction essentially abstracts every reachable state into its type, bool. Since the type of a term is in principle preserved by execution, this effectively collapses execution sequences into one single state. Even if the control flow is lost during the transformation, we will see later that the result can still be used to verify safety properties. On the other side of the spectrum, the third abstraction makes the distinction between even and odd numbers. It can be used to verify the same property as in Example 2.4.2 using the same modal µ-calculus formula but on a finite model instead.

This example shows how program abstraction is a critical step in program verification. The range of properties verifiable by the technique depends on which ways a given verification technique can abstract a program.

State Abstraction: Regular and Relational Properties

As we have seen in Example 2.4.1, when a higher-order functional program is represented as term rewriting system, a state of the execution is a term representing the currently evaluated expression. This means that for a given model S, →, V abstracted into S , → , V , each abstract state q ∈ S in the abstraction defines a tree language L(q) = {t | t q} that can be represented by a tree grammar. Hence, verification techniques can be compared against the family of grammars the states can be abstracted into. This can be done using the Chomsky Hierarchy [START_REF] Chomsky | Three models for the description of language[END_REF] comparing the expressiveness of different classes of grammars. Can the verification technique generate regular grammar abstractions? Context free grammars? etc. In this document we mainly consider two classes of grammars determining the class of properties verifiable by a verification technique.

Regular Problems

Definition 2.4.5 (Regular Abstraction). An abstraction into S , → , V is regular when for every abstract state q ∈ S , L(q) is regular.

In a regular abstraction, each state can be stored as a regular tree grammar, or tree automaton. This allows the verification of a specific family of problems: regular problems.

Definition 2.4.6 (Regular Problem). A regular problem is a problem that can be solved using a regular abstraction of the program. Conversely, non-regular problems cannot be solved without the need of at least context-free grammars abstractions.

In this thesis we are particularly interested in the verification of the combination of both safety and regular problems using term rewriting systems.

Definition 2.4.7 (TRS Regular Safety Problem). Following Definitions 2.4.3 and 2.4.6, a TRS regular safety problem R, I, O (or just regular safety problem for short) is a safety problem that can be solved using a regular abstraction of the program. In our setting this means that I, O are regular languages such that R * (I) ⊆ O. In other words, a regular safety problem can be solved by either finding a regular over-approximation L of R * (I) such that

R * (I) ⊆ L ⊆ O
or by finding a counter-example term of R * (I) outside of O. Since I and O are regular languages, we sometimes replace them by tree automata.

Relational Properties Incidentally, we will see in the next chapter that all the automatic verification techniques that can solve non-regular problems are generally not good at verifying regular problems (they are unable to generate arbitrary regular abstractions). These techniques can capture relations between the values, but not the regular structure of those values. In this case, we use the term "relational properties" to qualify the range of properties verifiable by these techniques.

Another useful abstraction family to consider is functional abstractions.

Definition 2.4.8 (Functional Abstractions). An abstraction into S , → , V is functional when is a function. This means that each state is abstracted into (at most) a unique abstract state. All states of S are disjoint.

Control Flow Abstraction

The way the program is abstracted can also affect what kind of logic can be discerned by the verification technique. For instance we have seen that when states are abstracted by their types, execution paths collapse and the control flow information disappears. Only safety properties can be verified against the resulting model. From a modal µ-calculus point of view, it means that every formula φ is equivalent to a simpler formula φ expressed in propositional logic (without nor ):

[[φ]] σ = [[φ ]] σ .
Hence, the modal logic cannot be discerned from the non-modal propositional logic, which narrows the range of problems the technique can solve. We name this family of abstractions "collapsing abstractions". Definition 2.4.9 (Collapsing Abstraction). Let S, →, V be an execution model. An abstraction of S, →, V into S , → , V is said to be collapsing when → is empty. This implies that for all t, u ∈ S and t , if t t and t → u then u t . The language L(t ) is closed w.r.t. →. As a consequence, the resulting abstraction can only be used to verify safety properties.

When

is a collapsing abstraction into S , → , V with P (the set of propositions) empty, we often write that is an abstraction into S as both → and V are known to be empty.

Completeness of Verification Techniques

A verification technique is complete w.r.t. to a family of abstractions if it can automatically build any abstraction of this family. For instance, a "regularly complete" verification technique can automatically solve any regular verification problem by generating any necessary regular abstraction of the program.

In the next chapter we explore different automated verification techniques that can be compared against the family of abstractions they can build (regular, relational, etc.) and against the family of logic they can discern (propositional, modal, etc.).

Chapter 3

State of the Art

This chapter summarizes the state of the art of the automatic verification of higherorder functional programs. The techniques presented in this chapter sacrifice expressiveness in favor of automation: the range of verifiable properties is restricted, but the user only needs to state the desired property and is not required to annotate the program or to participate in the proof in any ways. The common approach between these technique consists in finding a finite abstraction (or at least an abstraction with a finite representation) that over-approximates the execution of the program, and then solve the verification problem on the resulting finite abstract model. Techniques are then distinguished by the family of abstractions they are able to automatically generate. One is generally exclusively well suited to solve either relational problems on numerical values using non-regular abstractions, or regular problems on algebraic data types using regular abstractions. At the same time one may allow the user to verify temporal properties over the program, while others are restricted to safety properties.

Static Type Systems

The use of type annotations statically checked by a type checker can be seen as the most basic kind and most widely used program verification method that does not require program execution. The initial motivation behind the introduction of type systems and type annotations is to ensure the "well-behaveness" of the program execution, i.e. ensure that no execution path leads to a state not covered by the program's semantics: "well-typed programs cannot go wrong" [START_REF] Milner | A theory of type polymorphism in programming[END_REF]. In practice, the goal is to ensure that at each execution step, the current state of the memory matches the expected memory layout. In this context, a type represents the memory layout of a data value, shared by all the values of the same type. Rapidly the interest grew over the possibility to express more and more complex types describing more than just a memory layout in order to verify more and more complex properties, not only to ensure well-behaveness but also correctness.

Intersection and Set Theoretic Types

One way to extend simple type systems is to allow one term to have multiple types at once. This idea was first proposed by Coppo and Dezani-Ciancaglini [START_REF] Coppo | A new type assignment for λ-terms[END_REF][START_REF] Coppo | Functional characterization of some semantic equalities inside λ-calculus[END_REF] in 1978 as a way to assign a type to λ-terms that would normally be impossible to type in simply typed λ-calculus (for instance, W * = λx. (x x)). The result of this study is a static type system in which terms such that W * are not assigned a single 43 type, but are necessarily given multiple types (potentially infinitely many). The idea was then carried on [Rey91, CD80, CDV81, Hin82], giving rise to Intersection Types [BCD83, DCM84, RV84, Roc88], where the type of a term is given by the conjunction (denoted with ∩ or ∧) of multiple other types. Intersection Types have been found useful not only with λ-calculi to capture the terminating properties of terms, but also as an interesting programming language feature [START_REF] Reynolds | Design of the programming language forsythe[END_REF] introducing ad-hoc polymorphism. For instance, where classical ML type systems require two different addition operators for integers and floats (+ of signature int -> int -> int and +. of signature float -> float -> float in OCaml), Intersection Types can be used to define a single + operator whose signature would be

(int -> int -> int) ∩ (float -> float -> float)
From there, intersection types have been extended with all the usual set operations (union, complement, difference, etc.) giving rise to Set Theoretic Types [HVP00, HP01, HP03, BCF03, FCB02, CPN16] with their own inference algorithm [CNX + 14]. However, even if they can be used to describe more complex types, Set Theoretic Types cannot be used to express any regular language or to express relations between the values.

Dependent and Refinement Types

Focusing on theses issues, Refinement Types have been developed on top of the extensively studied foundations of Dependent Types [Nec97, ADLO10, FP91, [START_REF] Mckinna | Why dependent types matter[END_REF][START_REF] Ou | Dynamic typing with dependent types[END_REF] to express relational and structural properties over a program. They have already been implemented in a number of programming languages such as Agda [START_REF] Norell | Dependently Typed Programming in Agda[END_REF], Cayenne [START_REF] Augustsson | Cayenne -a language with dependent types[END_REF], Coq [START_REF]The coq proof assistant[END_REF], F* [MI13], Idris [START_REF] Brady | Idris, a general-purpose dependently typed programming language: Design and implementation[END_REF], etc. In their original form, Refinement Types allow the introduction of predicates in the types, and dependencies between the variables. It has been primarily used to remove runtime array bound checking by encoding the range of integer variables in their type [START_REF] Xi | Eliminating array bound checking through dependent types[END_REF]. For instance, if we consider the following nth function that returns the n th element of a given list. It is possible to statically enforce that n is in bounds by specifying the following signature for nth: a:int list -> { n: n >= 0 ∧ n < length a }:int -> int Refinements types are specified as the conjunction of qualifiers applied to the subject variable. In the previous example, the two qualifiers n >= 0 and n < length are used (where can be replaced by any variable). Note that the occurrence of >= and length in the qualifier does not refer to the actual >= and length functions, but have their own semantics defined by the refinement type checker: the language of type qualifier is disjoint form the programing language. In 2008, the development of Liquid Types [START_REF] Rondon | Liquid types[END_REF][START_REF] Vazou | Liquidhaskell: experience with refinement types in the real world[END_REF] allow for the relatively automatic inference of refinement types. The inference procedure is able to compose refinement types using a combination of qualifiers picked from a set of predefined qualifiers given by the user, or scrapped from (selected by looking at) the input program. It is further extended in [START_REF] Vazou | Abstract refinement types[END_REF][START_REF] Vazou | Bounded refinement types[END_REF] to allow higher-order refinements, with the presence of predicate variables. In our previous nth example, it can be used to transfer a property verified by every element of the list to the returned element:

∀(p : int -> bool). ({ e: p(e) } int) list -> int -> { e: p(e) } int By internally manipulating predicate variables as uninterpreted function symbols, this 3.1. STATIC TYPE SYSTEMS extension does not require the use of higher-order logics hence preserving decidability of the checking procedure. In parallel, efforts are made to capture structural properties over algebraic data types using Recursive Refinements [START_REF] Kawaguchi | Type-based data structure verification[END_REF] which allows the application of qualifiers on the subterms of an inductive type. In addition to the usual notation Using this pseudo-Caml syntax, it becomes clear that this type is defined as a sum (written |) of products (written * ) between int and (recursively) ilist. Then the following recursive refinement can be used to denote a list whose first element is positive: { _ | v, _: v >= 0 } int list. The first _ is the product refinement for the nil constructor, the first term of the sum (product refinements of each term are given in the order of the type definition). It is an empty product so we write _ to ignore it. the variable v refers to the first component of the cons constructor. It must be positive. Finally the last _ refers to the tail of the list which we ignore. This principle can be used to design more complex recursive types, for instance the type of sorted integer lists:

type sorted_ilist = nil | cons of v:int * { _ | w, _: w >= v } sorted_ilist)
Note however that Recursive Refinements are only able to capture "local" recursive relations. They are not capable to capture the global shape of a value, that is to describe any regular language. For instance, it is impossible to describe a list whose first part is exclusively composed of even numbers, and second part of odd numbers.

To summarize, we have seen that the different flavors of refinement types allow the expression of various properties on program by encoding them in the type signatures of the functions. However refinement types suffers from several limitations. First, some properties that cannot be expressed with dependent types are out of the scope of the previously detailed Refinement Types techniques. For instance let's consider the following find function taking a predicate p and integer i as parameters, and returning the smallest integer after i satisfying p. We are not interested in p, but want to prove that the returned integer is necessarily greater or equal to i. This can be expressed with the following signature: let find :: p:( int -> bool ) -> i:int -> { r: r >= i }:int = if p i then i else find p (i + 1)

Now instead of returning the value directly, we reformulate find so as to take a continuation k as a parameter, and call the continuation when the value is found:

let find :: p:( int -> bool ) -> k:(int -> 'a) -> i:int -> 'a = if p i then k i else find p (i + 1)
This time however, because k occurs before i in the signature, there is no way to specify that the integer passed to the continuation is greater that i. This problem is tackled in [START_REF] Vazou | Bounded refinement types[END_REF] by introducing Bounded Refinement Types, adding a new layer of complexity to the type system while not covering every cases. Moreover, while well-suited to the verification of relational properties over integers, refinement types can hardly be used to verify structural properties over algebraic data types which compose the most part of regular problems. Finally, contrarily to simple type systems and even set theoretic types for which there exists well known inference algorithms [Hin69,Mil78,CNX + 14], the inference of refinement types is undecidable in general. This requires the user to annotate the program to help the type checker, which may jeopardize the readability of the source code and require expertize. The type inference mechanism introduced by Liquid Types [START_REF] Rondon | Liquid types[END_REF] alleviating the number of needed manual annotations is only able to compose refinement types using a combination qualifiers that must be given by the user, or scrapped from the program. Knowing what qualifiers are necessary to infer the refinement type requires expertize while not completely removing the need for annotations.

Deep Specification

The lack of expressiveness of Refinement Types comes from the constrained shape of the predicates (qualifiers) allowed in the refinements. In particular, the language of qualifiers is disjoint from the programming language. This ensure the decidability of the checking procedure at the cost of reducing the range of verifiable specifications. Some language implementation using dependent types such as Dafny [START_REF] Leino | Dafny: An automatic program verifier for functional correctness[END_REF], F* [MI13] or Halo [START_REF] Vytiniotis | HALO: haskell to logic through denotational semantics[END_REF] have been described as "deep" specification and verification languages in [START_REF] Vazou | Liquid Haskell: Haskell as a Theorem Prover[END_REF], as opposed to the "shallow" specification of Refinement Types. In these languages, user defined functions can be embedded in the types, extending the range of specification. For instance, the "sortedness" property of a sorting function can be specified by first defining a sorted predicate in the language itself, and using it in the signature of the sorting function: However powerful, this comes at the cost of losing most of the type inference capabilities, and in particular the inference of auxiliary function invariants. For instance in F*, the verification of the insertion sort algorithm is not possible using only the preceding specification. For the type checker to successfully verify the given signature of sort, one must also give the signature of the intermediate function insert, exposing the necessary invariant (insertion preserves sorting): Moreover in addition to losing automation, the type checking procedure becomes undecidable in general. The later limitation is solved by Vazou et al. [START_REF] Vazou | Liquid Haskell: Haskell as a Theorem Prover[END_REF] using Refinement Reflection [START_REF] Vazou | Refinement reflection (or, how to turn your favorite language into a proof assistant using SMT)[END_REF][START_REF] Vazou | Refinement reflection (or, how to turn your favorite language into a proof assistant using SMT)[END_REF] to import deep specifications into Haskell. The idea is to use uninterpreted function symbols to encode user defined functions, preserving a decidable verification procedure. In addition, the authors provide a set of function combinators that can be used to compose proof in the programing language itself, effectively turning Haskell into a theorem prover. The drawback is the lost of automation compared to Liquid Haskell, and a more convoluted proof development mechanism compared to domain specific languages such as F* or Coq.

Higher-Order Trees Model Checking

Higher-Order Tree Model Checking is a family of automatic verification techniques concerned with the Model Checking of (possibly infinite) trees representing a program execution. These trees are generated by Higher-Order Recursion Schemes (HORS), a kind of higher-order typed tree generating grammar [START_REF] Ong | On model-checking trees generated by higher-order recursion schemes[END_REF]. Contrarily to static type systems seen in the previous section, this technique can preserve the control flow of the program and discern modal logics.

Higher Order Recursion Schemes

According to Ong06, a (deterministic) HORS G is a tuple Σ, N , S, R where Σ is a ranked alphabet of terminal symbols, N is a finite set of typed non-terminals, S ∈ N is a distinguished start symbol of type o and R is a finite set of rewriting rules for each non-terminal of the form

F x 1 . . . x n → e
where F is a non-terminal of type τ 1 → . . . → τ n → o, each x i is a variable of type τ i , and t is a pattern built from terminals, non-terminal and variables. The type system distinguish trees (of type o) from functions (whose types include →). The order of a HORS is given by the highest order of its non-terminal, where the order of a non-terminal symbol is given by the order of its type:

order(o) = 0 order(τ 1 → τ 2 ) = max(order(τ 1 ) + 1, order(τ 2 ))
Trees are generated by successively rewriting non-terminal symbols following the rules of R, starting with S. For instance, let's consider the following order-1 HORS taken from [START_REF] Kobayashi | Types and higher-order recursion schemes for verification of higher-order programs[END_REF] ))) → . . . . If we ignore the br terminal symbol, the generated tree pictured on the side includes all the paths of the form a n b n c. Overall, HORSs can be viewed as a kind of simply typed lambda calculus with recursion and tree constructors (but no destructor), where any lambda term (λx. t) can be modeled by adding the (lambda-lifted) rule F x → t to the HORS. They are hence well suited to model higher-order programs. Note however that, contrarily to term rewriting systems, the lack of tree destructor means that HORSs do not provide any pattern matching mechanism as in most modern higher-order functional programing language, complicating the translation to HORSs. For instance, this implies that the simplest data values such as booleans must be encoded using functions, following the Church encoding.The HORS encoding the if-then-else control structure becomes:

S → F c F x → br x (a (F (b x)))
IF c x y → c x y T x y → x F x y → y
In his paper [START_REF] Ong | On model-checking trees generated by higher-order recursion schemes[END_REF], Ong proves that, considering finite base types, the modal µ-calculus model-checking of trees generated by HORSs is decidable, turning them into an appealing candidate for program verification. Despite the k-EXPTIME theoretical complexity for order-k HORSs, multiple practical HORS model checker implementations [BK13, Kob09a, RNO14, TK14] have been developed that do not always suffer from the k-EXPTIME bottleneck. The use of HORS applied to higher-order functional program verification started in 2009, when Kobayashi [Kob09b] introduced a novel method for verifying resource usage problems in higher-order functional programs. His technique consists in a program transformation to HORS that preserves relevant control flow features relative to the resource usage verification. For instance, let's consider the following program taken from the original paper [START_REF] Kobayashi | Types and higher-order recursion schemes for verification of higher-order programs[END_REF]:

let rec g x = if _ then close(x) else (read(x); g(x)) in let d = open_in "foo" in g(d)
This program opens a file "foo" and manipulates it in the function g, either by reading it recursively, or closing it. We want to verify that the file is always closed, and that no further reading occurs after closure.

G x k → br (close k) (read (G x k)) S → G d •
This HORS is very similar to the input programs where the rule g is represented by the non-terminal G, and br a non-deterministic branching (the exact branching condition is lost during the translation). The program is now in Continuation-Passing Style (CPS): the nonterminal G takes an extra k parameter, a continuation, necessary to encode sequences of instructions in a purely functional manner. The • terminal is the final continuation, denoting the end of the program. The tree generated by this HORS, visible on the side, summarizes all the possible sequences of read and close that can happen during the program execution. Using existing HORS model checkers, the output tree can be automatically checked against the following two modal µ-calculus formula expressing our verification problem:

µx. close ∨ x
the file is eventually closed

¬(close ∧ (µx. read ∨ x))
no read occurs after a close Although useful to verify such resource usage problems, this first endeavor into HORS-based program verification shows the limitation of directly translating programs into HORSs. Since the model-checking of HORS-generated trees is only decidable with finite base types, this technique alone is not capable of dealing with infinite data types such as integers, lists or trees. As always, the program must first be abstracted into a finite base types program before being verified.

Predicate Abstraction

Building on his work on HORSs [Kob09b, Kob09a, KTU10], Kobayashi [START_REF] Kobayashi | Predicate abstraction and CEGAR for higher-order model checking[END_REF] and his coauthors define in 2011 an effective way of abstracting functional programs into finite base types HORSs, using Predicate Abstraction. In this setting, the input program is first abstracted into a higher-order boolean program where each (numerical) value v is abstracted into its predicates satisfaction. For a set P 1 , . . . , P n of predicates, v is replaced by b 1 , . . . , b n (where b i is the result of P i (v)). We directly note b instead of b 1 when considering a single predicate. For instance, let's consider the following program taken from [START_REF] Kobayashi | Predicate abstraction and CEGAR for higher-order model checking[END_REF]:

let f x g = g (x+1) in let h y = assert (y > 0) in let k n = if n > 0 then f n h else () in k(randi())
The assert instruction fails if its parameter is f alse and the function randi returns a pseudo-random integer value. We want to verify that the program never fails (assert false is never evaluated). Using the predicate P (x) = x > 0, the following boolean program is generated:

let f x g = g true in let h y = assert y in let k y = if y then f true h else () in k(randb())
All the integer variables have been replaced by their boolean satisfaction of P . Note how randi has been replaced by randb, generating pseudo-random booleans values.

As with the technique presented in the previous section 3.2.1, the obtained boolean program is then lambda-lifted and put in CPS form to translate it into an HORS.

The later is finally model checked (using the TRecS [Kob13] model checker in the original paper) to verify that assert never fails. Here we have succeeded with P (x) = x > 0, however in general it is not obvious what predicates should be used to verify a given property. Hence the main difficulty here is to find the correct abstracting predicates. To do that, Kobayashi et al. defines a Counter-Example Guided Abstraction Refinement (CEGAR) procedure. The idea is as follows: starting with no predicates, at each iteration of the procedure the program is abstracted and verified. If a spurious counter example is found, a new predicate is added to the abstraction extracted from the spurious rewriting path. The procedure continues adding new predicates until a real counter example is found, or the property is verified. The entire procedure, later improved for scalability [START_REF] Sato | Modular verification of higher-order functional programs[END_REF], has been implemented in MoCHi [START_REF] Kobayashi | Mochi: Model checker for higher-order programs[END_REF], and been used to successfully and automatically verify a number of relational properties over higher-order functional program manipulating numerical values. However it is not well suited to solve regular verification problems over tree processing programs. This is the purpose of the techniques presented in the next section.

Regular Verification

As we have seen using our model-checking reference point in Section 2.4, the range of properties verifiable by a technique depends on the range of abstraction it can generate. The techniques presented in the previous sections all focus on the automatic verification of relational properties. This means they can generate various abstractions of the program states, even though it is no clear what exact family of abstraction it can generate. The regular abstraction family on the other hand is holding much attention when it comes to modern higher-order functional programs. Because regular tree languages can be easily represented using grammars or automata there is hope to find an efficient procedure that can generate arbitrary regular abstractions automatically. And since algebraic data types define regular tree languages of values, regular abstractions are particularly useful to verify tree processing programs. In this section we explore techniques focusing on the generation of arbitrary regular abstractions of a program, aiming at providing an automatic and complete verification of regular problems.

HORS Extensions

Since the beginning of HORS-based program verification, HORS have been criticized [START_REF] Ong | Verifying higher-order functional programs with pattern-matching algebraic data types[END_REF][START_REF] Kobayashi | Higher-order multi-parameter tree transducers and recursion schemes for program verification[END_REF] for their intrinsic inability to represent tree processing program, blaming the lack of tree destructor in this formalism. Algebraic data types can be constructed but never destructed through mechanisms similar to pattern matching, present in most modern functional programing languages. Early after Ong's work on HORSs [START_REF] Ong | On model-checking trees generated by higher-order recursion schemes[END_REF], extensions are proposed to introduce tree destruction mechanisms. On one side, Kobayashi introduces Higher-Order Multi-Parameter Tree Transducers [START_REF] Kobayashi | Higher-order multi-parameter tree transducers and recursion schemes for program verification[END_REF], an extension of HORS where the right hand side of each rule can contain a pattern matching term of the form:

match x      case 1 ⇒ t 1 . . . case n ⇒ t n
where each case i is a pattern of the form a y 1 . . . y m where a is a terminal symbol of arity m and each y i a variable. This term reduces to one of t i when x matches the pattern case i . The main limitation of HMTTs is the distinction made between input trees and output trees: Where HORSs define only one base type o representing trees (composable with → to denote functions), HMTTs define two base types i for input trees and o for output trees. In this definition, each rule of a HMTT takes input trees, and reduces to an output tree. Only input tree may be destructed with a match, and only output trees may be constructed. For instance, let's consider the following HMTT composed of two rules concatenating two lists of as and bs together (to simplify we allow the use of terminals instead of variables in the matching patterns):

App x y → match x      nil ⇒ Copy y cons a x ⇒ cons a (App x y ) cons b x ⇒ cons b (App x y ) Copy x → match x      nil ⇒ nil cons a x ⇒ cons a (Copy x ) cons b x ⇒ cons b (Copy x )
The non-terminal symbol App defines the append function. The non-terminal Copy performs the copy of an input tree (of type i) to an output tree (of type o). This function is necessary because inputs and outputs cannot be mixed up in HMTTs. This also explains why we need to explicitly distinguish the two cases for a and b in App instead of just having the case cons v x . The (almost) automatic translation from functional programs to HMTT is explored in [START_REF] Unno | Verification of tree-processing programs via higher-order model checking[END_REF]. Under some linearity conditions, and given correct abstractions for the input trees x and y in the nonterminal App it is then possible to reduce the problem to the verification of a finite base types HORS. Here the abstractions of input variables are defined as tree automata recognizing all the possible input terms. However no procedure is given to automatically find the correct abstractions needed to verify a given property on the program.

On the other side, Ong and Ramsay introduce Pattern-Matching Recursion Schemes (PMRS) [START_REF] Ong | Verifying higher-order functional programs with pattern-matching algebraic data types[END_REF] another extension of HORSs providing a pattern matching mechanism. This time, patterns are allowed to appear on the left-hand side of a rewriting rule in place of the last rule variable. A PMRS rule is of the form:

F x 1 . . . x n-1 p → e
where p is a pattern again of the form a y 1 . . . y m where a is a terminal symbol of arity m. For a set of rules of the form F x 1 . . . x n-1 p k → e, the term F t 1 . . . t n reduces into e when t n matches p k . For instance, let's define again the append function, this time using a PMRS:

App x y → P re y x P re x nil → x P re x (cons y l) → cons y (P re x l)
Here we have two non-terminal symbols. App encodes the interface to the append function. However since a pattern can only occur at the last position of a left-hand side, append cannot be represented directly. Instead, we use the non-terminal P re to encode the prepend function. App defines the append function using P re by swapping x and y. This is necessary because of the restrictions imposed by this formalism. As such, the translation to PMRS is not as direct as the translation to HMTTs. However there is no need for a copy function here, as there is no distinction between inputs and outputs, and the overall size of the model is reduced. In addition, contrarily to HMTTs, in their paper [START_REF] Ong | Verifying higher-order functional programs with pattern-matching algebraic data types[END_REF] Ong and Ramsay propose a fully automatic mechanism to abstract higher-order tree-processing programs so as to verify a given property using PMRSs. However this abstraction mechanism is not complete. In this abstraction procedure, pattern matched variables are abstracted into regular grammars generated by new non-terminals symbols. For instance, let's consider the following F ilter function, filtering an input list of natural numbers according to a given predicate p.

Filter p nil → nil Filter p (cons x l) → Ite (p x) (cons x (Filter p l)) (Filter p l)
We omit the definition of Ite representing the usual if-then-else control structure. We want to verify that the output list of this PMRS never contains any odd number when considering the following start symbol definition:

S → Filter Even L N → 0 L → nil N → s N L → cons N L
The (non-deterministic) non-terminal L, combined with N defines all the possible input values of Filter (here we consider all the possible lists of natural numbers). We omit the definition of the non-terminal Even representing the predicate filtering even numbers. The PMRS abstraction procedure starts by abstracting all pattern-matched variables in the right-hand sides of each rules (p, x, l) by its associated non-terminal according to the starting rule. Here Filter is called with Even and L, so the variable p is abstracted by Even, l by L and x by N (by unfolding L). We get:

Filter p nil → nil (3.1) Filter p (cons x l) → Ite (Even N ) (cons N (Filter Even L)) (Filter Even L) (3.2)
A counter-example run is then searched in the abstraction. Here the following run reduces to a list containing an odd number:

S → Filter Even L → Filter Even (cons N L) → Ite (Even N ) (cons N (Filter Even L)) (Filter Even L) → Ite (Even 0) (cons N (Filter Even L)) (Filter Even L) → * (cons N (Filter Even L)) → * (cons N nil) → * (cons (s 0) nil)
This is spurious counter-example, since this run is not valid in the original PMRS: where we have reduced N into 0 and later into (s 0) in the abstraction hides in reality a single variable x in the original PMRS, which cannot hold two different values at once. Considering this spurious run, the abstraction is refined by unfolding the non-terminal N so as to not mix up 0 and (s 0) anymore. As a result, rule (3.2) is unfolded into:

Filter p (cons 0 l) → Ite (Even 0) (cons 0 (Filter Even L)) (Filter Even L) Filter p (cons (s x) l) → Ite (Even (s N )) (cons (s N ) (Filter Even L)) (Filter Even L)
The procedure continues searching for counter-examples and unfolding non-terminals until a real counter-example if found, or the property is verified. This particular example however shows that this procedure is unable to generate arbitrary regular abstractions, and may diverge even for regular properties. In our example, it will continue unfolding N into 0, (s 0), (s (s 0)), etc., missing the required separation between even and odd numbers.

Regular Tree Languages based Techniques

The interest over the fully automated verification of tree processing programs can be traced back to Jones [START_REF] Jones | Flow analysis and optimization of lisp-like structures[END_REF][START_REF] Jones | Flow analysis of lazy higher-order functional programs[END_REF], who first proposed to abstract functional programs using regular tree languages. In this setting, a higher-order functional program is modeled using a term rewriting system R, and the set of input states as a regular tree grammar G. For instance let us consider the following R (each rule is indexed for later use):

1 : even(0) → true 3 : odd(0) → false 2 : even(s(n)) → odd(n) 4 : odd(s(n)) → even(n)
with the following input grammar G (where we call even with even numbers):

S → even(E) E → 0 | s(O) O → s(E)
Jones and Andersen [START_REF] Jones | Flow analysis of lazy higher-order functional programs[END_REF] then define an algorithm that computes a new grammar G * R over-approximating the set of states (or terms) reachable from G using R:

R * (L(G)) ⊆ L(G * R )
The grammar G * R is computed by adding new derivations to G. For each rule a new non-terminal will be added that represents (an over-approximation of) the set of terms reachable from the rewriting rule. For each variable of the rule, a non-terminal is added that represents (an over-approximation of) the set of possible instantiations.

Example 3.3.1. We want to verify that for all even number n, even(n) never rewrite to f alse. To do that, let us build G * R . From the rule 1 the derivation R1 → true is added, where R1 is the non-terminal for this rule. From the rule 2 the derivation R2 → odd(R2 n ) is added where the non-terminal R2 n represents the possibles instantiations for the variable n in this rule. In the same way, from the rules 3 and 4 the derivations R3 → false and R4 → even(R4 n ) are added. To find the derivations rules of R2 n and R4 n we follow the initial grammar G. In the rule S → even(E), since we have E → 0 and E → s(O), the subterm even(E) can be rewritten using the rule 1 and 2. We hence add the derivations rules S → R1 and S → R2 to the grammar. For rule 2 this means that R2 n can be instantiated with the non-terminal O. We hence add the rule R2 n → O to the grammar. Now that we know something new about R2 n , we can consider the previously added rule R2 → odd(R2 n ). Again, since R2 n → O → s(E), the subterm odd(R2 n ) rewrites into even(E) with rule 4. We hence add the rule R2 → R4 and R4 n → E. By continuing this way, the final grammar G * R is defined by:

S → even(E) | R1 | R2 E → 0 | s(O) O → s(E) R1 → true R2 → odd(R2n) | R4 R2n → O R3 → f alse R4 → even(R4n) | R1 | R2 R4n → E
We can observe that in G * R , there are no possible paths from S to f alse. Since we have R * (L(G)) ⊆ L(G * R ) we can deduce that our property is verified.

The procedure that we have just illustrated continues until no new rules are added to the grammar. Since a finite amount of non-terminals is added to G * R , it is guaranteed to terminate. However the way the abstraction G * R is built is independent of the verification goal. For a given program, only one abstraction is possible. It is not at all guaranteed that this over-approximation will be enough to verify the input property.

Example 3.3.2. Let us consider the new (non-deterministic) TRS R derived from R with the additional rules

5 : ite(true, x, y) → x 7 : rand(n) → ite(even(n), n, rand(n)) 6 : ite(f alse, x, y) → y 8 : rand(n) → rand(s(n))
Here rand is a non-deterministic function that generates random (even) numbers.

Let us consider G defined as

S → rand(0)
We want to verify that rand always generate numbers that are even. Note however that this time, the initial grammar G does not naturally separate even number from odd numbers. Using Jones's algorithm we compute the grammar

G * R over-approximating R * (L(G )). From rule 7, G * R contains the derivation R7 → ite(even(R7 n ), R7 n , rand(R7 n )).
Because the variable n in this rule (represented by the non-terminal R7 n ) can be instantiated with an even number, we have R7 n → E. This means that even(R7 n ) can be derived into true, which in turn means that ite(even(R7 n ), R7 n , rand(R7 n )) can be derived into R7 n . We have R7 → * R7 n in G * R . However n can also be instantiated with an odd number: R7 n → O. Because of that, in G * R we have S → R7 → * R7 n → * s(0). The computed abstraction contains an odd number, which contradicts the property we want to verify. The over-approximation G * R is too rough.

This issue can be addressed by using the more recent Tree Automata Completion Algorithm [START_REF] Genet | Decidable approximations of sets of descendants and sets of normal forms[END_REF][START_REF] Genet | Equational approximations for tree automata completion[END_REF]. Just like Jones's algorithm, this algorithm's purpose is to compute the terms reachable from an input regular language, this time using tree automata instead of regular tree grammars. The previously considered grammar G can be translated into the following tree automaton A = {s, even, odd}, {q, q even , q odd }, {q}, ∆ with ∆ defined by: even(q even ) → q 0 → q even s(q even ) → q odd s(q odd ) → q even

In its simplest form, given a TRS R and tree automaton A, the completion algorithm is able to complete A using R into a new automaton A * recognizing exactly R * (L(A)).

Example 3.3.3. Similarly to Jones's algorithm, this algorithm works iteratively by finding missing transitions in the automaton. In A we have even(0) → ∆ even(q even ) → ∆ q. Since even(0) → R true, then true should be recognized by A * . The tree automata completion algorithm proceeds by adding new transitions in A * to have true → * ∆ * q. One way is to add the two transitions true → q true and q true → q (where q true is a new state). In A we also have even(s(q odd )) → * ∆ q. In the same way, since even(s(q odd )) → R odd(q odd ) we add the transitions odd(q odd ) → q and q → q to A * . Finally, because of the newly added transitions we have odd(s(q even )) → * ∆ q with odd(s(q even )) → R even(q even ). We already have even(q even ) → q so no new transitions are added. We have found every missing transitions and the completion algorithm stops. The output A * is defined by: even(q even ) → q 0 → q even q true → q s(q odd ) → q even q → q s(q even ) → q odd odd(q odd ) → q true → q true

One of the particularities of the Tree Automata Completion algorithm compared to Jones's algorithm is that this algorithm converges to a tree automaton A * recognizing exactly R * (L(A)) when L(A) is finite [START_REF] Genet | Equational approximations for tree automata completion[END_REF]. However contrarily to Jones's algorithm, the Tree Automata Completion algorithm is not guaranteed to converge when the considered TRS is not terminating.

Example 3.3.4. Let us consider again R defined in Example 3.3.2, with the initial automaton A defined by: rand(q 0 nat ) → q 0 rand 0 → q 0 nat where q 0 rand is the final state. It recognizes the same language as G from Example 3.3.2. Again we want to verify that rand generates even numbers, this time using the Tree Automata Completion algorithm. First, the algorithm detects that we have rand(q 0 nat ) → R rand(s(q 0 nat )) and rand(q 0 nat ) → R ite(even(q 0 nat ), q 0 nat , rand(q 0 nat )). New states and transitions are added to A * R to have rand(s(q 0 nat )) → * A * R q 0 rand and ite(even(q 0 nat ), q 0 nat , rand(q 0 nat ))

→ * A * R q 0 rand .
s(q 0 nat ) → q 1 nat even(q 0 nat ) → q 0 even rand(q 1 nat ) → q 1 rand ite(q 0 even , q 0 nat , q 0 rand ) → q 0 ite q 1 rand → q 0 rand q 0 ite → q 0 rand Note how q 1 nat now recognizes the term s(0) and q 1 rand the term rand(s(0)). In the next iteration, just like with q 0 nat and q 0 rand , because rand(q 1 nat ) → R rand(s(q 1 nat )) and rand(q 1 nat ) → R ite(even(q 1 nat ), q 1 nat , rand(q 1 nat )) the new state q 2 nat and q 2 rand are added, then q 3 and q 3 rand , and so on. In fact, for each integer i, the following transitions are added:

s(q i-1 nat ) → q i nat even(q i nat ) → q i even rand(q i nat ) → q i rand ite(q i even , q i nat , q i rand ) → q i ite q i rand → q i-1 rand q i ite → q i rand
The algorithm diverges.

As illustrated by this example, since R * (A) is uncomputable in general, the termination of the completion algorithm is not guaranteed. Multiple families of TRSs have been identified for which the termination is ensured [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. However in general, one must again rely upon an abstraction. In 2010 Genet et al. [START_REF] Genet | Equational approximations for tree automata completion[END_REF] proposes to use equations between patterns to merge terms in equivalence classes, effectively abstracting the program execution even when L(A) is finite.

Example 3.3.5. In the previous example, we can force the completion algorithm to terminate using the equation x = s(s(x)). During the completion process, it will detect that state q 0 nat recognizes 0 while state q 2 nat recognizes s(s(0)). Because 0 = s(s(0)) (according to our equation), states q 0 nat and q 2 nat are merged in A * R . As a consequence, the states recognizing even numbers are merged together, while all the states recognizing odd numbers are merged together. The infinite number of states collapses into a finite number of states, and the Tree Automata Completion algorithm terminates.

The equation mechanism allows the completion algorithm to terminate, but it also allows to precisely control the precision of the generated abstraction. For a given set of equations E, the language recognized by A * R follows the given inequality [START_REF] Genet | Termination criteria for tree automata completion[END_REF]:

R * (L(A)) ⊆ L(A * R ) ⊆ R/E * (L(A)) where R/E is the rewriting relation defined by {s → R/E t | s = E u → R v = E t}.
The completion algorithm terminates as long as the number of equivalence classes defined by E is finite [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. By carefully choosing the equivalence classes (E) one can control the shape of the generated abstraction to solve any regular safety verification problem. Some leads have be proposed to automatically find the correct equivalence classes needed to solve a given problem. So far, existing equations generation procedures are not complete and can only guarantee the termination of the completion algorithm for first-order programs. On the other hand Matsumoto et al. [START_REF] Matsumoto | Automata-based abstraction for automated verification of higher-order tree-processing programs[END_REF] define a procedure to infer equivalence classes directly represented with tree grammars, using a counter-example guided abstraction procedure. In this setting, the program is typed with tree automata, and the values abstracted into tree automata states. The tree automata types are then iteratively refined by looking for spurious counter-examples in the abstraction using a higher-order recursion scheme model checker. The refinement is performed by splitting the automata states following the information gathered from the counter-examples and using an SMT solver.

Example 3.3.6. Consider the following TRS:

even(0) → true even(s(0)) → f alse even(s(s(x))) → even(x) double(0) → 0 double(s(x)) → s(s(double(x)))
Note that in the original paper, authors are not working with rewriting systems but with their own representation of programs. This can be easily adapted to TRSs. Suppose we want to verify that the output of double is always even: the term even(double(n)) never rewrites to false for all natural number n. Using this technique, the program is first typed with tree automata and abstracted into a program manipulating states of these automata. Here the functions are given the following signatures:

even : A nat → A bool double : A nat → A nat
We start with the most general abstraction where A nat (on the left) and A bool (on the right) are defined as follows:

0 → q true → q s(q) → q false → q
The initial TRS is then abstracted into a new TRS manipulating the states of the automata as values: even(q) → q double(q) → q

There exists a rewriting sequence in the abstraction violating our property: even(q) → q where q recognizes 0 and q recognizes f alse. However this counter example is spurious since even(0) does not rewrite to false. In practice spurious counter example are found using a higher-order model checker. Then the two abstracting tree automata are refined so that this spurious counter example cannot occur in the next abstraction. This is done by splitting each tree automaton state q into {q 1 , . . . , q n } (where n is called the "split number") and building the transitions of these new states following constraints generated from the counter-example. Starting from 1, the split number is increased until a collection of tree automata is found that satisfies the generated constraints using an SMT solver. Here a solution exists for n = 2 where q is split into {q even , q odd } and q into {q true , q f alse } with the following transitions:

0 → q even true → q true s(q even ) → q odd false → q f alse s(q odd ) → q even

Minimizing the split number allows this technique to reach any regular abstraction. However because the split number affects every state, the generated abstraction may not be the smallest possible (in the number of states). Moreover a unique type is given to each function independently of the context which can again increase the number of states needed to abstract the function in a way that suits every context. This can become a problem considering that the number of states in the abstraction directly impacts the price of the SMT-constraints solving, especially since the described technique is not modular. In this thesis, we extend this series of work by going further using regular tree languages abstractions and rewriting systems. We explore the limits of the Tree Automata Completion algorithm (Chapter 4), design new ways of generating regular abstractions improving on Matsumoto et al. (Chapters 4 and 5), and define a modular verification technique for regular safety problems (Chapter 5). Finally we extend the reach of regular languages to the verification of relational properties (Chapter 6).

Chapter 4

Higher-Order Equational Abstractions

Since our goal is to verify tree-processing programs, we are mostly interested in the regular verification techniques presented in Section 3.3. In particular the Tree Automata Completion Algorithm (TAC) provides a flexible abstraction mechanism using equations, that can theoretically allow us to reach any regular abstraction of a program execution. However as of now, no automatic equations inference procedure has been formalized, and the only leads given by Genet [START_REF] Genet | Termination criteria for tree automata completion[END_REF] only apply to first-order programs. In this chapter we pursue the work of Genet at al. [START_REF] Genet | Equational approximations for tree automata completion[END_REF][START_REF] Genet | Termination criteria for tree automata completion[END_REF][START_REF] Genet | Completeness of tree automata completion[END_REF] and explore how the TAC algorithm can be used to automatically solve regular safety problems over higher-order functional programs.

Introduction

Using the TAC algorithm a program is represented as a term rewriting system R and the set of (possibly infinite) inputs to this program as a tree automaton A. The TAC algorithm computes (an over-approximation of) R * (L(A)), the set of reachable terms, as a new automaton A * R by completing A with the missing transitions. The automaton A * R effectively model the execution of the program, allowing us to solve regular safety problems (cf. Definitions 2.4.3 and 2.4.6) of the form R, L(A), O for some language O. Example 4.1.1. The following term rewriting system R defines the filter function along with the two predicates even and odd on Peano's natural numbers.

filter p nil → nil filter p cons(x, l) → ite(p x, cons(x, filter p l), filter p l)

even 0 → true odd 0 → false even s(n) → odd n odd s(n) → even n
The rewriting rules of ite representing the if -then-else control structure are omitted. This function filters out from the input list any element that does not satisfy the input predicate p. We want to check that for all lists l of natural numbers, (filter even l) filters out every odd numbers. One way to do this is to write a higher-order predicate, for _all , and check that for all list l, (for _all even (filter even l)) never rewrites to false. Let A be the tree automaton recognizing terms of form 59

(for _all even (filter even l)) where l is any list of natural numbers. We can verify this property using the TAC algorithm to compute

A * R . If false is not recognized by A * R , since R * (L(A)) ⊆ L(A * R )
we know in particular that for all list l, the input term does not rewrite to false.

Since R * (L(A)) is uncomputable in general, the TAC algorithm may diverge. Genet at al. [START_REF] Genet | Equational approximations for tree automata completion[END_REF] proposes to group every term into a finite set of equivalence classes using equations. These equations can force A * R to converge into an overapproximation of R * (L(A)). Depending on the set of equations used, any regular over-approximation can be reached, which means that any regular safety problem can theoretically be solved using this technique.

To transform the TAC algorithm into a fully automatic verification technique for regular safety problems, one must first develop a procedure that generates the correct equations set that gives the correct over-approximation of R * (I). In his paper [START_REF] Genet | Termination criteria for tree automata completion[END_REF], Genet draws the outline of a procedure able to quickly find such equations using "contracting equations". However using the generated equations, the termination of the TAC algorithm is only guaranteed in the case of first-order programs. In this chapter, we pursue the development of this equation generation procedure. We propose a solution to shortcomings mentioned above with the following contributions:

• We state and prove a general termination theorem for the Tree Automata Completion algorithm with contracting equations;

• From the conditions of the theorem we characterize a class of higher-order functional programs for which the completion algorithm terminates using contracting equations. This class covers common usage of higher-order features in functional programming languages.

• We discuss the completeness of the abstraction procedure based on contracting equations and propose a novel counter-example guided abstraction refinement (CEGAR) procedure which guaranties regular completeness and completeness in refutation.

The chapter is organized as follows: We start by giving a formal definition of the Tree Automata Completion Algorithm and its properties in Section 4.2. We state and prove a more general termination criterion for the TAC algorithm used with contracting equations in Section 4.3. We use this termination criterion to define a class of higherorder functional TRS on which there always exists a set of contracting equations that makes the TAC algorithm terminate in Section 4.4. We then define our novel abstraction procedure in Section 4.5. In Section 4.6, we present a series of experiments validating our verification technique. Section 4.7 concludes the chapter.

Tree Automata Completion Algorithm

In this section we give more details about the Tree Automata Completion (TAC) algorithm that computes, for a given TRS R and initial REFD tree automaton A 0 , an over-approximation of R * (L(A 0 )) as a new tree automaton A * . Remember that, as stated in Section 2.2, for any language L the set of reachable terms R * (L) is defined as

R * (L) = { t | ∃s ∈ L, s → * R t }.
If R represents a functional program and L(A 0 ) the set of initial states, then R * (L(A 0 )) includes all intermediate computations and, in particular, the outputs of the program. This can be used to verify regular safety properties on the program.

Core Algorithm

The TAC algorithm proceeds by iteratively computing new automata A 1 , A 2 , . . . such that A i+1 = C R (A i ) until it reaches a fixed point, A * . Here, C R (A i ) represents one step of completion and is performed by searching the missing transitions in A i necessary to recognize R * (L(A i )). To do that, the algorithm completes the critical pairs of A i , corresponding to R-rewriting steps that are not taken into account in the automaton. Definition 4.2.1 (Critical Pair). Let R be a TRS and A a tree automaton whose states live in Q. Given a rule l → r ∈ R a substitution σ ∈ X → Q and a state q ∈ Q. The pair rσ, q is critical when lσ → * A i q and rσ → * A i q.To avoid redundancy, in the rest of the document we note such critical pair l → r, q, σ which gives all the needed information. We name CP(R, A) the set of critical pairs in A w.r.t. R. Since Q and X are finite, so is CP(R, A).

A critical pair is completed by adding new transitions in the automaton to have rσ → * A i q as illustrated below:

lσ R / / * A i rσ q ⇒ lσ R / / A i+1 * rσ A i+1 * n n q
Example 4.2.1. Let Σ = {0 : 0, s : 1, add : 2}. Let us consider the following TRS R: add(0, y) → x add(s(x), y) → add(x, s(y))

Let A = Σ, Q, Q f , ∆ the REFD tree automaton recognizing add(s(0), s(0)) using the following transitions:

0 → q 0 nat s(q 0 nat ) → q 1 nat add(q 1 nat , q 1 nat ) → q 1,1 add
Because of the rule add(s(x), y) → add(x, s(y)) and the substitution σ = {x → q 0 nat , y → q 1 nat } the pair add(s(x), y)σ, q 1,1 add is critical because add(s(q 0 nat ), q 1 nat ) is recognized by q 1,1 add in A, but add(q 0 nat , s(q 1 nat )) is not. The pair can be completed by adding the necessary transitions to recognize add(q 0 nat , s(q 1 nat )) in q 1,1 add .

In our case, the completion algorithm completes critical pairs by first adding or reusing transitions in the automaton so to have rσ → £ * A i+1 q for some state q . It then adds the -transition q → A i+1 q symbolizing the rewriting step.

lσ R / / A i * rσ £ * A i+1 q q A i+1 o o
Note that the algorithm keeps the automaton normalized. As such, it may not directly add the transition rσ → q in A i+1 since rσ may not be a normal configuration (of the form f (q 1 , . . . , q n )). Instead the completion algorithms proceeds by using a normalization procedure to ensure that rσ is recognized in q by adding or reusing normal transitions.

Definition 4.2.2 (Normalization). Let A = Σ, Q, Q f , ∆ be a tree automaton. For a given pattern t ∈ T (Σ, Q), the normalization function N orm(Q, ∆, t) returns a triple Q , ∆ , q with Q ⊇ Q, ∆ ⊇ ∆ and q ∈ Q such that in the automaton A = Σ, Q , Q f , ∆ we have t → £ * ∆ q using normalized transitions and preserving ¡ -determinism. The normalization may add new states taken from an arbitrary infinite reserve of new states Q * . For any Q, ∆ and t, the normalization function is defined as follows:

N orm(Q, ∆, q) = Q, ∆, q N orm(Q, ∆, f (t 1 , . . . , t n )) = Q n ∪ { q }, ∆ n ∪ { f (q 1 , . . . , q n ) → q }, q
where for each

i ∈ [1..n], Q i , ∆ i , q i = N orm(Q i-1 , ∆ i-1 , t i ) with Q 0 = Q, ∆ 0 = ∆,
and where q = q if f (q 1 , . . .

q n ) → q ∈ ∆ n , or q ∈ Q * \ Q n (a new state) otherwise.
Example 4.2.2. In the previous example, we need to add the term add(q 0 nat , s(q 1 nat )) in A in order to complete the critical pair add(s(x), y) → add(x, s(y)), σ, q 1,1 add where σ = {x → q 0 nat , y → q 1 nat }. To do that we use the normalization function: N orm(Q, ∆, add(q 0 nat , s(q 1 nat ))). Transitions are added from bottom to top. By definition we start by computing N orm(Q, ∆, q 0 nat ) = Q, ∆, q 0 nat . We continue with N orm(Q, ∆,

q 1 nat ) = Q, ∆, q 1 nat . By definition N orm(Q, ∆, s(q 1 nat )) is Q ∪ {q 2 nat }, ∆ ∪ {s(q 1 nat ) → q 2 nat }, q 2 nat where q 2 nat is a new state recognizing s(s(0)) which was not recognized in A. Finally by definition, N orm(Q, ∆, add(q 0 nat , s(q 1 nat ))) is Q ∪ {q 2 nat } ∪ {q 0,2 add }, ∆ ∪ {s(q 1 nat ) → q 2 nat } ∪ {add(q 0 nat , q 2 nat ) → q 0,2 add }, q 0,2
add where q 0,2 add is a new state to recognize add(q 0 nat , s(q 1 nat )). Definition 4.2.3 (One Step Completion). Let A = Σ, Q, Q f , ∆ be a tree automaton, R be a left-linear TRS. The one step completed automaton is

C R (A) = Σ, Q , Q f , ∆ with Q , ∆ = Join(Q, ∆, CP(R, A))
where for any Q, ∆ and set of critical pairs S, Join(Q, ∆, S) is inductively defined by:

Join(Q, ∆, ∅) = Q, ∆ Join(Q, ∆, { l → r, q, σ } ∪ S) = Join(Q , ∆ ∪ {q → q}, S) with Q , ∆ , q = N orm(Q, ∆, rσ).
C R (A) = Σ, Q , Q f , ∆ with Q , ∆ = Join(Q, ∆, CP(R, A)). In our case CP(R, A) is the singleton { add(s(x), y) → add(x, s(y)), σ, q 1,1 add }. Then by def- inition we have Join(Q, ∆, CP(R, A)) = Q , ∆ ∪ {q → q 1,1 add } with Q , ∆ , q 0,2 add = N orm(Q, ∆, add(q 0 nat , s(q 1 nat ))
). We already have computed this in the previous example to get Q = Q∪{q 2 nat }∪{q 0,2 add } and ∆ = ∆∪{s(q 1 nat ) → q 2 nat }∪{add(q 0 nat , q 2 nat ) → q 0,2 add }. The overall difference between A and C R (A) can be summarized as follows:

add(q 1 nat , q 1 nat ) R / / A add(q 0 nat , q 2 nat ) C R (A) s(q 1 nat ) C R (A) q 1,1 add q 0,2 add C R (A) o o q 2 nat 4.2.

Properties of the TAC Algorithm

Lemma 1 (Soundness). All the definitions stated above ensure by construction that each completion step i, the following properties are verified:

L(A i ) ⊆ L(A i+1 ) and s ∈ L(A i ) ⇒ s → R t ⇒ t ∈ L(A i+1 )
This implies that, if a fixed point A * is found, then it recognizes an over-approximation of R * (L(A)).

In the final automaton A * produced by the TAC algorithm, -transitions are exclusively used to connect states that are connected by a rewriting relation. Two states q and q are connected with an -transition q → q iff rewriting rule l → r of the considered TRS R and a substitution σ such that lσ ∈ L(A * , q), rσ ∈ L(A * , q ). More precisely, -transitions represents rewriting step that occurs at the root of terms (position λ). A direct consequence of this particular structure of A * is that terms connected by rewriting steps that do not occur at the root are recognized by the same state in A * .

Lemma 2 (Subterm Collapse). Let u, v be two terms recognized by A * . If there exists a rule l → r a substitution σ and a position p ∈ P os(u) such that p = λ, u| p = lσ and v = u[rσ] p , then u and v are recognized by the same state in A * .

Proof. Let q be the state recognizing u in A * . Since A * is normalized, there exists a context C and a state q l such that u = C[lσ], lσ → * A * q l and C[q l ] → * A * q. Because lσ → R rσ and A * is a fixed point to the TAC algorithm, there exists a state q r such that rσ → * A * q r with q r → q l . So

v = C[rσ] → * A * C[q r ] → A * C[q l ] → * A * q, v
is also recognized by q.

If we transpose this lemma in the context of program verification, considering that the TAC algorithm computes an over-approximation of the reachable states of the program, this means that the TAC algorithm computes in fact an abstraction of the program execution where multiple execution states are recognized by the same tree automaton state. We call the particular abstraction realized by the TAC algorithm a subterm-collapsing abstraction. Definition 4.2.4 (Subterm-Collapsing Abstraction). Let Σ be a ranked alphabet. An abstraction of

T (Σ), →, V into Q, → , V is subterm-collapsing iff for all terms t = f (t 1 , . . . , t n ) of T (Σ) and u such that t i → * u for some i ∈ [1, n], then for all q ∈ Q such that t q we also have t[u] i q.
In addition, because A * is ¡ -deterministic, the TAC algorithm can only generate functional abstractions (cf. Definition 2.4.8).

Equations

It is in general impossible to compute a tree automaton recognizing exactly R * (L(A)). In most cases the TAC algorithm will compute an over-approximation of R * (L(A)) (depending on the shape of the initial automaton), or diverge. In their paper [START_REF] Genet | Equational approximations for tree automata completion[END_REF], Genet at al. propose to use equations to merge states during the completion algorithm, which can enforce the termination of the algorithm and control the precision of the approximation. From a program verification point of view, equations can precisely control how execution states are abstracted.

Example 4.2.4. This example shows how using equations can lead to approximations in tree automata. Let A be the tree automaton defined by the set of transitions ∆ = {0 → q 0 , s(q 0 ) → q 1 }. This automaton recognizes the two terms 0 in q 0 and s(0) (also known as 1) in q 1 . Let E = {s(x) = x} containing the equation that equates a number and its successor. For σ = {x → 0} we have s(x)σ → A q 1 , xσ → A q 0 and s(x)σ = E xσ. Then in the completed automaton, q 0 and q 1 are merged. The resulting automaton has transitions { 0 → q 0 , s(q 0 ) → q 0 }, which recognizes N in q 0 . Definition 4.2.5 (Equation Set). An equation set E is composed of equations of the form l = r where l, r ∈ T (Σ, X ). From E we derive the relation = E as the smallest congruence such that for all terms l, r and substitution σ we have:

l = r ∈ E ⇒ lσ = E rσ
The set of equivalence classes defined by

= E on T (Σ) is noted T (Σ)/ =E .
In this chapter we also write E for the TRS { l → r | l = r ∈ E }. The tree automata completion algorithm is altered so that at each completion step, the algorithm simplifies the automaton by merging states together according to E. Definition 4.2.6 (Simplification Relation). Let A = F, Q, Q f , ∆ be a tree automaton and E be a set of equations. If s = t ∈ E, σ : X → Q, q, q ∈ Q such that sσ → £ * A q, tσ → £ * A q and q = q then A can be simplified into A = A{q → q} (where q has been substituted by q), denoted by A E A .

We write S E (A) for the unique [START_REF] Genet | Equational approximations for tree automata completion[END_REF] automaton (up to renaming) A such that A * E A and A is irreducible by E . One completion step is now defined by

A i+1 = S E (C R (A i )). sσ E A i * tσ A i * q q ⇒ sσ E A i+1 * tσ A i+1 * n n q
With the simplification phase, equations can be used to enforce termination of the algorithm by over-approximating R * (L(A)) even in cases where it would normally diverge [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. Even better, equations can be used to control the precision of the over-approximation as long as the initial automaton does not recognize multiples equivalence classes of T (Σ)/ =E without epsilon transitions with the same state: it is

R/E-coherent [Gen16].
Definition 4.2.7 (Coherent Automaton). Let R be a term rewriting system, A a tree automaton and E a set of equations. The automaton A is said to be R/E-coherent iff for all state q ∈ Q, for all terms s, t ∈ L(A, q) we have:

s → £ * A q ∧ t → £ * A q ⇒ s = E t s → £ * A q ∧ t → * A q ⇒ s → * R/E t
where R/E is the rewriting relation defined as

{ s → R/E t | s = E u → v = E t}.
It is always possible to transform any tree automaton into a R/E-coherent automaton. Moreover, R/E-coherence is preserved by completion and simplification. For this reason in the rest of the chapter, we always assume the automata used in the TAC algorithm to be R/E-coherent.

Theorem 17 in Gen16 (Precision). Let R be a term rewriting system, E a set of equations and A an initial automaton. If the the Tree Automaton Completion algorithm on R, A and E converges into A * R , then the language recognized by A * verifies the given inequality:

R * (L(A)) ⊆ L(A * ) ⊆ R/E * (L(A))
Remember that because the output A * R is ¡ -deterministic, the TAC algorithm can only generate functional abstractions (cf. Definition 2.4.8). Thankfully with this theorem, Genet [START_REF] Genet | Completeness of tree automata completion[END_REF] showed that any regular over-approximation (abstraction) of R * (L(A)) can be reached, given the right equations.

Theorem 32 in Gen18 (Completeness). Let R be a left-linear term rewriting system and A an initial reduced -free automaton. Let L be a regular language such that R * (L(A)) ⊆ L. Then there exists a set of equations E such that L(A * R/E ) ⊆ L. A consequence of this theorem is that the Tree Automata Completion algorithm combined with equations can theoretically be used to solve any regular problem. The main difficulty of this technique becomes finding E that ensure both termination and verifiability of the considered property.

Contracting Equations

One naive way of finding equations E that ensure both termination and verifiability of the property would be to simply enumerate every possible set of equations. The enumeration must be fair so that if an equations set verifying the property exists, it will eventually be found. This is not practicable of course because the number of possibilities is too high. In his paper [START_REF] Genet | Termination criteria for tree automata completion[END_REF], Genet proposes a method to reduce the number of possibilities by considering only contracting equations over the constructor terms (or values). The set of equations is then completed with reflexive and R-transitive equations. Definition 4.2.8 (Contracting Equations). Let L ⊆ T (Σ). A set of equations is contracting for L, denoted by E c L , if all equations of E c L are of the form u = u| p with u a linear term of T (Σ, X ), p = λ and if the set of normal forms of L w.r.t the TRS

E c L = { u → u| p | u = u| p ∈ E c L } is finite. Example 4.2.5 (Contracting Equations). Assume that Σ = { 0 : 0, s : 1 }. The set E c L = { s(x) = x } is contracting for L = T (Σ) because the set of normal forms of T (Σ) with respect to E c L = { s(x) → x } is the (finite) set { 0 }. The set E c L = { s(s(x)) = x } is contracting because the normal forms of { s(s(x)) → x } are { 0, s(0) }.
Combined with reflexive equations, contracting equations can enforce termination of the TAC algorithm. Definition 4.2.9 (Reflexive Equations). Let Σ be a ranked alphabet. We name E r the set of reflexive equations defined as:

E r = { f (x 1 , . . . , x n ) = f (x 1 , . . . , x n ) | f ∈ Σ n }
Theorem 62 in Gen16 (Termination with Contracting Equations). Let R be a left-linear term rewriting system defined over T (Σ), A an initial automaton and E c T (Σ) a set of contracting equations for T (Σ). If E ⊇ E c T (Σ) ∪ E r then the Tree Automaton Completion algorithm terminates on R, A and E.

Contracting equations alone are not enough to significantly reduce the equations sets search space when it comes to solving a verification problem on a functional TRS. When the considered TRS R is a first-order functional TRS defined over Σ = F ∪ C, Genet proposes to focus on contracting the values manipulated by the program by defining contracting equations over T (C) instead of T (Σ). In this setting, the set of equations is then completed with R-transitive and reflexive equations to ensure the termination of the TAC algorithm. Definition 4.2.10 (R-Transitive Equations). Let R be a TRS. We name E R the set of R-transitive equations defined as: Example 4.2.6 (Contracting equations in action). This example is derived from Example 3.3.4 developed in Section 3.3.2. Consider the following first-order functional TRS R defining a non-deterministic rand function generating an even random number greater or equal to its input:

E R = { l = r | l → r ∈ R }
rand(n) → ite(even(n), n, rand(n)) rand(n) → rand(s(n))
The ite rules defining the standard if-then-else control structure are omitted but can be found in the original example. We want to show that even(rand(0)) cannot rewrite to false: false ∈ R * (I) with I = { even(rand(0)) }. Even if the input language is finite, this cannot be decided simply by evaluating the initial term because of the non-determinism of R. We hence use the TAC algorithm to compute an overapproximation of R * (I). The smallest initial automaton A recognizing I is defined by the following transitions: 0 → q 0 rand(q 0 ) → q 0 rand even(q 0 rand ) → q 0 even where q 0 even is the unique final state. As shown in the original example, without equations, the TAC algorithm will diverge while trying to generate all the possible transitions of the form s(q n-1 nat ) → q n nat even(q n nat ) → q n even rand(q n nat ) → q n rand ite(q n even , q n nat , q n rand ) → q n ite q n rand → q n-1 rand q n ite → q n rand for each number n of N. To solve this verification problem, another set of equations must be used. This is where contracting equations become handy. By using the contracting equations set E c T (C) = { s(s(0)) = 0 }, even and odd number will be merged into two separate equivalence classes, allowing the TAC algorithm to terminate with a more precise over-approximation of R * (I). By definition the full set of equations E used during the completion is

E = E c T (C) ∪ E R ∪ E r with E R ⊇ rand(n) = ite(even(n), n, rand(n)) rand(n) = rand(s(n))
Here the initial automaton A is already R/E-coherent. Using this initial automaton and equations, the TAC algorithm will merge every state of the form q 2k f in one equivalence class and all the states of the form q 2k+1 f in another class during the simplification phase:

s(s(0)) E A i * 0 A i * q 2 nat q 0 nat ⇒ s(s(0)) E A i+1 * 0 A i+1 * p p q 0 nat
The TAC algorithm will generate the following transitions when converging to A * : true → q 0 even s(q 1 nat ) → q 0 nat even(q 0 nat ) → q 0 even rand(q 0 nat ) → q 0 rand ite(q 0 even , q 0 nat , q 0 rand ) → q 0 ite q 0 rand → q 1 rand q 0 ite → q 0 rand false → q 1 even s(q 0 nat ) → q 1 nat even(q 1 nat ) → q 1 even rand(q 1 nat ) → q 1 rand ite(q 1 even , q 1 nat , q 1 rand ) → q 1 ite q 1 rand → q 0 rand q 1 ite → q 1 rand Note however that because of the set of R-transitive equations E R ⊆ E, states connected by epsilon transitions are also be merged during the completion. The actual transitions added into A * are:

true → q 0 even false → q 1 even s(q 1 nat ) → q 0 nat even(q 0 nat ) → q 0 even s(q 0 nat ) → q 1 nat even(q 1 nat ) → q 1
even rand(q 0 nat ) → q rand rand(q 1 nat ) → q rand ite(q 0 even , q 0 nat , q rand ) → q ite ite(q 1 even , q 1 nat , q rand ) → q ite q rand → q rand q ite → q rand Note how false is recognized by the automaton, but not by the final state q 0 even . The over-approximation is more precise and proves that false is not in R * (I), solving our verification problem.

Because of the equation set E R , the resulting abstraction is not only subtermcollapsing, but collapsing (cf. Definition 2.4.9). However this allows us to focus on E c T (C) which considerably reduce the search space. Termination is ensured when R is complete because every term eventually rewrites into a term of T (C) which is captured by E c T (C) . However this is not the case for higher-order functional TRSs where the special symbol @ is used to encode higher-order functions into first-order TRSs. This means that termination guaranties are lost on higher-order programs as we show in the following example.

Example 4.2.7 (Unbounded partial applications stack). Let Σ = F ∪ C be a ranked alphabet such that F = {f, g} and C = {a}. Consider the following higher-order functional TRS R defined by the rules:

f x → g (f x) g h y → h y
We want to an over-approximation of the R * (I) where I is the singleton { (f a) }. This TRS is not terminating but it is still possible to find a set of equations E such that the TAC algorithm terminates. The problem is that E cannot be of the form

E c T (C) ∪E R ∪E r where E c T (C)
is a set of contracting equations over C. Since C contains a single constant a, the only contracting equations set over C is E c T (C) = ∅. This is not enough for the TAC algorithm to terminate on this instance. To see why, we can use R to rewrite the initial term (f a) and get:

f a → g (f a) → g (g (f a)) → g (g (g (f a))) → . . .
Note how g is never fully applied here. Contracting equations are only defined over constructor symbols (a), but g and @ the hidden symbol encoding higher-order (cf. Definition 2.2.16) are not constructor symbols. This means that each term of the form g (. . . (g (f a))) is in its own unique equivalence class w.r.t. E. As a consequence, the TAC algorithm will diverge trying to add infinitely many states in the automaton to recognize these terms.

Of course one solution to solve this problem would be to extend the contracting equation set to contract partial applications. However this greatly expands the equations sets search space which makes it impracticable with current equations inference techniques. Moreover in practice, because partial applications are often part of total applications, contracting them often leads to many equations sets generating poorly precise abstractions of the control flow. In the next section we see how to preserve termination while keeping the equations search space small by defining a more general termination theorem of the TAC algorithm with contracting equations. We then define a general class of higher-order TRS for which the theorem applies with contracting equation over the values (T (C)).

Termination Criterion Using Contracting Equations

In the previous section we have seen how equations can be used to control the precision of the TAC algorithm. In particular how contracting equations on values can be used to reduce the search space when looking for an equations set able to verify (prove or disprove) a given (regular) property. Contracting equations have been proposed by Genet et al. [START_REF] Genet | Equational approximations for tree automata completion[END_REF], and successfully used to rather efficiently verify first-order programs. However as we have seen in the previous section, the termination of the completion algorithm with contracting equations on values has only be proved for first-order TRSs. In this section we prove a more general termination theorem (Theorem 1) for the TAC algorithm with contracting equations, that does not rely on the order of the considered TRSs. We show that termination of the completion algorithm with a set of equations E is ensured under the following conditions: if (i) for every step k of completion, A k is reduced and ¡ -deterministic (REFD); (ii) every term of A k can be rewritten into a term of a given language L ⊆ T (Σ) using R;

(iii) L has a finite number of equivalence classes w.r.t. E. Condition (i) is ensured by showing that, in our verification setting, completion preserves REFD. Condition (ii) is ensured for instance when R is terminating. To simplify, in the rest of the chapter we assume that R is terminating. The last condition is ensured by having E ⊇ E c L where E c L is a set of contracting equations over L (cf. Definition 4.2.8). The exact theorem is as follows:

Theorem 1. Let A be an REFD tree automaton, R a left-linear TRS, E a set of equations and L a language closed by subterms such that for all k ∈ N and for all

s ∈ L (A k ), there exists t ∈ L s.t. s → * R t. If E ⊇ E r ∪ E c L ∪ E R

The Role of Contracting Equations

To prove termination of the TAC algorithm, we first prove that it is possible to bound the number of states needed in A * to recognize a language L by the number of normal forms of L with respect to E c L (Lemma 4). In our case L will be the set of output terms of the program. Since A * does not only recognize the output terms, we need additional states to recognize intermediate computation terms. In the proof of Theorem 1 we show that with E R , the simplification steps will merge the states recognizing the intermediate computation with the states recognizing the outputs. If the latter set of states is finite then using Lemma 4 we can show that A * is finite.

This theorem only holds for REFD tree automata. Thus we first need to prove that completion preserves REFD. It is already known that completion preserves ¡ -determinism and ¡ -reduction [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. Considering that an ¡ -deterministic and ¡reduced automaton that is -free is REFD by definition, Lemma 3 proves preservation of -freeness. Lemma 3. Let A be an -free tree automaton and E a set of equations.

If E ⊇ E R then S E (C R (A)) is -free.
Proof. A is -free so for each critical pair l → r, σ, q we have lσ → £ A q. The resolution of the critical pair only adds -transitions of form q → q with q a new state [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. So, in C R (A), for every transition q → q such that q = q there exist s, t such that s →£ q, t →£ q and s → R t. Now, since E ⊇ E R , we have s = E t, and q and q are merged in the simplified automaton S E (C R (A)). The only -transitions that remain after the simplification steps are of the form q → q and can be removed without altering the recognized language.

The following Lemma 4 states that with an REFD tree-automaton A, the number of states needed in S E (A) to recognize a language L is bounded by the number of normal forms of L w.r.t. E c L .

Lemma 4. Let A be a REFD tree automaton, L a language and E a set of equations such that

E ⊇ E r ∪ E c L . Let G be the set of normal forms of L w.r.t. E c L . If G is finite then S E (A)
is a deterministic automaton such that terms of L are recognized by no more states than terms in G.

Proof. First we prove that for all terms s ∈ L, if we additionally have s → * S E (A) q then there exists t ∈ G such that t → * S E (A) q. We show this by induction on the structure of s. If s ∈ G then it is trivially true. If s is not in normal form w.r.t. E c L then there exists a subterm t of s such that s → E c L t. Since t is a subterm of s, there exists a state q such that t → * S E (A) q , and since s = E c L t it guarantees that q = q in the simplified automaton S E (A). By hypothesis of induction, there exists term t ∈ G of such that t → * S E (A) q. S E (A) being deterministic (Lemma 3), for all terms t ∈ G there is at most one state q such that t → * S E (A) q. Moreover, we have just seen that every state recognizing a term of L recognizes a term of G. Hence there are no more states in S E (A) than terms in G.

The Role of Transitive Equations

We now show that by adding transitive equations E R (and reflexive equations E r ), the termination of the TAC algorithm is ensured. In the previous section we showed that thanks to E c L each term of L is recognized by the same state as a term of G. Here we show that thanks to E R each term of A * is recognized by the same state as a term of L. This effectively show that the number of states in A * is bounded by |G|. This is a proof of the general termination theorem, Theorem 1.

Proof. For every index k and state q of A k , q satisfies at least one of the following properties:

• P 0 (q): There exists a transition f → q in the automaton A k , with f ∈ Σ 0

• P i+1 (q): There exists a transition f (q 1 , . . . , q n ) → q in the automaton A k such that P i (q 1 ) ∧ • • • ∧ P i (q n ).

If not, then L(A, q) = ∅ which contradicts the fact that A is reduced. We know there are a finite number of states recognizing terms of L (Lemma 4). Let Q be the set of those states. First we prove that for every symbol f and index k such that A k contains a transition f (q 1 , . . . , q n ) → q, with q 1 , . . . , q n ∈ Q, there exists an index k f such that for all k > k f , if A k contains f (q 1 , . . . , q n ) → q , then q ∈ Q. By hypothesis, for any term s such that s → * A k f (q 1 , . . . , q n ) → A k q, there exists a term t ∈ L such that s → l R t. After at most l successive completion steps, and because E ⊇ E R , we will have q merged with a state q f of Q. Moreover, having E ⊇ E r ensures that for every transition f (q 1 , . . . ,

q n ) → q ∈ A k such that k ≥ k + l we have q = q f . Let k = max{ k f | f ∈ Σ }.
We show, by induction on the property P , that for all k ≥ k, all states of A k are in Q. For every q such that P 0 (q), there exists a transition f → q with f ∈ Σ 0 . Since k > k f , we have shown earlier that q ∈ Q. Assume now that it holds for any state q and index i such that P i (q). For every q such that P i+1 (q), there exists a transition f (q 1 , . . . , q n ) → q with P i (q 1 ) ∧ • • • ∧ P i (q n ). By induction hypothesis, q 1 , . . . , q n ∈ Q. As already shown, this implies q ∈ Q. So, after at most k steps, all states of A k are merged into Q. Since Q is finite, and A k REFD for all k, the algorithm terminates.

A Class of Analyzable Programs

The next step is to identify a class of TRS and a language L for which Theorem 1 applies. One trivial possibility is to directly chose L = T (Σ) while providing a set of contracting equations E c T (Σ) . In this case, the termination theorem above proves that the completion algorithm terminates on any functional program R. If this works in theory, in practice we want to avoid introducing equations over the application symbol (such as @(x, y) = y). Contracting equations on applications makes sense in certain cases, e.g., with idempotent functions (@(sort, @(sort, x)) = @(sort, x)), but in most cases, such equations dramatically lower the precision of the completion algorithm. Hence, we want to identify a language L with no contracting equations over @ in E c L . Such a language L still has to have a finite number of normal forms w.r.t. E c L (required by Theorem 1). As a consequence it cannot include terms containing an unbounded stack of applications. For instance, L cannot contain all the terms of the form @(f, x), @(f, @(f, x)), @(f, @(f, @(f, x)), etc. The @ stack must be bounded, even if the applications symbols are interleaved with other symbols (e.g. @(f, s(@(f, s(@(f, s(x))))))). To do that:

1. we define a set B d of all terms where the size of such stacks of @s is bounded by d ∈ N;

2. we define a set K n (where n depends on the considered TRS) and a class of TRS called K-TRS such that for any TRS R in this class, K n is closed by R and K n ∩ IRR(R) ⊆ B k for some k. This is done by first introducing a type system over the terms;

3. finally we define L = B k ∩ IRR(R) that can be used to instantiate Theorem 1.

Bounded Applications Stacks

First, if we want to forbid equations over the @ symbol encoding applications, we need to control the maximum size of application stacks in IRR(R). That way when equations are applied we still have a finite number of equivalence classes (the abstraction is finite). In this perspective, we start by defining the set B d of terms where the size of each application stack is bounded by d.

Definition 4.4.1 (Bounded Applications Stacks). For a given alphabet Σ ∪ {@}, for any depth d ∈ N, B d is the smallest set defined by:

f (t 1 , . . . , t n ) ∈ B i ⇐ f ∈ Σ n ∧ t 1 . . . t n ∈ B i @(t 1 , t 2 ) ∈ B i+1 ⇐ t 1 , t 2 ∈ B i
Note that for any term t ∈ B i implies t ∈ B i+1 . This is because the first rule applies for any constant symbol f ∈ Σ 0 , for any i ∈ N.

In Section 4.5.1, we show how to produce a set of contracting equations over B d ∩ IRR(R), with a finite number of equivalence classes and no equations on @. To be able to instantiate the termination theorem with L = B d ∩ IRR(R), we still need to ensure that every term encountered during completion reduces into a term of B d . More formally, for all completion step k, for all term t ∈ L (A k ) we need to have a term s ∈ B d ∩ IRR(R) s.t. t → * R s. However such bound d does not exist in general: we can write programs that will generate unbounded stacks of partial applications (cf. Example 4.2.7). As a consequence, Theorem 1 cannot be directly instantiated with L = B d ∩ IRR(R), not for any TRS R. Instead we define (i) a set K n ⊆ T (Σ) and function φ such that K n ∩ IRR(R) ⊆ B φ(n) and (ii) a class of TRS, called K-TRS for which L (A k ) ⊆ K n . In K-TRS, the right hand sides of TRS rules are constrained to forbid the construction of unbounded partial applications during rewriting. If the initial automaton A 0 satisfies L (A 0 ) ⊆ K n then we can instantiate Theorem 1 with L = B φ(n) ∩ IRR(R) and prove termination.

Type System

Our goal is to make sure that when a term of K n is reduced with a K-TRS, then the size of partial application stacks is bounded by some φ(n). To do that we require terms in K n and K-TRS to be well-typed so applications are easier to keep track of and control. Our type system definition closely follows the one of [START_REF] Baader | Term Rewriting and All That[END_REF], where partial application are annotated with arrow types, which will allow us to control the construction and reduction of stack of applications. Definition 4.4.2 (Types). Let A be a non-empty set of base types. The set of types T is inductively defined as the least set containing A and all function types:

A ∈ T ⇐ A ∈ A τ 1 → τ 2 ∈ T ⇐ τ 1 , τ 2 ∈ T
The function type constructor → is assumed to be right-associative. The arity of a type τ is inductively defined on the structure of τ by:

ar(A) = 0 ⇐ A ∈ A ar(τ 1 → τ 2 ) = 1 + ar(τ 2 ) ⇐ τ 1 → τ 2 ∈ T
Definition 4.4.3 (Typed Alphabet and Symbol Signature). Remember that a ranked alphabet defines a set of symbols along with their arity. A typed alphabet Σ defines a set of symbols along with their type. We write f : (τ 1 , . . . , τ n ) → τ ∈ Σ for the symbol f of arity n whose type signature is (τ 1 , . . . , τ n ) → τ . A single symbol f can have multiple signatures in Σ but a single arity.

Intuitively for a term t of the form f (t 1 , . . . , t n ), τ i give the type of each subterm t i while τ is the type of t. For higher-order encoding TRSs, the special symbol @ has multiple signatures of the form (τ 1 → τ 2 , τ 1 ) → τ 2 to encode every possible typed application.

Definition 4.4.4 (Type Environment). For a set of variable X , a type environment Γ : X → T associates each variable with its type.

Definition 4.4.5 (Well typed terms). Let Σ be a typed alphabet with X a set of variables and Γ a type environment. We write Σ, Γ t : τ when the term t ∈ T (Σ, X ) can by typed with τ using Σ and Γ. The type judgment is inductively defined by:

cons f : (τ 1 , . . . , τ n ) → τ ∈ Σ Σ, Γ t i : τ i Σ, Γ f (t 1 , . . . , t n ) : τ var Γ(x) = τ Σ, Γ x : τ
We name W Γ (Σ, X ) the set of all well-typed terms defined by

{ t | t ∈ T (Σ, X ) ∧ ∃τ ∈ T . Σ, Γ t : τ }
Note that for higher-order encoding TRSs, the function application is just a special case of the cons rule with the symbol @:

cons @ : (τ 1 → τ 2 , τ 1 ) → τ 2 ∈ Σ Σ, Γ t 1 : τ 1 → τ 2 Σ, Γ t 2 : τ 1 Σ, Γ @(t 1 , t 2 ) : τ 2
To simplify, when there are no ambiguities about which Σ and Γ are considered, we directly write t : τ instead of Σ, Γ t : τ , and W(Σ, X ) instead of W Γ (Σ, X ). We also write W(Σ) for well-typed closed terms.

Definition 4.4.6 (Typed Rewriting Systems). A term rewriting system R is (well) typed w.r.t. a type environment Γ if for all rule l → r ∈ R, there exists a single type τ such that l : τ and r : τ . We then often write l : τ → r : τ . A typed functional TRS is complete iff every well typed term reduces to a value (a term of T (C)).

In the same way, an equation s = t is well typed if both s and t have the same type.

In the rest of this chapter we only consider well typed equations and TRSs.

Types provide information about how a term can be rewritten. For instance we expect every term of the form @(f : τ 1 → τ 2 , t : τ 1 ) : τ 2 to be rewritten into a value by every complete (no partial function) TRS R when ar(τ 1 → τ 2 ) = 1 and τ 2 ∈ A Furthermore, for certain types, we can guarantee the absence of partial applications in the result of a computation using the type's order. Definition 4.4.7 (Order). For a given typed alphabet Σ = C ∪ F ∪ {@}, the function ord : T → N gives the order of each type. It is inductively defined as the unique function that gives the minimum order for each type such that:

ord(τ ∈ A ) = max{ max{ ord(τ 1 ), . . . , ord(τ n ) } | f : τ 1 → • • • → τ n → τ ∈ C n } ord(τ 1 → τ 2 ) = max{ ord(τ 1 ) + 1, ord(τ 2 ) }
At first glance, it may seem strange that for τ ∈ A , ord(τ ) depends on the constructor symbols. Notice that constructor symbols are used to define the algebraic structure of base types. In particular, it is not unusual in modern functional programming languages to see algebraic data types embedding functions in their constructors. The order of those functions must contribute to the order of the type.

Example 4.4.1. Consider the two base types list and list . The type list defines lists of int (corresponding to the int list type in OCaml) with the constructor:

cons : int → list → list ∈ C
The second type list defines lists of functions (corresponding to the (int -> int) list type in OCaml) with the constructor:

cons : (int → int) → list → list ∈ C
The importance of looking at the signatures of cons in the definition of ord(list) and ord(list ) is manifest here: In the first case a fully reduced term of type list cannot contain any partial application (no @ symbol in the term) whereas in the second case it can. It is reflected by the order of the types: ord(list) = max{ord(int), ord(list)} = 0 and ord(list ) = max{ord(int → int), ord(list )} = 1.

Lemma 5. If R is a complete functional TRS and τ a type such that ord(τ ) = 0, then all closed terms t of type τ are rewritten into an irreducible term with no partial applications:

∀s ∈ IRR(R), t → * R s ⇒ s ∈ B 0 .
Proof. By induction on the length of the rewriting path. If t = s then we proceed by induction on the structure of s. Since ord(τ ) = 0, we can't have s = @(f, u).

Otherwise because R is a functional TRS we would have s ∈ IRR(R) which contradict the hypothesis. Thus s = f with f : τ ∈ F 0 and by definition s

∈ B 0 . Now if t → k+1 R s. If t = @(f, u) then we know that there is v : τ such that t → R v since R is a functional TRS. Then u → k R s

The K-TRS Class

Recall that we want to define (i) a set K n ⊆ T (Σ) and φ such that K n ∩IRR(R) ⊆ B φ(n) and (ii) a class of TRSs, K-TRS, for which L (A k ) ⊆ K n . Assuming that L (A) ⊆ K n we instantiate Theorem 1 with L = K n ∩ IRR(R) and prove termination. Definition 4.4.8 (K-TRS). Let Σ ∪ {@} be a typed alphabet. A TRS R is part of K-TRS if it is functional TRS and if for all rules l → r ∈ R, r ∈ K where K is inductively defined by:

x ∈ K ⇐ x ∈ X f (t 1 , . . . , t n ) : τ ∈ K ⇐ f ∈ Σ n ∧ t i ∈ K ∧ arity(τ ) = 0 f (t 1 : τ 1 , . . . , t n : τ n ) : τ ∈ K ⇐ f ∈ Σ n ∧ t i ∈ K ∧ ord(τ i ) = 0 (4.1) @(t 1 : τ 1 → τ 2 , t 2 : τ 1 ) : τ 2 ∈ K ⇐ t 1 ∈ Z, t 2 ∈ K ∧ arity(τ 2 ) = 0 (4.2) @(t 1 : τ 1 → τ 2 , t 2 : τ 1 ) : τ 2 ∈ K ⇐ t 1 , t 2 ∈ K ∧ ord(τ 1 ) = 0 (4.3)
with Z defined by:

t ∈ Z ⇐ t ∈ K @(t 1 , t 2 ) ∈ Z ⇐ t 1 ∈ Z, t 2 ∈ K
By constraining the form of the right hand side of each rule of R, K defines a set of TRS that cannot construct unbounded partial applications during rewriting.

The definition of K takes advantage of the type system and Lemma 5. The rules (4.2) and (4.3) ensure that an application @(t 1 , t 2 ) is either: (4.2) a total application, and the whole term can be rewritten; or (4.3) a partial application where t 2 can be rewritten into a term of B 0 (Lemma 5). Rule (4.1) can be seen as the uncurryfied version of (4.3). In (4.2), Z allows partial applications inside the total application of a multi-parameter function.

Example 4.4.2. Consider the classical map function over lists of elements of type A, where A is a base type of order 0. A typical call to this function is @(@(map, f ), l) of type list, where f is a mapping function, and l a list.

@ : list @ : list → list map : (A → A) → list → list f : A → A l : list
The whole term belongs to K because of rule (4.2): list is a base type (arity(list) = 0) and its subterm @(map, f ) : list → list framed above, belongs to Z. This subterm is a partial application, but there is no risk of stacking partial applications as it is part of a complete call (to the map function).

Example 4.4.3. Consider the higher-order functional TRS R defining a function stack as follows:

@(@(stack, x), 0) → x @(@(stack, x), s(n)) → @(@(stack, @(g, x)), n)

Here g is a function of type (A → A) → A → A. The stack function returns a stack of partial applications whose height is equal to the input parameter: @(@(stack, f ), s(s(s . . . s k (0) . . . ))) → * R @(g, @(g, @(g, . . . @(g k , f ) . . . )))

The depth of partial applications stacks in the output language is not bounded. With no equations on the @ symbol, the completion algorithm may not terminate. Notice that x is a function and @(g, x) a partial application (represented by a solid frame below). Hence the term @(stack, @(g, x)) (represented in a dashed frame) is not in Z, which means that @(@(stack, @(g, x)), n) is not in K. The TRS R does not belong to the K-TRS class.

@ : int

@ : int → int stack : A → A → int → int @ : A → A g : (A → A) → A → A x : A → A n : int
This peculiar way of stacking partial applications may seem useless at first. However this programming pattern frequently appears on higher-order program written in Continuation Passing Style (CPS), where each function is passed a continuation function to be called with the output of the function. In this setting the partial application stack contains the entire program's execution stack. CPS is mostly used as an intermediate representation useful for program analysis.

In the rest of this section we show how we can bound the size of partial application stacks generated by K-TRSs. More precisely, we define a set of terms

K n ⊆ T (Σ) as { tσ | t ∈ K, σ : X → B n ∩ IRR(R) } and claim that if R is a K-TRS and if A is a
REFD tree automaton with L(A) ⊆ K n then we can use Theorem 1 to prove that the completion of A with R terminates.

Theorem 2. Let A be a K n -coherent REFD tree automaton, R a K-TRS and E a set of equations. Let L = B n+2B ∩ IRR(R). If E = E r ∪ E c
L ∪ E R then the completion of A by R and E terminates.

In this theorem, B is a fixed upper bound on the arity of all the types of the program. The notion of K n -coherence (cf. Definition 4.4.9) of a tree automaton allows us to ensure that L (A) ⊆ K n . The idea of the proof developed in this section is the following:

• First we prove that K n is closed by → R (Lemma 10).

• Prove that if L(A) ⊆ K n , then it is preserved by completion using the notion of K n -coherence of A (Lemma 17).

• Prove that K n ∩ IRR(R) ⊆ B n+2B ∩ IRR(R) where B ∈ N is a fixed upper bound of the arity of all the types of the program (Lemma 19).

• Finally, we use those three properties combined to instantiate Theorem 1 with L = B n+2B ∩ IRR(R) to prove Theorem 2.

The rest of this section is dedicated to the proof of each of these items, leading to the proof of 2 at the end of the section. First, to prove that K n is closed by → R , we need some intermediate properties over K n . In particular make sure that, when we apply a rule, the considered substitution gives for each variable a term of K n : all rules are applied with a substitution of K n .

Lemma 6. For all well typed constructor terms t ∈ W(C, X ), For all terms s ∈ K n , if there exists σ such that tσ = s, then for all x ∈ Var (t), σ(x) ∈ K n .

Proof. By induction on t.

• t = x. If tσ = s, s ∈ K n , then σ(x) = s and σ(x) ∈ K n . • t = f (t 1 , . . . , t n ), f ∈ C n . If tσ = s, then s = f (s 1 , . . . , s n ) = f (t 1 σ, . . . , t n σ).
By hypothesis of induction, for all t i , for all x ∈ Var (t i ), σ(x) ∈ K n . Since Var (t) = n i=1 Var (t i ), then for all x ∈ Var (t), σ(x) ∈ K n .

Lemma 7. For all terms t ∈ LHS (the left-hand side of a functional TRS, cf. Definition 2.2.16), for all terms s ∈ K n , if there exists σ such that tσ = s, then for all x ∈ Var (t), σ(x) ∈ K n .

Proof. By induction on t.

• t = f (t 1 , . . . , t m ), f ∈ Σ m ∧ t 1 , . . . , t m ∈ W(C, X ). For all t i , using Lemma 6 we get for all variables x ∈ Var (t i ), σ(x) ∈ K n . Since Var (t) = m i=1 Var (t i ), then for all x ∈ Var (t), σ(x) ∈ K n .

• t = @(t 1 , t 2 ) with t 1 ∈ LHS and t 2 ∈ W(C, W). Using Lemma 6 we get for all variables x ∈ Var (t 2 ), σ(x) ∈ K n . By induction hypothesis, for all variables x ∈ Var (t 1 ), σ(x) ∈ K n . Since Var (t) = Var (t 1 )∪Var (t 2 ), then for all variables x ∈ Var (t), σ(x) ∈ K n .

Lemma 8. Let R be a functional TRS. For all rules l → r ∈ R, For all terms t ∈ K n , if there exists σ such that lσ = t, then for all x ∈ Var (l), σ(x) ∈ K n . In other words, each time a rule is used, we know that all variables are substituted with a term of K n .

Proof. Since R is a functional TRS, we have l ∈ LHS (cf. Definition 2.2.16). Using Lemma 7 we have for all x ∈ Var (l), σ(x) ∈ K n . Lemma 9. For all t ∈ K n , for all rules l → r ∈ R if there exists a position p and a substitution σ with lσ = t| p , then t| p ∈ K n , and for all terms

s ∈ K n , t[s] p ∈ K n .
Proof. By induction on t.

• t ∈ B n ∩ IRR(R). Since t ∈ IRR(R), there is no p and σ s.t. lσ = t| p .

• t = f (t 1 : τ 1 , . . . , t n : τ n ) : τ, arity(τ ) = 0. Two cases:

if p = λ, then t| p = t, and t ∈ K n . t[s] p = s, s ∈ K n .

if p = i.q, Since t i ∈ K n , by induction hypothesis we have

t i | q ∈ K n and t i [s] q ∈ K n . t| p = t i | q , so t| p ∈ K n . t[s] p = f (t 1 , . . . , t i [s] q , . . . , t n ), and since t i [s] q ∈ K n , t[s] p ∈ K n .
• t = f (t 1 : τ 1 , . . . , t n : τ n ) : τ with ord(τ 1 ) = 0, . . . , ord(τ n ) = 0. We can use exactly the same reasoning (note that the order of the arity of each type never change).

• t = @(t 1 : τ 1 , t 2 : τ 2 ) : τ with ord(τ 2 ) = 0, t 1 , t 2 ∈ K. We can use exactly the same reasoning.

• t = @(t 1 : τ 1 , t 2 : τ 2 ) : τ with arity(τ ) = 0, t 1 ∈ Z, t 2 ∈ K. We can use the same reasoning if p = λ or p = 2.q. If p = 1.q we have t 1 ∈ Z n and t| p = t 1 | q . Let us prove by induction on t 1 that we have t 1 | q ∈ K n , and t 1 [s] q ∈ Z n .

t 1 ∈ K n . By induction hypothesis (on t), t 1 | q ∈ K n , t 1 [s] q ∈ K n , and since

K n ⊆ Z n , t 1 [s] q ∈ Z n .
t 1 = @(t 1 , t 2 ) : τ . * If q = λ, then since R is a functional TRS, arity(τ ) = 0, which means t 1 ∈ K n , and by induction hypothesis (on t), t 1 | q ∈ K n and t 1 [s] q ∈ K n . So t 1 [s] q ⊆ Z n . * If q = 1.q , then by induction hypothesis on t 1 , t 1 | q ∈ K n and t 1 [s] q ∈ Z n . * If q = 2.q , then by induction hypothesis on t, t 1 | q ∈ K n and t

1 [s] q ∈ K n . So t 1 [s] q ⊆ Z n .
t[s] p = @(t 1 [s] q , t 2 ), and since t 1 [s] q ∈ Z n , t 2 ∈ K n and arity(τ ) = 0, then by definition t[s] p : τ ∈ K n .

Lemma 10 (K n is closed by → R ). Let assume that for all rules l → r ∈ R we have r ∈ K. For all t ∈ K n , for all u such that t → R u we have u ∈ K n .

Proof. By induction on t.

• If t ∈ B n ∩ IRR(R) then t is irreducible. • If t = f (t 1 , . . . , t n ), f ∈ Σ n .
For all rules l → r ∈ R and position p such that lσ = t| p and u = t[rσ] p . By definition of functional TRS, there is no rule for such term at the root, p cannot be λ. So p = p .q. Let us define t = t|p , and u = t [rσ] q . We have u = t[u ] p . By induction hypothesis, u ∈ K n , so using Lemma 9 we have t[u ] p ∈ K n .

• t = @(t 1 : τ → τ , t 2 : τ ) : τ with ord(τ ) = 0, t 1 , t 2 ∈ K n . We can use exactly the same reasoning. For all rules l → r ∈ R and position p such that lσ = t| p and u = t[rσ] p . Two cases:

if p = λ, for all x ∈ Var (l), t xσ, which implies xσ ∈ K n (Using Lemma 8). Finally, rσ ∈ K n .

if p = p .q, we can use the same reasoning as above.

• t = @(t 1 , t 2 ) : τ with arity(τ ) = 0, t 1 ∈ Z n , t 2 ∈ K n . For all rules l → r ∈ R and position p such that lσ = t| p and u = t[rσ] p . Two cases:

if p = λ, we can use the same reasoning as above.

if p = 1.q, using Lemma 9 again we have t 1 | q ∈ K n , and by induction hypothesis,

t 1 = t 1 [rσ] q ∈ K n , thus @(t 1 , t 2 ) ∈ K n .
if p = 2.q, let us define t 2 = t 2 [rσ] q . We have u = @(t 1 , t 2 ). By induction hypothesis, t 2 ∈ K n , so @(t 1 , t 2 ) ∈ K n .

In the same way we can prove that Z n is closed by → R . To prove that after each step of completion the recognized language stays in K n , we require the considered automaton to be K n -coherent.

Definition 4.4.9

(K n -Coherence). Let L ⊆ W(Σ) and n ∈ N. L is K n -coherent if L ⊆ K n ∨ L ⊆ Z n \ K n
Note that K n -coherence is preserved by inclusion (any subset of L is also K n -coherent). By extension we say that a tree-automaton

A = Σ, Q, Q f , ∆ is K n -coherent if the language recognized by all states q ∈ Q is K n -coherent. Lemma 15 (C R (A) preserves K n -coherence). Let A be a REFD tree automaton. If A is K n -coherent, then C R (A) is K n -coherent. Proof. Let A = Σ, Q, Q f , ∆ . By definition, C R (A) = Σ, Q , Q f , ∆ where Q , ∆ = Join(Q, ∆, CP(R, A)).
Let us proceed by induction on the structure of CP(R, A).

• CP(R, A) = ∅. Then C R (A) = A, which is K n -coherent. • CP(R, A) = { l → r, q, σ } ∪ S. Join(Q, ∆, CP(R, A)) = Join S (Q , ∆ ∪ {q → q }, S) with Q , ∆ , q = N orm(Q, ∆, rσ). Let A = Σ, Q , Q f , ∆ ∪ {q → q } . By Lemma 14, A is K n -coherent. By definition CP(R, A ) = S. By hypothesis of induction C R (A ) is K n -coherent. Also note that Join(Q , ∆ ∪ {q → q }, CP(R, A )) = Join(Q , ∆ ∪ {q → q }, S) so C R (A) = C R (A ). Thus C R (A) is K n -coherent.
Lemma 16 (S E (A) preserves K n -coherence). Let A, A two REFD tree automata, R a K-TRS and E a set of equations such that

E = E r ∪ E c n ∪ E R . If A E A and A is K n -coherent then A is K n -coherent and S E (A) is K n -coherent.
Proof. Let us name q a and q b the two states merged from A to A . The proof is based on the idea that merging those two states is equivalent to adding the transitions q a → q b and q b → q a . Let A be the tree automaton defined from A by Σ, Q, Q f , ∆ ∪ {q a → q b , q b → q a } . Note that for all states q ∈ Q \ {q a }, L(A , q) = L(A , q). Let us show that A is K n -coherent instead of A . First we show that L(A, q a ) ⊆ K n ⇐⇒ L(A, q b ) ⊆ K n . Since q a and q b are being merged, it means that there exists two terms s and t such that s → * ∆ q a , t → * ∆ q b and s = E t. Here we have three cases.

1. s = E r t, then s = t and s ∈ K n ⇐⇒ t ∈ K n .

s = E c

n , then t = s| p for some p meaning that there is an equation u = u| p and a substitution σ such that uσ = s and u| p σ = t. Recall that we restrain ourselves to well-typed equations where u ∈ W(C, X ). Then using the definition of K n we have s ∈ K n ⇐⇒ s| p ∈ K n .

3. s = E R t, then s → R t and using Lemma 10 (and its equivalent with

Z n ) we have s ∈ K n ⇐⇒ t ∈ K n . So we have s ∈ K n ⇐⇒ t ∈ K n . Now since A is K n -coherent, L(A, q a ) ⊆ K n ⇐⇒ L(A, q b ) ⊆ K n .
By Lemma 13 we have A K n -coherent. By extension since for all states q ∈ Q \ {q a }, L(A , q) = L(A , q), A is K n -coherent and S E (A) is K ncoherent.

By using Lemma 15 and Lemma 16, we can prove that the completion algorithm, which is a composition of C R (A) and S E (A), preserves K n -coherence.

Lemma 17 (Completion preserves K n -coherence). Let A 0 = Σ, Q, Q f , ∆ an initial REFD tree automaton for the completion algorithm, R a fK-TRS and E a set of equations.

If E = E r ∪ E c L ∪ E R with L = B n+2B ∩ IRR(R) and A 0 is K n -coherent then for all k ∈ N, A k is K n -coherent. In particular, A * is K n -coherent. Proof. By induction on k. If k = 0, by hypothesis A 0 is K n -coherent. If k = i + 1, by definition A i+1 = S E (C R (A i )). By hypothesis of induction A i is K n -coherent. We then apply Lemma 15 to show that C R (A i ) is K n -coherent, and Lemma 16 to show that S E (C R (A i )) is K n -coherent. We conclude that if A * exists, it is K n -coherent.
By construction we can prove that the depth of irreducible K n terms is bounded, which correspond to the following lemmas.

Lemma 18. Let B ∈ N be the maximum function arity in the program. For all t : τ ∈ K n , t : τ ∈ IRR(R) ⇒ t : τ ∈ B n+B-arity(τ ) .

Proof. By induction on t : τ .

• t : τ ∈ B n . Since B is the maximum program arity, arity(τ ) ≤ B, so n ≤ n + B -arity(τ ). Then t : τ ∈ B n+B-arity(τ ) .

• t = f (t 1 : τ 1 , . . . , t n : τ n ) : τ, arity(τ ) = 0. Since R is a functional TRS, if t ∈ IRR(R) and arity(τ ) = 0 then f ∈ C n . By induction hypothesis, for all t i , t i ∈ B n+B-arity(T i ) , with B n+B-arity(T i ) ⊆ B n+B . This implies t 1 , . . . , t n ∈ B n+B , so by definition, t ∈ B n+B . Since arity(τ ) = 0, t ∈ B n+B-arity(τ ) .

• t = f (t 1 : τ 1 , . . . , t n : τ n ) : τ with order(τ 1 ) = 0, . . . , order(τ n ) = 0. If arity(τ ) = 0 then we can use the same reasoning. Otherwise, for all t i since order(T i ) = 0 we have t i ∈ B 0 , so t ∈ B 1 . Since n > 0, then B > 0, B > arity(τ ) and 1 ≤ n + B -arity(τ ). So by definition, t ∈ B n+B-arity(τ ) .

• t = @(t 1 : τ 1 , t 2 : τ 2 ). Since R is a functional TRS, if t ∈ IRR(R) then we can't have arity(τ ) = 0, so by definition of K n , order(τ 2 ) = 0 and t 1 , t 2 ∈ K n . Moreover, since t 2 ∈ IRR(R), t 2 ∈ B 0 , t 2 ∈ W(C). By induction hypothesis, since t 1 ∈ IRR(R) we have t 1 ∈ B n+B-arity(τ 2 ) . By definition, t ∈ B n+B-arity(τ 2 )+1 , and since arity(τ 2 ) = arity(τ ) -1, t ∈ B n+B-arity(τ ) .

Lemma 19. For all t : τ ∈ K n , t : τ ∈ IRR(R) ⇒ t : τ ∈ B n+2B-arity(τ ) .

Proof. Note that if t ∈ K n then t ∈ Z n . We reason by induction on t : τ .

• t : τ ∈ K n , then by Lemma 18 we have t : τ ∈ B n+B-arity(τ ) , thus t : τ ∈ B n+2B-arity(τ ) .

• t = @(t 1 : τ 1 , t 2 : τ 2 ) : τ with t 1 ∈ Z n , t 2 ∈ K n . By Lemma 18 we have t 2 : τ 2 ∈ B n+B . By induction hypothesis we have t 1 : τ 1 ∈ B n+2B-arity(τ 2 ) . Since arity(τ 2 ) = arity(τ ) + 1, t 1 : τ 1 ∈ B n+2B-arity(τ )-1 .

No that know the depth of t 1 and t 2 , we can deduce the depth of t by taking the maximum depth of t 1 and t 2 , which gives us t : τ ∈ B max(n+2B-arity(τ ),n+B+1) .

However arity(τ 2 ) ≤ B implies arity(τ ) < B and then B -arity(τ ) -1 ≥ 0.

Thus n + B + 1 ≤ n + 2B -arity(τ ). Finally t : τ ∈ B n+2B-arity(τ ) .

Reminder of Theorem 2. Let A be a K n -coherent REFD tree automaton, R a K-TRS and E a set of equations.

Let L = B n+2B ∩ IRR(R). If E = E r ∪ E c L ∪ E R then the completion of A by R and E terminates.
Proof. According to Lemma 17, for all k ∈ N, the completed automaton A k is K ncoherent. By definition this implies that L (A k ) ⊆ K n . Moreover, we know that IRR(R) ∩ K n ⊆ B n+2B (Lemma 19). Let L = B n+2B ∩ IRR(R). R is terminating, so for every term s ∈ L (A k ) there exists t ∈ L such that s → * R t. Since the number of normal form of L is finite w.r.t. E, Theorem 1 implies that the completion of A by R and E terminates.

Verification Procedure

In the previous section we have identified a class of TRS, K-TRS, for which contracting equations over the values can be used while preserving termination of the TAC algorithm. We now want to automatically generate those contracting equations to abstract the program execution using the TAC algorithm and solve a given regular safety problem (cf. Definition 2.4.7).

Contracting Equations Generation

Theorem 2 states a number of hypotheses that must be satisfied in order to guarantee termination of the TAC algorithm:

• The initial automaton A must be REFD and K n -coherent for some n.

• R is a K-TRS. This is a straightforward syntactic check. If it is not verified, we can reject the TRS before starting the completion.

• R must be terminating.

• The set of equations E must be of the form

E c L ∪E r ∪E R with L = B n+2B .
That is what we want to automatically generate here. The equation sets E r and E R are determined directly from the syntactic structure of R (cf. Definitions 4.2.10 and 4.2.9). However, there is no unique suitable set of contracting equations E c L . This set must be generated carefully in order to prove or disprove a given property.

In this section, we describe a method for generating all possible sets of contracting equations E c L . Following the previous section, L should be replaced by B n+2B (cf. Theorem 2). However to simplify we only present the case where L = W(C) (with IRR(R) ⊆ W(C), i.e., all results are first-order terms). We generate the contracting equations iteratively, as a series of equation sets E c k where the equations only equate terms of depth at most k.

Recall that according to Definition 4.2.8, a contracting equation is of the form u = u| p with p = λ, i.e., it equates a term with one of its strict subterm (of the same type). A set of contracting equations over the set W(C) is then generated as follows: (i) generate the set of left-hand side of equations as a covering set of patterns [START_REF] Kounalis | Testing for the ground (co-)reducibility property in term-rewriting systems[END_REF], so that for each term t ∈ W(C) there exists a left-hand side u of an equation and a substitution σ such that t = uσ. (ii) for each left-hand side, generate all possible equations of the form u = u| p , satisfying that both sides have the same type. (iii) from all those equations, we build all possible E c L (with L = W(C)) such that the set of normal forms of W(C) w.r.t. E c L is finite. Since E c L is left-linear and L = W(C), this can be decided efficiently [START_REF] Comon | Sequentiality, monadic second-order logic and tree automata[END_REF].

Example 4.5.1. Assume that C = { 0 : 0, s : 1 }. For k = 0, E 0 c = ∅ because there is no covering set of terms with depth 0. Thus, there is no set E c L satisfying Definition 4.2.8 for k = 0. For k = 1, the covering set is {s(x), 0} and E 1 c = {{s(x) = x}}. For depth 2, the covering set is {s(s(x)), s(0), 0} and E 2 c = E 1 c ∪ {{s(s(x)) = x}, {s(s(x)) = s(x)}, {s(0) = 0}, {s(0) = 0, s(s(x)) = x}, {s(0) = 0, s(s(x)) = s(x)}}. All equation sets of E 1 c and E 2 c satisfy Definition 4.2.8 and lead to different approximations.

To find every left-hand side we use the notion of covering set of patterns inspired by Kounalis [START_REF] Kounalis | Testing for the ground (co-)reducibility property in term-rewriting systems[END_REF].

Definition 4.5.1 (Covering Patterns). A set of patterns P ⊆ W(Σ, X ) is covering for t ∈ W(C, X ) if for all substitution σ, there exists a term s ∈ P and a substitution σ such that tσ = sσ . Similarly, P is covering for a type τ if it is covering for the set {t | t : τ }. We write P k (τ ) the set of patterns of depth (at most) k covering the type τ . It is inductively defined by:

P 0 (τ ) = { { x } } P k+1 (τ ) = { f (t 1 , . . . , t n ) | f : (τ 1 , . . . , τ n ) → τ ∧ t i ∈ P k (τ i ) }
Note that in P k (τ ), each term of {t | t : τ } is covered by exactly one pattern.

From a pattern u of type τ we can extract a set of possible contracting equations, C(u : τ ) defined as:

C(u : τ ) = ∅ if u is closed { u = u| p | u : τ ∧ u| p : τ } otherwise
For any k > 0, the set of depth-k contracting equation set E c k is the smallest set such that:

E c ∈ E c k ⇐ ∀τ. ∀u ∈ P k (τ ). ∃! u = u| p ∈ C(u : τ ) s.t. u = u| p ∈ E c
By construction, for each contracting set E c in E c k , E c is deterministic, complete over W(C) (it can rewrite any term of W(C)) with a finite number of normal forms. In each E c , the set P k (τ ) control the number of normal forms for each type τ while the normal form attributed to each term u depends on what equation have been picked from C(u : τ ). In the following, we demonstrate that by choosing the right k and by picking the right equations in C(u : τ ), any regular abstraction of W(C) can be built. In other words, for any regular abstraction of W(C), there exists k such that some equation set in E c k produces this abstraction (Lemma 21). Note that a regular abstraction of W(C) can be represented by a tree automaton where each state recognizes one element (equivalence class) of the abstraction. We first show that for any of such automaton, it is possible to find k such that each state of type τ is covered by different patterns in P k (τ ).

Lemma 20. Let A be a deterministic and reduced tree automaton recognizing terms of W(C). Let us assume that A is type-coherent, meaning that for all state q there exists a unique base type τ ∈ A such that, L(A, q) ⊆ {t | t : τ } which we write q : τ . Let us write Q τ = {q | q : τ }. Then there exists k ∈ N for which for each base type τ ∈ A , there exists a partition P of P k (τ ) such that each state q ∈ Q τ is covered by a different element of P . In other words, each pattern of P k (τ ) cover (part of ) a unique state of A.

Proof. We can show this by contradiction. If no such k exists, it means that for all k ∈ N, for all partition P of P k (τ ) there is always two states q and q covered by the same element of P . This means that there is a pattern p ∈ P and two substitutions σ, σ such that pσ → * A q and pσ → * A q . Since this is true for all k, it must be because the same term t is recognized by both states. However this is not possible because A is deterministic.

Lemma 21 (Any regular abstraction is in E c k ). For any regular abstraction of W(C), there exists k such that some equation set in E c k produces this abstraction.

Proof. A regular abstraction can be represented with a tree automaton A where each equivalence class (abstraction) is represented by a state. We already showed with Lemma 20 that there exists k for which P k (•) generates a covering set that covers each state of the A with different patterns. Let us name P k (q) the set of patterns covering state q. We now need to prove that there exists some k ≥ k for which there exists E c ∈ E k c where each state q are represented by different normal forms in E c . We show that there exists k ≥ k with the following properties:

• For each recursive state q, with k we have in addition that each covering pattern u in P k (q) uses recursion: there exists some position p such that for all σ, u| p σ is also recognized by q. We write Rec q (u) (one of) such position for a given covering pattern u.

• For each non-recursive state q, with k we have in addition that for each covering pattern u in P k (q) if there exists a substitution σ, a context C and recursive state q such that uσ → * A C[q ] → * A q then there exists some position p such that for all σ, u| p σ is also recognized by q . We write Rec q (u) = p (one of) such position for a given covering pattern u.

Using the contracting equations E c = { u = u| Recq(u) | q ∈ Q ∧ u ∈ P k (q) }, we ensure that every term recognized by q are represented by the same term, unique to q. Note that for each state q, for each u ∈ P k (q) we have u ∈ P k (τ ) with q : τ and u = u| Recq(u) ∈ C(u : τ ). By definition this means that we have E c ∈ E k c . Note that there are potentially multiple solutions for E c since there are potentially multiple candidates for each Rec q (u). The generated E c gives in general a more precise abstraction compared to the one represented by A. Two terms represented by the same state may rewrite to two different normal forms w.r.t. E c .

Equations Exploration

In the following, we define a simple verification procedure that uses the Tree Automata Completion algorithm and the equation generation method described in the previous section to verify regular safety problems of the form R, A, O where R is a K-TRS, A a K n -coherent tree automaton (where n follows Theorem 2) and O some regular language.

Input : A regular safety problem R, A, O Output : Success if the problem's property is verified or

Fail if a counter-example is found.

1 forall k from 1 to ∞ do 2 A * k ← Completion R (A k ); 3 if L(A * k ) ⊆ O then 4 return Fail; 5 else 6 forall E c ∈ E k c do 7 E ← E r ∪ E R ∪ E c ; 8 A * ← Completion R,E (A); 9 if L(A * ) ∩ O = ∅ then 10 return Success;
Algorithm 1: Verification Procedure The procedure defined by Algorithm 1 searches for a set of contracting equations E c such that verification succeeds, i.e. L(A * ) ⊆ O. Starting from k = 1, we apply the following algorithm:

1. We first complete the tree automaton A k recognizing the finite subset of L(A) of terms of maximum depth k. Since L(A k ) is finite and R is terminating, the set of reachable terms is finite, completion terminates without equations and computes an automaton Because of E R the generated abstraction is necessarily closed w.r.t. R which makes the abstraction collapsing (cf. Definition 2.4.9). Furthermore, because the tree automaton generated by the TAC algorithm is ¡ -deterministic, the generated abstraction is necessarily functional (cf. Definition 2.4.8).

A * k recognizing exactly the set R * (L(A k )) [GR10].
Theorem 3 (Functional & Collapsing Regular Completeness). Let P = R, A, O a regular safety problem over K-TRS. Let L be the contracted language as defined in Theorem 2. If there exists a set of contracting equations

E c L such that E r ∪ E R ∪ E c
L is able to verify P , this algorithm will eventually find

E c L such that E r ∪ E R ∪ E c
L is also able to verify P .

Proof. First note that each use of the completion algorithm terminates. For each k, The completion of A k without equations terminates because L(A k ) if finite. The completion of A with E terminates because R is a K-TRS (Theorem 2). E c L represents a regular abstraction of W(C). Thanks to Lemma 21 we know there exists some k such that E k c contains some E c L representing the same abstraction. Then if E c L is enough to verify P , so is E c L . Since this algorithm does an exhaustive exploration of k and E k c , and since because R is a K-TRS each use of the completion algorithm terminates, it will eventually find it.

Theorem 4 (Completeness in Refutation). Let P = R, A, O a regular safety problem over K-TRS. Let L be the contracted language as defined in Theorem 2. If there is a counter-example to P , this procedure will eventually find it.

Proof. At each step k, we complete A k to find counter-examples. If there exists a term in L(A) that rewrites outside of O, then there exists some k such that this term is recognized by A k .

Experiments

The verification technique described in this chapter has been integrated in the Timbuk library [START_REF] Genet | Reachability Analysis and Tree Automata Calculations[END_REF] (version 3.2). We implemented the naive equation generation procedure described in Section 4.5.2 where all possible equation sets E c are enumerated. Despite the evident scalability issues of this simple version of the verification procedure, we have been able to verify a series of properties on several classical higher-order functions such as map, filter , exists, forall , foldLeft, foldRight as well as higher-order sorting functions parameterized by an ordering function.

Test Suite

Our test suite, publicly available on Timbuk's website [START_REF] Genet | Experiments with timbuk 3[END_REF], is composed of positive and negative regular safety problems of the form R, A, O where R is a K-TRS representing the functional program, A a K n -coherent tree automaton representing the possible initial states, and O a regular language. The goal for Timbuk is to prove the safety of the program by proving that R * (L(A)) ⊆ O, or disprove it by finding a counter example. Most problems are taken from or inspired by [START_REF] Ong | Verifying higher-order functional programs with pattern-matching algebraic data types[END_REF][START_REF] Kobayashi | Mochi: Model checker for higher-order programs[END_REF], and Tons of Inductive Problems [START_REF] Claessen | Tip: Tons of inductive problems[END_REF]. We expect some problem instances to be similar to the ones used in [START_REF] Matsumoto | Automata-based abstraction for automated verification of higher-order tree-processing programs[END_REF]. However this cannot be verified since the test suite used in this paper is not publicly available.

Timbuk will prove the input property by exhibiting some tree automaton A * over-approximating R * (L(A)) using the verification technique described in this chapter. For each problem R, A, O in the test suite, we have been able to verify the correctness of the verification, i.e. the fact that R * (L(A)) ⊆ L(A * ), using a proof assistant embedding a formalization of rewriting and tree automata [START_REF] Boyer | Certifying a tree automata completion checker[END_REF]. To do that, it is enough to prove that (a) L(A * ) ⊇ L(A) and that (b) for all critical pairs l → r, σ, q of A * we have rσ → * A * q. Property (a) can be checked using standard algorithms on tree automata. Property (b) can be checked by enumerating all critical pairs of A * (there are finitely many) and by proving that all of them satisfy rσ → * A * q. Since there exists algorithms for checking properties (a) and (b), the complete proof of correctness can automatically be built in the proof assistant. For instance, the automaton A * can be used as a certificate to build the correctness proof in Coq [START_REF]The coq proof assistant[END_REF] and in Isabelle/HOL [START_REF] Nipkow | Isabelle/HOL -A Proof Assistant for Higher-Order Logic[END_REF]. Besides, since verifying (a) and (b) is automatic, the correctness proof may be run outside of the proof assistant (in a more efficient way) using a formally verified external checker extracted from the formalization. All our (successful) verification attempts has been certified automatically using an external certified checker defined in Coq [START_REF] Boyer | Certifying a tree automata completion checker[END_REF]. The next three columns give the execution time before reaching a solution or finding a counter example, averaging on 10 executions: the time spent by the Tree Automata Completion algorithm, the time spent generating equations and the total time (± standard deviation). The timeout threshold is set to 120s. The last column gives the memory usage of Timbuk. Each table is split in two, with on the top positive problems where the property is proved, and on the bottom negative problems where the property is disproved.

Experimental Results

Execution Time

The tables show mostly low execution times (far below the second) for most problems, in particular when dealing with lists or incorrect properties. However we see that simply enumerating all possible equations sets becomes a problem when dealing with binary trees (treeDepth, orderedTreeTraversal, orderedTree) where the execution time start rising above the second. This cannot be improved without developing a more efficient equation generation procedure that doesn't need to go through all possible equations.

Memory Usage

We also see that the memory usage is extremely volatile, ranging from 4MiB in the best case to 4GiB in the worst case. This happens because the whole program is analyzed at once, on the whole set of inputs. It makes the Tree Automata Completion algorithm generate huge automata, especially during the exact completion phase (without equations) in the equation generation procedure, Algorithm 1 (line 2). This could be improved be adding modularity, analyzing each function separately when possible (which is one of the purposes of the next chapter).

Completeness

Finally, many problems has not been solved before the timeout threshold. For the most part, this is because this technique is not regularly complete (does not cover every regular safety problem). We proved in the previous section the "functional and collapsing" completeness of this technique (Theorem 3), which means that it can prove any regular problem which can be proved using a collapsing functional abstraction, i.e. each term is abstracted into a unique equivalence class and each class is R-closed. It is enough to handle non-trivial problems such as proving that the output of the insertion-sort algorithm is sorted (insertionSort). However it is not enough to prove the merge-sort algorithm. In fact, it is not even enough to verify some simpler functions.

Example 4.6.1. Consider the following TRS R defining a pred function computing the predecessor of a natural number. The term pred(0) rewrites into the special error state f ail.

pred(0) → f ail pred(s(x)) → x
We want to show that pred(n) when n > 0 never fail. Using the technique described in this chapter, the natural approach would be to (automatically) search for the contracting equation set E c = {s(s(0)) = s(0)} (or similar) that separates the natural numbers into two equivalence classes O and N + . This doesn't work because the generated set of equation E contains E c but also E R . This means that for all n ∈ N + , since pred(n)

rewrites into a natural number it must be abstracted into either O or N + . If we take n = s(0) then pred(s(0)) rewrites into 0 so pred(s(0)) ∈ O. Now if we take n = s(s(0)) then pred(s(s(0))) rewrites into s(0) so pred(s(s(0))) ∈ N + . However both s(0) and s(s(0)) are in the same class N + , which means that pred(s(0)) and pred(s(s(0))) should be in the same class, although we have just seen that they are not (pred(s(0)) ∈ O and pred(s(s(0))) ∈ N + ). This shows that there are no sets of equations of the form E c ∪ E R ∪ E r that can verify this function. As a result, this verification technique is bound to diverge even on this simple problem. There are two ways to solve this problem:

• A first solution is to remove E R so that the final abstraction may not be collapsing. This would allow pred(s(0)) to not be automatically abstracted by O but in a third abstract value N for instance, along with pred(s(s(0))).

• Another solution would be to generate non-functional abstractions, so that pred(s(0)) can be abstracted by both O (because of E R ) and N + .

This example shows that the only way to reach full regular completeness is to generate either non-functional abstraction or non-collapsing abstractions [START_REF] Genet | Equational approximations for tree automata completion[END_REF]. Remember that by definition the Tree Automata Completion algorithm can only generate functional abstractions. Generating non-collapsing abstractions would mean generating equation sets without E R , which was introduced to reduce the number of equations to consider [START_REF] Genet | Termination criteria for tree automata completion[END_REF]. In the light of our experimental results this is to be reconsidered.

Related Work

To our knowledge, the technique developed by Matsumoto et al. [START_REF] Matsumoto | Automata-based abstraction for automated verification of higher-order tree-processing programs[END_REF] is the only other fully automatic verification technique targeting regular safety problems. As mentioned in Section 3.3.2, this paper defines a counter-example guided refinement procedure to directly find equivalence classes represented as tree automata instead of equations (each automaton recognizing one equivalence class). The abstraction procedure is based on SMT solving and is regularly complete as it generates regular abstractions that are not necessarily functional or R-closed. However because of that, the abstraction procedure is computationally expensive. Even if in our case enumerating equation sets can also be expensive, we seem to have overall better performances. However this cannot be properly verified as neither the implementation nor the test suite of used in the paper [START_REF] Matsumoto | Automata-based abstraction for automated verification of higher-order tree-processing programs[END_REF] are publicly available.

Conclusion

In this chapter we have used the Tree Automata Completion algorithm to automatically verify regular safety properties on higher-order functional programs. We have defined the class of K-TRS defining higher-order functional programs on which contracting equations on values could be used to generate regular abstractions without compromising the termination of the completion algorithm. This has been used to automatically verify regular properties on higher-order programs with good time performances, in particular on list processing programs. However we have seen with Example 4.4.3 that the K-TRS does not include higherorder functional programs written in CPS form, which is an important family of functional programs. Moreover we also showed that the current abstraction procedure based on contracting equations can only generate functional, collapsing abstractions. We have seen that this prevents us to reach regular completeness: some regular problems as simple as the pred problem (cf. Example 4.6.1) cannot be handled. We need to find another way to generate abstractions. We have seen that in their paper [MKU15] Matsumoto et al. define a regularly complete abstraction procedure by using tree automata instead of equations. However by generating regular abstractions that are not necessarily functional or collapsing, this procedure seems expensive. In the next chapter we show that we can still generate collapsing only abstractions while preserving regular completeness. By using regular languages as types, we define a new complete and modular verification technique designed to scale.

Chapter 5

Regular Language Type Inference

Introduction

In this chapter we design a novel technique for the verification of regular safety properties on higher-order functional programs that is complete and modular. This continues the effort developed in the last chapter where we ended off with three problems to solve:

• (Abstraction) Using contracting equations on values with the Tree Automata

Completion algorithm can only generate abstractions that are both functional and collapsing. We have seen with Example 4.6.1 that this prevents the use of contracting equations in a regularly complete verification technique for safety properties. We need a new way of abstracting the program in a non-functional manner.

• (Inference) For now we used a very naive way of generating abstractions that just enumerates all the possibles abstractions. This is relatively fast in practice for small programs as shown in Section 4.6.2. However this is favored by the use of contracting equations on values, which we cannot use in a complete verification technique. We need to define a proper abstraction inference procedure.

• (Modularity) Finally, analyzing whole programs at once is surely not practicable with larger programs. We need to design a modular verification procedure able to break down the verification problem into smaller parts.

Our new technique is defined as a type inference system which aims at typing each term of the input term rewriting system using regular languages as types.

Abstraction solution: regular abstract interpretation

Our first problem (Abstraction) is solved by explicitly defining the abstraction relation (cf. Definition 2.4.4) using term rewriting systems. This defines a framework of regular abstract interpretation, a special case of abstract interpretation where: (a) the concrete domain is T (Σ) the set of terms/states, (b) the abstract domain is Σ # a set of abstract states, (c) the abstraction relation is defined as a rewriting system ∆ # that rewrites concrete terms into abstract states. We write Λ = Σ # , ∆ # the tree automaton defining the abstraction of T (Σ). In this automaton, each Σ # recognizes a regular language. (d) the abstract semantics of the program is defined by a TRS R # , extracted from R, that rewrites abstract terms. The relation is defined as → ! ∆ # ∪R # (remember that we write s → ! R t when s → * t and t ∈ IRR(R)). 93

Example 5.1.1. Let us consider the following property over the TRS R defining a filter function over lists of natural numbers (List). We want to verify that for each list l, when filter is called with the even predicate, all odd numbers are filtered out of the l in the output. This can be stated as follows using the for _all predicate function:

∀l ∈ List. for _all even (filter even l) → * R false

All the functions (for _all , even, filter, etc.) are defined in R. To verify this particular property, in the context of regular abstract interpretation we can restate our property as for _all even (filter even List # ) false #

where List # and false # are some elements of the abstract domain Σ # and the relation → ! ∆ # ∪R # . Each abstract value of Σ # recognizes a concrete language as defined by ∆ # :

0 → N at # nil → List # true → true # s(N at # ) → N at # cons(N at # , List # ) → List # false → false #
Note how the ∀ disappears. A solution to this problem is a complete definition of R # (which was not yet given) and Λ = Σ # , ∆ # such that the new (abstract) property is verified when is an abstraction of R in Σ # . In this case, a possible solution contains ∆ # defined by (in addition to the transitions above):

0 → Even # s(Even # ) → Odd # nil → EvenList # s(Odd # ) → Even # even → even # cons(Even # , EvenList # ) → EvenList #
and R # defined as1 :

filter even # List # → EvenList # f or_all even # EvenList # → true #
In this abstraction, (filter even l) rewrites into EvenList # using ∆ # and R # . In the same way, (f or_all even EvenList # ) rewrites into true # and only true # , which means that overall we have (f or_all even (filter even List # )) false # . (remember that = → ! ∆ # ∪R # ). The verification problem boils down to the problem of automatically finding Λ = Σ # , ∆ # and R # that proves or disproves the user's property.

Modularity solution: regular language types

Note how each rule of ∆ # and R # acts as a type signature for its associated constructor symbol or function symbol respectively. To find Σ # , ∆ # and R # we embrace the type perspective by defining a modular type inference algorithm that can find the type signatures of each symbol necessary to solve a given regular safety verification problem. The algorithm is a backward static analysis that uses the input expression target type to find the input type of each function call recursively.

Example 5.1.2. Our previous (abstract) rewriting problem becomes a regular typing problem stated as: Λ, R # , π f or_all even (filter even x) : false # where is our type judgment relation with Λ, R # the typing environment and π : X → Σ # the substitution binding each variable to its type. We want to make sure that it is impossible to type this input term with false # . It can be resolved by inferring every possible type for (f or_all even (filter even x)) and its subterms, and check that it is never typed with false # .

To solve this problem, we have developed a regular type inference procedure. Given an input term (such as (f or_all even (filter even x))) and a target type (such as false # ), this procedure is able to infer Λ, R # and all the possible type substitutions π for which the given term is typed with the given type. This typing algorithm analyzes each function separately, solving our modularity problem.

Example 5.1.3. To find Λ, R # , π such that (f or_all even (filter even x)) can be typed with the target type false # , our type inference algorithm starts by solving the simpler problem of finding Λ, R # , π such that (f or_all y z) can be typed with the same target false # , which does not involve filter nor even. It will find/generate input types for y and z that can then be recursively used to analyze the subterms even and (filter even x), and so on. If it cannot find a suitable type for x (or any other term), then we know (f or_all even (filter even x)) cannot be typed with false # and the property is verified.

In this algorithm, each function is analyzed separately.2 For each function f , it needs to solve the following problem: Given a target output type for f , infer all the possible inputs types to the function that leads to this type. We see in Section 5.3.3 that for non-recursive functions, this can be done using well known operations on tree automata by simply looking at the output type and the rewriting rules of the function. For recursive functions however, a more complex invariant learning procedure is needed.

Inference solution: Regular language learning

Analyzing a recursive function is much harder than non-recursive functions, as it requires finding a regular fixpoint for the function. We do that by solving our last problem (Inference) with the definition of a new regular invariant learning procedure. The core of the invariant inference procedure is an example guided regular language learning procedure, based on the Tree Automata Completion Algorithm [START_REF] Genet | Termination criteria for tree automata completion[END_REF] and SMT constraints solving. For a given symbol f and target regular language partition P (a set of disjoint regular languages), our procedure is able to give, for all L ∈ P , all the input regular languages L 1 , . . . , L n such that for all t i ∈ L i , f (t 1 , . . . , t n ) rewrites into a term belonging to L. By giving R, even, and the target partition {{true}, {false}} to our procedure, it is able to automatically learn that the interesting input languages for even are the languages Even = {n | n % 2 = 0} and Odd = N \ Even. It will also infer that ∀n ∈ Even. even n → * true and that ∀n ∈ Odd. even n → * false.

As we will see in Section 5.3.4, learning is done by providing a series of positive and negative examples to the procedure. For instance by providing (even 0) and (even s(0)), because both rewrite to two different elements of the target partition, we learn that 0 and s(0) must be in different input languages. In practice, the target language partition is given in terms of abstract values in an input abstraction domain.

In our example, the input partition would be {true # , false # }. Similarly, the output of the invariant inference procedure is a refinement of the abstract domain (rules to add to ∆ # ) and an abstract TRS R # . In our example, the output of the procedure would be:

0 → Even # s(Even # ) → Odd # s(Odd # ) → Even # added to ∆ # even Even # → true # even Odd # → false # added to R # oddOdd # → false # odd Even # → true #
Note that it also gives information about the odd function, since the definition of even is mutually recursive with odd. In Section 5.3.4 we show that this procedure is regularly complete and complete in refutation. This means that, if there exists a regular abstraction (∆ # and R # ) that satisfies the input partition, then the procedure will find it. In addition, if there exists an input term rewriting into two different elements of the input partition P , the procedure terminates and provides such a counter-example term. The rest of the chapter is structured as follows. Section 5.2 introduces our regular abstract interpretation framework. Section 5.3 presents the regular language type system and the type inference algorithm. Section 5.4 exposes the results of our experiments carried on a custom implementation of the technique in a new version of Timbuk [START_REF] Haudebourg | Timbuk 4: Regular verification framework based on tree automata and term rewriting systems[END_REF], and compare them with the technique defined in Chapter 4. Finally, Section 5.5 concludes the chapter.

Regular Abstract Interpretation

In this section we define a regular abstract interpretation framework that adapts classical abstract interpretation to term rewriting systems and regular languages. In this framework, terms representing states of the execution are abstracted into a regular language, denoted by a state of a tree automaton. For readability, we denote those states by identifiers of the form name # , where name is an arbitrary symbol whose role is to provide an intuition of the language recognized by name # . In the following, we also call those states abstract values. The set of abstract value is the abstract domain, denoted by Σ # . The semantics of the program R is represented by an abstract TRS R # that operates over the abstract domain of regular languages. We start with an example. Define I as the set {nil, cons(a, nil), cons(b, nil), ...} of (not nested) lists of as and bs and assume that the safety property we wish to verify is

(1) ∀l ∈ I. sorted (sort l) → * R false This can be translated into an abstract interpretation problem where the goal is to find an abstract domain Σ # provided with an abstraction function α : T (Σ) → P(Σ # ) and a concretization function γ : Σ # → P(T (Σ)), and an abstract semantics → # extracted from R which faithfully models R such that

(2) sorted (sort ab_list # ) → # * false # where ab_list # and false # are two abstract values of Σ # such that α(I) = {ab_list # } and γ(false # ) = {false}. Note that having an abstraction α returning a set of abstract elements is non-standard in abstract interpretation but we have ensured that it is used consistently throughout the chapter. In this particular first abstraction, all lists of a and b are abstracted by the same abstract element ab_list # thus α(I) is the singleton {ab_list # }. Since Σ # is finite, it is easier to verify property (2) than property (1). Then, if the abstraction is correct and property (2) is true, we can then deduce that property (1) is also true. In the rest of this section, we show how to define → # , α and γ in terms of term rewriting systems and how to infer them with an abstract domain Σ # which together will allow us to verify a given property. In the rest of this chapter, we use σ to denote concrete substitutions from X to T (Σ), and π to denote abstract substitutions from X to Σ # . In addition we name γ(π) the set of all concrete substitutions extracted from π, i.e., γ(π) = { σ | ∀x ∈ Dom(π). σ(x) ∈ γ(π(x)) }.

Regular Abstract Domain

The concrete domain of a regular abstract interpretation is T (Σ). The elements of the abstract domain of a regular abstract interpretation are arbitrary symbols, each of them representing a regular set of terms of T (Σ). One originality of our approach resides in representing an abstract domain by a tree automaton, where each state corresponds to a regular set of terms. More precisely, an abstraction is represented by a tree automaton Λ = Σ, Σ # , Σ # , ∆ # where Σ # is the set of abstract values and ∆ # a term rewriting system defining the associated abstraction function α that describes how concrete terms are abstracted into abstract values. For readability, we write Λ = Σ # , ∆ # . In the previous example, Λ can be defined using the following

∆ # : a → ab # b → ab # nil → ab_list # cons(ab # , ab_list # ) → ab_list #
Note that a configuration of Λ is a term of T (Σ, Σ # ), a mix between abstract and concrete terms which allows the transition from the concrete domain to the abstract one. Each term of T (Σ, Σ # ) is called an abstract pattern.

Definition 5.2.1 (Regular Abstract Domain). Let Σ be an alphabet. A T (Σ)abstraction Λ = Σ # , ∆ # is a regular abstract domain iff the corresponding tree automaton Σ, Σ # , Σ # , ∆ # is normalized and complete. The abstraction and concretization functions are then defined by

α(t) = {a # | t → * ∆ # a # } γ(a # ) = {t | t → * ∆ # a # }
Note that in this definition α returns the set of all possible abstract values for any term t. Since Λ can be non-deterministic, this set may contain more than one abstract element.

Abstract Semantics

By rewriting a term t using ∆ # , we can abstract some subterms of t by elements of Σ # . However, this abstracted term can no longer be rewritten using R. We need to introduce a new rewriting system R # as an abstraction of R, rewriting abstract patterns while preserving the behavior of R. To deduce a property of R using R # , the abstract R # must itself respect specific constraints w.r.t. R. Definition 5.2.2 (Abstraction of a TRS). Let R be a TRS, Λ = Σ # , ∆ # a regular abstract domain over IRR(R). Let R # be a TRS over

T (Σ # ). R # is an abstraction of R over Λ iff • every rule of R # has the form f (a # 1 , . . . , a # n ) → a # with f ∈ Σ n , a # 1 , . . . , a # n , a # ∈ Σ # ; • (soundness) for all rules f (a # 1 , . . . , a # n ) → a # of R # , for all terms t of {f (t 1 , . . . , t n ) | t i ∈ γ(a # i )}, for all terms u ∈ IRR(R) such that t → * R u then u ∈ γ(a # );
• (completeness) for all symbols f used in R # , for all terms t = f (t 1 , . . . , t n )

where t i ∈ IRR(R) for all i, there must exist some rule f (a # 1 , . . . , a # n ) → a # such that for each i, t i ∈ γ(a # ). In other words, for a given used symbol, each possible abstract input is mapped to at least one abstract output.

Theorem 5 (Correctness). Let R be a TRS, Λ = Σ # , ∆ # an abstract domain over IRR(R). If R # is an abstraction of R over Λ then we have for all pattern p, abstract substitution π and abstract value v # ∈ Σ # :

pπ → * R # ∪∆ # u # ⇒ ∀σ ∈ γ(π). ∀u ∈ IRR(R). pσ → * R u ⇒ u ∈ γ(u # ) with γ(π) = { σ | ∀x ∈ Dom(π). σ(x) ∈ γ(π(x)) } the set of all possible concretized substitutions.
Proof. This can be proved by a simple induction on the size of the (abstract) rewriting path using the soundness property of R # . If pπ = u # then p is some variable x. For all σ ∈ γ(π), pσ is a ground term. So for all u ∈ IRR(R) such that pσ → * R u we have u = pσ, with u ∈ γ(u # ). Next, if pπ → k+1 R # ∪∆ # u # . By definition (of R # and Σ # ), there exists a context C, a pattern l = f (l 1 , . . . , l n ) and an abstract value r # such that p

= C[l] and pπ → R # ∪∆ # C[r # ] → k R # ∪∆ # u # . Now let us consider σ ∈ γ(π) and u ∈ IRR(R). Since C[l]π → R # ∪∆ # C[r # ]
then by definition of R # (soundness of abstraction R # ) there exists some term r such that lσ → * R r so that C[l]σ → * R C[r] which we can write as (1) pσ → * R C[r]σ. Finally let x be a fresh variable and π be the abstract substitution such that π = π ∪ {x → r # }. By construction we have

C[r # ]π = C[x]π . Using the induction hypothesis on C[x]π → * R # ∪∆ # u # , we get that (2) ∀σ ∈ γ(π ). ∀u ∈ IRR(R). C[x]σ → * R u ⇒ u ∈ γ(u # ). Since π = π ∪ {x → r # }, for all σ ∈ γ(π ) there exists r ∈ γ(r # ) and σ ∈ γ(π) such that σ = σ ∪ {x → r}. In this case, C[x]σ = C[r]σ and we can connect (1) and (2) to obtain that pσ → * R u ⇒ u ∈ γ(u #
). Note that it is not sufficient to have some abstraction of R to be able to verify the desired property. On our previous example, the following TRS is a correct abstraction of R:

sort ab_list # → ab_list # sorted ab_list # → bool #
however since all lists of as and bs are always abstracted by the same element ab_list # , this abstraction is too coarse to prove that for all list l, sorted (sort l) → * R false. To succeed, we need to make sure that our abstraction provides the additional property:

∀v # . sorted (sort v # ) → * R # ∪Λ false # ⇒ ∀l. sorted (sort l) → * R false
Another way to phrase it is for R # to be "complete" w.r.t. sorted (sort l) and f alse # , according to the following definition:

Definition 5.2.3 (Complete abstraction). Let R # be a TRS abstraction of R over Λ = Σ # , ∆ # . We say that R # is complete with regard to a pattern p and abstract value v # when for all term t ∈ γ(v # ), for all substitution σ : X → T (Σ), if pσ → * R t then there exists an abstract substitution π : X → Σ # such that σ ∈ γ(π) and pπ → * ∆ # ∪R # v # . This can be seen as the the contraposition of Theorem 5 for a particular case of p and v # .

In our example, for R # to be complete w.r.t. sorted (sort l) and false # we need at least three elements to abstract lists of as and bs: sorted # , unsorted # , and b_list # recognizing respectively sorted a and b lists, unsorted lists, and lists of bs. The abstract domain Λ becomes:

a → a # cons(b # , b_list # ) → b_list # cons(b # , sorted # ) → unsorted # b → b # cons(a # , b_list # ) → sorted # cons(b # , unsorted # ) → unsorted # nil → b_list # cons(a # , sorted # ) → sorted # cons(a # , unsorted # ) → unsorted #
and the abstract TRS R # becomes:

sort b_list # → b_list # sorted b_list # → true # sort sorted # → sorted # sorted sorted # → true # sort unsorted # → sorted # sorted unsorted # → false #
Using those abstract elements, domain and TRS, we can show:

sorted (sort sorted # ) → * R # ∪Λ false # sorted (sort unsorted # ) → * R # ∪Λ false # sorted (sort b_list # ) → * R # ∪Λ false #    ⇒ ∀l ∈ I. sorted (sort l) → * R false
To use this for verification, we need to infer Λ with an R # that is complete with regard to our desired property: this is the inference problem we solve in this chapter.

Definition 5.2.4 (Inference problem). Assume that we are given a TRS R, a pattern p ∈ T (Σ, X ), an initial abstract domain

Λ * = Σ # * , ∆ # * and a target abstract value v # ∈ Σ # * . A solution to the inference problem is 1. an abstract domain Λ = Σ # , ∆ # such that Σ # ⊇ Σ # * and ∆ # ⊇ ∆ # * ;
2. an abstraction R # of R in Λ, complete w.r.t. p and v # ;

3. the set Π of all the abstract substitutions π such that pπ

→ * ∆ # ∪R # v # .
Intuitively, a solution provides a set of substitutions from variables to abstract values such that if the pattern rewrites to a result that belongs to v # then there is an abstract substitution in Π such that the pattern rewrites to v # . Note that if the resulting set Π is empty, we can deduce that for all t ∈ γ(v # ), for all substitution σ we have pσ → * R t.

Abstraction Inference Challenges

To automatically solve inference problems, we designed an abstraction refinement procedure where an initial coarse abstraction is iteratively refined from the informations gathered at previous iterations, until it converges to a satisfying abstraction able to prove or disprove the desired property. A classical approach to abstraction refinement is the Counter-Example Guided Abstraction Refinement (CEGAR) procedure [CGJ + 00], where the refinement information comes from spurious counterexamples introduced by the considered abstractions. In our setting, using CEGAR would mean to start the procedure with some initial abstractions R # and Λ, and search for spurious counter-examples introduced by the abstraction to refine it. For instance, consider the inference problem defined by p = (sorted l) and v # = false # . By Definition 5.2.4 we want to find an abstract domain Λ = Σ # , ∆ # (with false # ∈ Σ # and false → false # ∈ ∆ # ) an abstraction R # of R in Λ that is complete w.r.t. p and false # . Assume we start from the basic abstraction Λ where ∆ # is defined by

true → true # false → false # a → ab # b → ab # nil → ab_list # cons(ab # , ab_list # ) → ab_list #
and the initial abstraction R # :

sorted ab_list # → true # sorted ab_list # → false #
Obviously this abstraction is too coarse because it does not distinguish between sorted and unsorted lists. As a result we get:

sorted ab_list # → * R # ∪Λ true #
even though there exists an (unsorted) list l ∈ γ(ab_list # ) such that sorted l → * R true R # is not a sound abstraction of R, which is required to be a valid solution of our inference problem. We need to refine it. To do that using CEGAR, we need to find such a (sorted l) that does not rewrite to true. It is a counter-example to the soundness of the abstraction, and we must make sure it does not occur again in the refined abstraction. For instance here, a possible counter-example is l = cons(b, cons(a, nil)), where l ∈ γ(ab_list # ) and sorted l → * R true. In the next abstraction we need to abstract l into an abstract value l # such that

sorted l # → * R # ∪Λ true #
To concretely design such a procedure we have to solve three problems. First, building a new abstract domain by separating only one concrete value at each refinement step may lead to non-termination. For instance in our example, one could refine the abstraction by separating cons(b, cons(a, nil)) from the other lists, then separating cons(a, cons(b, cons(a, nil))) at the next iteration and so on. If we do this, we end up creating infinitely many abstract values, one for each unsorted list instead of creating one single abstract value for all unsorted lists. Remember that this is the cause of the incompleteness of the PMRS abstraction procedure designed by Ong et al. [START_REF] Ong | Verifying higher-order functional programs with pattern-matching algebraic data types[END_REF] as showed in Section 3.3.1. Thankfully, there are ways to explore the set of possible abstractions so that the refinement procedure is guaranteed to terminate if there exists a regular abstraction R # and Λ proving or disproving the property. These exploration techniques generally uses SMT solvers to explore the set of possible abstractions Σ # w.r.t. their cardinal [START_REF] Matsumoto | Automata-based abstraction for automated verification of higher-order tree-processing programs[END_REF]. The second problem is that, given an abstract domain, finding spurious counter-examples is not easy. Indeed, the TRS R # does not explicitly encode the rewriting relation between terms. As a consequence, even if we know that a counter-example may exist, extracting a concrete rewriting sequence leading to the counter-example from R # and Λ is computationally expensive. Moreover, in the above example, the rewritings only depend on a single function: sorted. In practice, to find counter-examples on complex programs it is necessary to explore a much larger abstraction of the whole program. Thus, the computational cost for finding a counter-example grows with the size of the program to verify. Finally, the third problem is that the "abstract and refine" procedure presented above is not modular. From a set of constraints that we extract from a spurious counter-example, the next abstraction is recomputed for the whole program. As a result the size of the program directly and greatly impacts the efficiency of two main steps of the procedure: the search for counter-examples and the abstraction refinement. This is why we want to define a modular procedure able to analyze functions independently and whose termination is guaranteed if there exists a regular abstraction satisfying the property. As we will see in Section 5.3.4, contrarily to classical CEGAR, our procedure extracts counter-examples directly from the concrete TRS itself instead of the abstraction to reduce the computation cost. To ensure completeness, we also use an SMT-based technique exploring possible abstractions w.r.t. the size of the abstract domain Σ # . Finally for modularity, we design a type system attaching abstraction information to each function (to each symbol of the TRS). In the next section, we show how to translate the above inference problem into a type inference problem over regular language types, that will allow us to design a modular inference procedure for those types.

Regular Language Types

A convenient way of modularizing the abstract interpretation is by introducing a type system to attach abstraction information to each term and symbol. In this approach, the set of types is the abstract set of values Σ # and a term has type τ if it rewrites to τ using ∆ # and R # . In practice, each type represents a regular language. A type substitution π ∈ X → Σ # maps variables to types. From this perspective, we say that the abstract semantics R # is the type environment of symbols. In the following, we define a typing judgment which can be used to give types to patterns, relative to a given Λ, R # and substitution π which assigns a type to each variable of the pattern. The typing judgment Λ, R # , π p : τ means that the pattern p ∈ T (Σ, X ) can be typed with τ ∈ Σ # using the substitution π, Λ and R # . Definition 5.3.1 (Typing rules). Let Λ = Σ # , ∆ # be an abstraction of T (Σ), R # a type environment and π a substitution from X to Σ # . We define the typing judgment via the following inference rules. In the rules we use the rewriting rules p → τ of ∆ # to deduce a type for constructor and function applications. Recall that Σ is the disjoint union of constructor symbols C and function symbols F.

var π(x) = τ Λ, R # , π x : τ constructor Λ, R # , π p 1 : τ 1 . . . Λ, R # , π p n : τ n f ∈ C f (τ 1 , . . . , τ n ) → τ ∈ ∆ # Λ, R # , π f (p 1 , . . . , p n ) : τ sub-typing Λ, R # , π f (p 1 , . . . , p n ) : τ τ → τ ∈ ∆ # Λ, R # , π f (p 1 , . . . , p n ) : τ application Λ, R # , π p 1 : τ 1 . . . Λ, R # , π p n : τ n f ∈ F f (τ 1 , . . . , τ n ) → τ ∈ R # Λ, R # , π f (p 1 , . . . , p n ) : τ
The var rule uses the type substitution π to type a single variable. The application rule follows the abstract semantics R # to type a pattern which can be rewritten. The constructor rule uses the abstraction Λ, and in particular ∆ # , to give a type to patterns built from a constructor symbol. The sub-typing rule does the same using the -transitions of ∆ # . From this rule we extract a sub-typing relation where

τ 1 τ 2 means that τ 1 → * ∆ # τ 2 .
The typing judgment definition makes a bridge between the rewriting world and the typing world. Each typing rule correspond to a rewriting step with either R # which becomes the type environment, or ∆ # which includes the types definitions. The following lemma states the correctness of the type system using the abstract interpretation defined in the previous section. Its proof uses the rewriting system R # ∪∆ # as an intermediate step between the type system and abstract interpretation.

Theorem 6 (Correctness). Let R be a TRS, Λ = Σ # , ∆ # an abstraction of T (Σ) and R # an abstraction of R over Λ. For all patterns p and types τ (which are abstract values of Σ # ) we have:

Λ, R # , π p : τ ⇐⇒ pπ → * R # ∪∆ # τ
Proof. First, let's show that Λ, R # , π p : τ implies pπ → * R # ∪∆ # τ . We show this with a simple induction on the type inference rules.

• (var) Λ, R # , π x : τ . Then π(x) = τ , p = x and thus pπ = τ .

• (constructor) Λ, R # , π f (p 1 , . . . , p n ) : τ with f ∈ C. Then Λ, R # , π p i : τ i . By induction hypothesis p i π → * R # ∪∆ # τ i . By definition f (τ 1 , . . . , τ n ) → τ ∈ ∆ # so f (p 1 , . . . , p n )π → * R # ∪∆ # τ . • (sub-typing) Λ, R # , π f (p 1 , . . . , p n ) : τ with Λ, R # , π f (p 1 , . . . , p n ) : τ and τ → τ ∈ ∆ # . By induction hypothesis we have that f (p 1 , . . . , p n )π → * R # ∪∆ # τ . Using τ → τ ∈ ∆ # we conclude that f (p 1 , . . . , p n )π → * R # ∪∆ # τ . • (application) Λ, R # , π f (p 1 , . . . , p n ) : τ with f ∈ F. Then Λ, R # , π p i : τ i . By induction hypothesis, p i π → * R # ∪∆ # τ i . By def. f (τ 1 , . . . , τ n ) → τ ∈ R # so f (p 1 , . . . , p n )π → * R # ∪∆ # τ .
Then we show that pπ → * R # ∪∆ # τ implies Λ, R # , π p : τ , by strong induction on the length of the rewriting path. If pπ = τ then p is a variable x and we have π(x) = τ . By the inference rule (var) we have Λ, R # , π x : τ . Now if pπ → k+1 R # ∪∆ # τ , we have the following cases:

• pπ → k R # ∪∆ # f (τ 1 , . . . , τ n ) → ∆ # τ with f ∈ C and p = f (p 1 , . . . , p n ). For all i = 1 . . . n, we have p i π → k i R # ∪∆ # τ i with k i ≤ k.
Applying the induction hypothesis we get Λ, R # , π p i : τ for i = 1 . . . n. The constructor rule then gives Λ, R # , π p : τ .

• pπ → k R # ∪∆ # τ → ∆ # τ .
Using the induction hypothesis on pπ → k R # ∪∆ # τ we get that Λ, R # , π p : τ . We can then apply the sub-typing rule to get Λ, R # , π p : τ .

• pπ → k R # ∪∆ # f (τ 1 , . . . , τ n ) → R # τ with f ∈ F and p = f (p 1 , . . . , p n ). For all i = 1 . . . n, we have p i π → k i R # ∪∆ # τ i with k i ≤ k.
Thus, we can apply the induction hypothesis and get Λ, R # , π p i : τ for i = 1 . . . n. Applying the application rule gives Λ, R # , π p : τ .

Remark 5.3.1. Recall that we encode higher-order programs using the dedicated @ symbol (Section 2.2.3). For instance @(@(f, x), y) is the total application of f on two parameters x and y. A reader familiarized with usual ML-like type systems may notice the lack of arrow type for partial applications such as @(f, x). We do not need them in our type system as they can be represented using regular languages, just like any other type. The ML-type τ 1 → τ 2 is represented by the regular language of all terms t ∈ T (Σ) such that the application @(t, x) rewrites to a term of τ 2 when x rewrites to a term of τ 1 .

Example 5.3.1. Consider R defining a (buggy) delete function as (d1) delete x nil → nil (d2) delete x cons(y, l) → if (eq(x, y), delete x l, delete x l)

The definition of the equality predicate eq and of the if-then-else symbol if are omitted but present in R. The delete function is supposed to remove every occurrence of x in the given list. In the last rule however, we forgot to put y back in the list when x = y. As a result, this delete function always returns nil . This can be spotted by noticing that there exists an abstraction Λ and R # such that Λ, R # , π delete x l : nil # with π(x) = ab # and π(l) = ab_list # . Let the abstraction Λ of T (Σ) be defined by

a → ab # true → bool # nil → ab_list # nil → nil # b → ab # f alse → bool # cons(ab # , ab_list # ) → ab_list # nil # → ab_list #
and let the abstraction R # of R over Λ be defined by

eq(ab # , ab # ) → bool # if (bool # , nil # , nil # ) → nil # delete ab # ab_list # → nil #
It is then easy to show Λ, R # , π delete x l : nil # by the following typing derivation (Λ, R # , π are omitted to preserve the readability):

π(x) = ab # x : ab # π(l) = ab_list # l : ab_list # delete ab # ab_list # → nil # ∈ R # delete x l : nil #
It is possible to build an abstraction R # from Λ and R so as to give the most precise regular type information to each function. The outline of the procedure is as follows: For each function f of R, for every combination of input and output types, make the hypothesis that this combination is valid and try to type both sides of each rule defining f in R with this combination of types. If it is possible, then the combination is valid. From the set of valid combinations, keep the most precise.

Example 5.3.2. In the previous example, the combination delete ab # ab_list # → bool # is not valid for the delete function since it is impossible to type the right-handside of the rule (d1) of delete with bool # . The combination delete ab # ab_list # → nil # that has been selected is valid. The right-hand side of the rule (d1) of delete has the type nil # by the typing derivation:

nil → nil # ∈ ∆ # nil : nil #
The typing judgment of the right-hand-side of rule (d2) with nil # comes from the following derivation (where we omit the rules of if , eq and delete in R # for readability):

π(x) = ab # x : ab # π(y) = ab # y : ab # eq(x, y) : bool # π(x) = ab # x : ab # π(l) = ab_list # l : ab_list # delete x l : nil # if (eq(x, y), delete x l, delete x l) : nil #
Note that the combination (delete ab # ab_list # → ab_list # ) is also valid for the delete function, but is less precise than (delete ab # ab_list # → nil # ).

This example illustrates that using this type system and a given abstraction Λ of T (Σ), there exists a procedure to build an abstraction R # of R from Λ that gives precise information about the functions of the program. In this example, it is convenient to have nil # as part of Λ, allowing us to type the delete function with nil # as output, which in turns allowed us to spot the mistake in delete (cf. Section 5.4). In general however, Λ does not contain enough information to prove or disprove the wanted property. The abstraction Λ must be refined along with R # . This transforms the abstraction inference problem introduced in the previous section into the following type inference problem.

Definition 5.3.2 (Regular Language Type Inference Problem). Let R be a term rewriting system and p a pattern of T (Σ, X ). Let Λ * = Σ # * , ∆ # * be an initial abstract domain, and τ ∈ Σ # * a (target) type. A solution to the type inference problem is 1) an abstract domain

Λ = Σ # , ∆ # such that Σ # ⊇ Σ # * and ∆ # ⊇ ∆ # * ; 2) an abstraction R # of R in Λ, complete w.r.t. p and v # ; 3) the set Π of all the type environments π such that Λ, R # , π p : v # .
Note that type inference problems are usually concerned with finding some type substitution π such that π p : τ . In our case however, since we want to capture the entire behavior of the input pattern with regard to the target type τ , we are interested in finding an abstraction containing all such type substitutions, in Π. For instance if we consider the pattern p = xor(x, y) with the target type τ = true # , a solution to the regular language type inference problem must include Λ, R # with Π containing π 1 , π 2 such that π 1 = {x → true # , y → false # } and π 2 = {x → false # , y → true # }. These two substitutions are necessary (and sufficient) to capture the entire behavior of the xor function on its input with regard to the target type true # .

Type Partitions

The type inference procedure we define in this chapter is fundamentally an inductive inference procedure working on the structure of the given input pattern. However having multiple possible type environments for a single pattern and target type is not convenient, since we would need to analyze every case and "split" the analysis at each induction step.

Example 5.3.3. We want to type the term xor(f (x), y) with target type true # . The only possible abstraction for xor separating true and false is the following R # :

xor(true # , true # ) → false # xor(false # , true # ) → true # xor(true # , false # ) → true # xor(false # , false # ) → false #
This means that to have xor(f (x), y) : true # , we may have either f (x) : true # or f (x) : false # . We need to analyse both cases, splitting the analysis of f into two branches. Then, depending on the definition of f , we may need to split again each of the two branches to analyze x, etc. This can result into an exponential blow-up.

Instead, we would like to have one single environment to pass along. The fundamental idea to achieve this is to use type partition environments instead of using type environments. A type partition is a set of types which represent non-overlapping (regular) sets of values and which together cover the whole domain of values.

Definition 5.3.3 (Type Partition). Let Λ = Σ # , ∆ # be an abstraction of T (Σ). A set T = { τ 1 , . . . , τ n } ⊆ Σ # is a type partition if for all i, j ∈ [1, n], γ(τ i ) ∩ γ(τ j ) = ∅ when i = j, and τ ∈T γ(τ ) = T (Σ)
We write P # (Λ) the set of type partitions defined over Λ. Type partitions form a semi-lattice w.r.t. , where T 1 T 2 iff for all element a # 1 of T 1 there exists an element a # 2 of T 2 such that γ(a # 1 ) ⊆ γ(a # 2 ). The greatest lower bound of two partitions T 1 and T 2 is the partition T with the fewest elements such that T T 1 and T T 2 . Following tree automata usage, we call this the product T 1 ⊗ T 2 . Definition 5.3.4 (Type Partition Environment). Let Λ be an abstraction of T (Σ). A type partition environment is a mapping from variables to type partitions X → P # (Λ). If Π is a set of type environments (X → Σ # ), then we write Π the type partition environment where for all variable x ∈ X , Π(x) is the product of all the types associated to x in Π:

Π(x) = ⊗ π∈Π { π(x), π(x) }
where τ refers to the element of Σ # complement of τ such that γ(τ ) = γ(τ ) (we assume such element exists since we can always add it otherwise). We call { π(x), π(x) } the partitioned domain of the variable x in π.

Example 5.3.4. Let Σ # = { a # , b # , c # , ab # , bc # }. We assume that γ(ab # ) = γ(a # ) ∪ γ(b # ) and γ(bc # ) = γ(b # ) ∪ γ(c # ). Let x be a variable, π 1 = { x → a # }, π 2 = { x → c # }, and Π = { π 1 , π 2 }. The partitioned domain of x in π 1 is { a # , bc # } since a # = bc # . The partitioned domain of x in π 2 is { ab # , c # }. The partitioned domain of x in Π is Π(x) = { a # , bc # } ⊗ { ab # , c # } = { a # , b # , c # }.
To solve the regular language type inference problem of a pattern p for the target type τ , it is sufficient to compute Π instead of Π. Indeed from Π we can extract another set of type environments, Π = { π | ∀x ∈ V ar(p). π(x) ∈ Π(x) }, for which by construction for all π ∈ Π we have either π p : τ or π p : τ . So Π is a super-set of Π extracted from Π. Hence in the rest of this chapter, we shall focus on finding Π instead of Π. This is the type partition inference problem.

Definition 5.3.5 (Regular Language Type Partition Inference Problem). Let R be a term rewriting system and p a pattern of T (Σ, X ). Let Λ * = Σ # * , ∆ # * be an initial abstract domain, and a target type partition T ∈ P(Σ # * ). A solution to the type partition inference problem is 1) an abstract domain

Λ = Σ # , ∆ # such that Σ # ⊇ Σ # * and ∆ # ⊇ ∆ # * ;
2) an abstraction R # of R in Λ, complete w.r.t. p and every v # ∈ T ; 3) a type partition environment Π, from which we can derive

Π = { π | ∀x ∈ V ar(p). π(x) ∈ Π(x) } containing all the substitutions π such that Λ, R # , π p : v # with v # ∈ T .
Example 5.3.5. We return to the previous xor example and type the term xor(f (x), y) with the target type partition {true # , false # }. Again, the only possible abstraction for xor in R # is to have

xor(true # , true # ) → false # xor(false # , true # ) → true # xor(true # , false # ) → true # xor(false # , false # ) → false #
But now we only have one single inductive case to type f , which is to consider f (x) with the type partition { true # , false # }. If we assume that f is the identity function, then the result of the analysis gives

Π = { x → { true # , false # }, y → { true # , false # } }.
The resulting Π includes the four possible combinations, which are

π 1 = {x → true # , y → true # }, π 2 = { x → true # , y → false # }, π 3 = { x → false # , y → true # } and π 4 = { x → false # , y → false # }.
The rest of this section defines an inference procedure for solving this type partition inference problem. We first present the general algorithm. We then present the invariant learning procedure which uses SMT solving for minimizing the size of the abstraction automaton.

Inference Algorithm

This section introduces the type inference algorithm for any TRS given on the next page as Algorithm 2. The main function of this algorithm, partitions-inference is in charge of solving the type partition inference problem, that is to find correct type partitions for every variable of a given pattern we wish to type with a given type partition. This algorithm uses the auxiliary functions analyze-function and merge. The role of the merge function will be detailed below. The role of analyze-function is to compute the type partitions signature of a given symbol f for a given output type partition T . This correspond to the input type partitions needed for every term t 1 , . . . , t n to type f (t 1 , . . . , t n ) with the given output type partition T . For instance, the expected type partition signature of xor of Example 5.3.5 for the output partition {true # , f alse # } is ({true # , f alse # }, {true # , f alse # }) → {true # , f alse # }. Definition 5.3.6 (Type Partitions Signature). Let R be a TRS, Λ = Σ # , ∆ # an abstract domain, and R # an abstraction of R defined over Λ. Let f be a symbol of Σ. Let T, T 1 , . . . , T n be type partitions over Σ # . We say that (T 1 , . . . , T n ) → T is Proof. By induction on the pattern p.

• If p = x, then the output of partitions-inference is Λ = Λ * , R # = ∅ and Π = {x → T }. We have (1) Σ # ⊆ Σ # * by construction (2) R # is an abstraction of R since it is empty (3) for all π ∈ Π, if we take τ = π(x) then π, Λ, R # x : π. • In the other case, if p = f (p 1 , . . . , p n ), then we have Λ , R # , (T 1 , . . . T n ) → T = analyze-function(R, Λ * , f, T ).
Since we assume that analyze-function is correct, we known that (T 1 , . . . , T n ) → T is the type partitions signature of f in Λ , R # . We write Σ # i , Λ # i , R # i and Π i for the outputs of partitionsinference(R, Λ , R # , p i , T i ) for all i. By hypothesis of induction, we know those are solutions to the type partition inference problem with R, Λ , R # , p i , T i as inputs. Then by definition of merge and Lemma 22, we conclude that the returned Σ # , Λ, R # , Π are solutions to the type partition inference problem of p with Λ * , R and T . This algorithm is independent of the actual implementation of analyze-function. We detail in Section 5.3.3 a direct implementation of analyze-function that efficiently computes the input partitions signature, but only for non-recursive functions. In Section 5.3.4, we propose an implementation of analyze-function based on a regular language invariant learning procedure that can analyze any function, including recursive functions. In our implementation (cf. Section 5.4), we combine the two versions to be more efficient.

Non-Recursive TRS

In the case of non-recursive TRSs, it is possible to directly find a solution to the type partition inference problem, without relying on any learning technique needed in the general case. We give the following implementation of analyze-function as Algorithm 3 that works in this specific setting. This algorithm makes the distinction between two cases: constructor symbols for which no rules of R apply, and functional symbols for which at least one rule apply. The constructor case is handled using a projection operator whose idea is as follows: If a regular language L is shaped as {f (t 1 , . . . , t n ) | t 1 ∈ L 1 , . . . , t n ∈ L n }, then the projection of L on the symbol f at position i is L i . For instance, if we consider the language of lists of As and Bs defined by L = {nil, cons(A, nil), cons(B, nil), cons(A, cons(A, nil)), . . .}, then proj(L, cons, 1) is {A, B} and proj(L, cons, 2) = L. In our case, since a type basically represents a regular language, we directly define the projection on types. We then further extend this definition to partitions of types. Definition 5.3.7 (Type Projection). Let Λ = Σ # , ∆ # . The projection of the type τ ∈ Σ # on a given symbol f at position i is

proj(τ, f, i) = unify {τ i | f (τ 1 , . . . , τ i , . . . , τ n ) → * τ ∈ ∆ # }
where unify is the type unification function. We write unify({τ 1 , . . . , τ n }) for the most precise type τ w.r.t.

such that for all i, 1 ≤ i ≤ n, τ i τ . 3 By convention, we write proj(τ, f, i) = ⊥ when the projection is not defined, that is when f is not 1 function analyze-function input : A TRS R, an initial abstract domain Λ * , a symbol f ∈ F n and an output type partition T output : An abstraction R # and a partitions signature

(T 1 , . . . , T n ) → T of f in R # 2
Let R f be the set of rules applicable on f ;

3 if R f is empty then / *
8 rule k = f (p 1 , . . . , p n ) → r ∈ R do 9 Let Λ k , R # k , (T k,1 , . . . , T k,n ) ← rule-signature(R, Λ * , rule k , T ); 10 Let Λ ← Λ 1 ∪ • • • ∪ Λ K ; 11 Let R # ← R # 1 ∪ • • • ∪ R # K ; 12 return merge-rules(Λ, R # , (T 1,1 , . . . , T 1,n ), . . . , (T K,1 , . . . , T K,n ))
Algorithm 3: Non-Recursive Function Analysis recognized by τ in ∆ # . If we consider the type partition T = τ 1 , . . . , τ n , the projection of T on the symbol f at position i is defined as

proj(T, f, i) = remove ⊥ {proj(τ 1 , f, i), . . . , proj(τ n , f, i)}
where remove ⊥ filters out the ⊥ element from the set. Note that proj(T, f, i) is also a type partition.

The projection operator (Definition 5.3.7) is used in the algorithm to directly find the type partition signature of a constructor symbol. We can show that picking the projection of every sub-position of the constructor symbol on the target partition gives us the type partition signature of this symbol for this target type partition.

Lemma 23 (Projection on Constructor). Let p = f (p i , . . . , p n ) be a non-functional pattern (without function symbol) and T a type partition. Then (T 1 , . . . , T n ) → T where

T i = proj(T i , f, i) is a type partitions signature of f .
Proof. Let (τ 1 , . . . , τ n ) be a combination of (T 1 , . . . , T n ). Let τ ∈ T be the type such that for all i, τ i = proj(τ, f, i). Let π be a type environment such that Λ # , R # , π p i : τ i . By definition of the projection, we have f (τ 1 , . . . , τ n ) → * Λ # τ . We can then conclude by construction of the type judgment rules that we have Λ # , R # , π p : τ .

Finding the type partitions signature of a functional symbol is a bit harder since it involves following the rewriting rules associated to this symbol. To do that that we first introduce the notion of "rule type partitions signature". Informally, for a given rule of a symbol, a rule type partitions signature is a type partitions signature for the symbol that is correct when considering this symbol's rule alone. This allows us to analyze every rule separately. We then show that we can merge all the rule signatures into one symbol signature.

(T k,1 (τ 1 ), . . . , T k,n (τ n )) is a combination of the rule signature (T k,1 , . . . , T k,n ) → T of the rule k with Λ, R # , we know there exist a type τ ∈ T such that π, Λ, R # r k : τ . Since R # is an abstraction of R for Λ, by definition this means that u ∈ γ(τ ). Now let's show R # is a complete abstraction of R. Let us consider any (τ 1 , . . . , τ n ) where τ i ∈ T i . First note that R f is not empty. Since the TRS is complete, there exists at least one rule f such that Π k (τ 1 , . . . , τ n ) is not empty. Since R # is an abstraction of R, there exists a unique τ such that for all rule k and all π ∈ Π k (τ 1 , . . . , τ n ), π, Λ, R # r k : τ . Then by definition there exists the rule

f (τ 1 , . . . , τ n ) → τ in R # .
The last missing piece left is the rule-signature algorithm that will allow us to actually compute the signature of each rule. For the considered rule l → r, it will type the right-hand-side r of the rule in order to find the type partition of each variable (for this rule), and deduce a type for the left-hand-side from the found type partitions.

1 function rule-signature input : A TRS R, an initial abstract domain Λ * , a rule f (p 1 , . . . , p n ) → r ∈ R and an output type partition T output : An abstraction R # and the rule signature

(T 1 , . . . , T n ) → T of the given rule in R # 2 Let Λ, R # , Π ← partitions-inference(R, Λ * , r, T ); 3 foreach sub-pattern p i do 4 Let Λ i , T i ← bottom-up-type-pattern(Λ, p i , Π); 5 Let Λ ← Λ 1 ∪ • • • ∪ Λ n ; 6 return Λ , R # , (T 1 , . . . , T n )
The goal of the bottom-up-type-pattern function is to give a fitting type partition to the left-hand-side of the rule from a given type partition environment. This type can easily be built by typing the pattern from bottom to top.

Example 5.3.7 (Bottom-Up Typing). Let us imagine we wish to type the irreducible pattern cons(s(x), y) with the type partition environment

Π = {x → {0 # , N + # }, y → {[nat list] # }} where ∆ # is 0 → 0 # nil → [nat list] # s(0 # ) → N + # cons(0 # , [nat list] # ) → [nat list] # s(N + # ) → N + # cons(N + # , [nat list] # ) → [nat list] # Since Π(x) = {0 # , N + # } we can create two new types [s(0)] # and [s(N +)] # in Σ # with the associated rules s(0 # ) → [1] # and s(N + # ) → [2+] # in ∆ # such that a valid type partition for s(x) is {[1] # , [2+] # }.
We also need to add the sub-typing rules

[1] # → N + # and [2+] # → N + # in ∆ #
. Now that we have a partition for s(x), we can continue up and type the whole pattern. This gives us the partition

{[cons(1, nat list)] # , [cons(2+, nat list)] # } associated to the rules cons([1] # , [nat list] # ) → [cons(1, nat list)] # cons([2+] # , [nat list] # ) → [cons(2+, nat list)] #
The actual specification of the bottom-up-type-pattern function is given by the following definition. a rough abstraction is iteratively refined using constraints learned from previous iterations. Those new constraints are defined by finding a spurious counter-example generated because of a faulty previous abstraction. Constraints are accumulated until no spurious counter-example can be found in which case the invariant has been found, or by finding a real counter-example. In this section we show how to use the Tree Automata Completion Algorithm [START_REF] Genet | Equational approximations for tree automata completion[END_REF] to adapt this family of techniques to Term Rewriting Systems and Regular Languages, allowing us to learn recursive symbol partitions signatures.

1 function analyze-function input : An input TRS R, an initial abstraction Λ * , a function symbol f , and a target type partition T . output : An abstraction Λ ⊇ Λ * ,an abstraction R # of R over Λ and a type partitions signature

(T 1 , . . . , T n ) → T of f in R # 2 let L = {f (t 1 , . . . , t n ) | t 1 . . . t n ∈ IRR(R)}; 3 let A 0 = finite-subset(L); 4 let i = 0; 5 forever / * (1) Tree Automata Completion * / 6 let A * i = tree-automata-completion(A i , R); / * (2) Counter-example/constraints generation * / 7 let φ = S(A * i , T ); 8 if φ is unsatisfiable then return counter-example ; / * (3) Abstraction * / 9 let A # i = φ(A * i ); / * (4) Validity Check / Termination * / 10 if A # i is R-closed and IRR-complete then 11 let R # = { f (τ 1 , . . . , τ n ) → τ | f ∈ F }; 12 let ∆ # = { f (τ 1 , . . . , τ n ) → τ | f ∈ C }; 13 let Λ = states_of (A # ), ∆ # ; 14 let T i = { τ i | f (. . . , τ i , . . . ) → τ ∈ R # }; 15 return Λ, R # , (T 1 , . . . , T n ) → T 16 else 17 A i+1 = grow (A i , A # i ); 18 i = i + 1; Algorithm 4: Invariant Learning Procedure
To find a type partition signature for a symbol f with the target type partition T , the procedure showed as Algorithm 4 computes a series of tree automata A # 0 , A # 1 , . . . according to the following outline: 1. We start by using the Tree Automata Completion Algorithm on R and an automaton A 0 recognizing a finite subset L 0 of the language

L = {f (t 1 , . . . , t n ) | t 1 . . . t n ∈ IRR(R)}.
The result is an automaton A * 0 recognizing R * (L 0 ). This automaton is guaranteed to exist if R is terminating [START_REF] Genet | Equational approximations for tree automata completion[END_REF]. 2. We then check for any counter-example: any input term that violate the target type partition T by rewriting to two different types of the partition. For now, no abstraction has been done. If a counter-example is found in A * 0 recognizing R * (L 0 ), it is a real counter-example, no such signature exists for f and the property is disproved. Otherwise, using A * 0 and T , we build a set of disequality constraints over its states. 3. We then merge the states of A * 0 according to those disequality constraints to build an abstraction A # 0 as the smallest automaton respecting those constraints. 4. If A # 0 is complete and R-closed (cf. Definition 5.3.11), then we know it contains a valid abstraction of R and Σ, and we can extract a signature for the symbol f . If not, we need to start over from (1) with a new automaton A 1 , recognizing L 1 another finite subset of L such that L 1 ⊃ L 0 . In Algorithm 4, this is done by the grow function (cf. Definition 5.3.13). We continue until a counter-example is found, or a type partitions signature is found.

Example 5.3.8. Consider again the TRS defined by even(0) → true odd(0) → f alse even(s(x)) → odd(x) odd(s(x)) → even(x)

We want to find a partitions signature for the symbol even for the target type partition T = {true # , f alse # }. The language L generated by this symbol is L = {even(n) | n ∈ N }. We start (1) with A 0 recognizing L 0 = {even(0)}, and use the tree automata completion algorithm on it, which gives us a new tree automaton A * 0 recognizing the reachable terms {even(0), true} and defined by: (a) 0 → q 0 even(q 0 ) → q e0 true → q t q t → q e0

There is (2) no violation of T yet. Let S(A * 0 , T ) be the set of constraints to consider to (3) build an abstraction from A * 0 , in which terms typed with different types of T are recognized by different states in the abstraction. For now, the only constraints to consider are well-typedness constraints: S(A * 0 , T ) = {q 0 = q e0 , q t = q 0 }. Indeed, q 0 recognizes a fragment of type N and q t , q e0 a fragment of type bool, they must not be merged. We then use an SMT solver to build the smallest renaming φ from Q to Σ # respecting the given constraints. The result, φ(A * 0 ), is as follows (for the sake of readability we give comprehensible names to the new elements of Σ # ):

0 → nat # even(nat # ) → true # true → true #
This automaton is not complete (4): the well-typed term even(s(0)) is not recognized. We hence start over (1) with A 1 recognizing L 1 = {even(0), even(s(0))}. After using the completion algorithm, A * 1 contains the transition set (a) plus the following new transitions: (b) s(q 0 ) → q 1 even(q 1 ) → q e1 odd(q 0 ) → q e2 f alse → q f q f → q e2 q e2 → q e1

Building the associated abstraction (3), we use the set of constraints S(A * 1 , T ) = S(A * 0 , T ) ∪ {q e0 = q e1 , q e0 = q e2 , q e0 = q f } ∪ {q t = q e1 , q t = q e2 , q t = q f } where we separate q e0 , q t from q e1 , q e2 , q f because they match two different elements of the type partition T , respectively true and false. The resulting φ(A * 1 ) is

0 → nat # true → true # even(nat # ) → true # s(nat # ) → nat # false → false # odd(nat # ) → false #
This automaton is not R-closed w.r.t. the rule odd(s(x)) → even(x): if we instantiate x with nat # , since even(nat # ) is recognized in true # and odd(s(nat # )) in false # , for it to be R-closed it should include the transition true # → false # , which it does not. So we start again (1) with A 2 recognizing L 2 = {even(0), even(s(0)), odd(s(0))} where odd(s(0)) has been generated from odd(s(nat # )). After using the completion algorithm, A * 2 contains transition sets (a), (b) and the new transition odd(q 1 ) → q e0 . The associated set of constraints is S(A * 2 , T ) = S(A * 1 , T ) ∪ {q 0 = q 1 ∨ q e0 = q e1 }.

The new constraint q 0 = q 1 is added because with the two transitions odd(q 0 ) → q e1 and odd(q 1 ) → q e0 if the abstraction chooses q 0 = q 1 then the resulting abstraction automaton would no longer be deterministic. The resulting φ(A * 2 ) is defined by

0 → 0 # s(1 # ) → 0 # s(0 # ) → 1 # true → true # false → false # even(0 # ) → true # even(1 # ) → false # odd(0 # ) → false # odd(1 # ) → true #
This automaton is completed, R-closed and contains an abstraction of R and of Σ.

From it, we extract a symbol signature for even with output partition T = {true, false} which is

({0 # , 1 # }) → T .
The rest of this section details each step of one iteration of the procedure followed by proofs of correctness (Theorem 8), regular completeness (Theorem 9) and completeness in refutation (Theorem 10).

Tree Automata Completion

Let R be a functional TRS, and f the symbol of Σ we are searching a type partitions signature for. Each iteration i of the procedure starts by using the Tree Automata Completion algorithm to complete the automaton A i . This automaton is an -free tree automaton in which each state q recognizes exactly one term. The language recognized by A i is a finite subset of R * (L) where

L = {f (t 1 , . . . , t n ) | t 1 . . . t n ∈ IRR(R)}. A state q is final in A i if it
recognizes a term of L. We write A * i for the output of this step of the procedure. It is a new tree automaton recognizing exactly R * (L(A i )) and having additional properties ensured by the Tree Automata Completion algorithm: A i is syntactically included in A * i , and for all state q, q of A * i and term t (resp. t ) recognized by q (resp. q ), if t → R t then there exists a transition q → q in A * i (t is also recognized by q).

Counter-Example Finding and Constraints Generation

Since A * i converges to R * (L), it can be used to search for a counter-example. Note that at this point, no abstraction has been made: a counter-example found in A * i is not spurious. Note also that the structure of a completed automaton allows us to easily build the rewriting path from an initial term of L to its faulty outcomes.

Abstraction Generation

An abstraction A # i is built from A * i by first computing a set S(A * i , T ) of constraints over the states of A * i (which will depends on the target type partition T ). Definition 5.3.10 (SMT Constraints). For any tree automaton A = Σ, Q, Q f , ∆ and type partition T , S(A, T ) is the smallest set of SMT constraints such that:

(q = q ) ∈ S(A, T ) ⇐ ∃τ, τ ∈ T.      q ∈ T A (τ ) ∧ q ∈ T A (τ ) ∧ τ = τ (5.1) (q 1 = q 1 ∨ • • • ∨ q n = q n ∨ q = q ) ∈ S(A, T ) ⇐ f (q 1 , . . . , q n ) → q ∈ A ∧ f (q 1 , . . . , q n ) → q ∈ A (5.2) (v = v ∨ q = q ) ∈ S(A, T ) ⇐ v → q ∈ A ∧ v → q ∈ A (5.3) where T A (τ ) = { q f | q f ∈ Q f ∧ ∃t ∈ γ(τ ). t → * A q f }.
In other words, T A (τ ) contains all the final states recognizing terms of type τ .

In this definition, the first type of constraints (5.1) ensures that the target type partition is respected: for any two final states q, q (recognizing each a term of L), if they rewrite into members of two different types of T , then we must have the constraint q = q . The two other kind of constraints, (5.2) and (5.3), ensure determinism. We then use an SMT solver to find the smallest renaming φ from Q to

Σ # such that A # i = φ(A * i ) satisfies φ(S(A * i , T )).

Termination

We stop the procedure when we detect that A # i contains an abstraction of R. It is the case when A # i is R-closed and IRR-complete w.r.t. R.

Definition 5.3.11 (R-Closed Tree Automaton). Let R be a term rewriting system and A a tree automaton. For all state q of A, rule l → r of R and substitution σ of X → Q such that lσ → * A q, the pair lσ, q is called a critical pair. It is resolved if there exists q such that rσ → * A q and q = q or q → q ∈ A. A is R-closed if every critical pair is resolved.

Definition 5.3.12 (IRR-Completeness). An automaton A is IRR-complete w.r.t. R if for all symbols f in A, for every term t = f (t 1 , . . . , t n ) with t i ∈ IRR(R) there exists a state q such that t → * A q.

Note that in our case, IRR(R) is easily computable since it is the language of values. If A # i is not IRR-complete and R-closed, we start a new iteration of the procedure using A i+1 = grow(A i , A # i ) where the purpose of grow is to add new samples to the completed automaton while eliminating the causes of the non-IRR-completeness and non-R-closure.

Definition 5.3.13 (The grow Function). The automaton

A i+1 = grow(A i , A # i ) is defined as follows. (i) if A #
i contains an unresolved critical pair lσ, q (not R-closed), then a rewriting path has not been taken into account yet. Let t be a term such that t → * A # i lσ.

We add to A i+1 the necessary transitions to recognize t, to ensure that this critical pair will be solved in the next iteration.

(ii) if A # i has no unresolved critical pair but it is not IRR-complete, then we know that the set E = L \ L(A # i ) is not empty. Let t be one term of E having the smallest number of symbols. Then we add to A i+1 the necessary transitions to recognize t. This ensures that in the next iteration, A # i+1 recognizes it.

When A # i = Σ, Q, Q f , ∆ is IRR-complete R-closed it contains an abstraction Λ = Σ # , ∆ # and an abstraction R # of R where Σ # is Q the set of states of A #
i , and where ∆ # and R # are defined as

R # = {f (τ 1 , . . . , τ n ) → τ | f ∈ F ∧ f (τ 1 , . . . , τ n ) → τ ∈ ∆} ∆ # = ∆ \ R # Then A # i is returned by analyze-function.
then we will eventually find it; and 2. completeness in refutation, if there exists a counter-example, we will eventually find it.

Theorem 9 (Regular Completeness). If there exists a regular abstraction Λ = Σ # , ∆ # of T (Σ) and a regular abstraction R # of R providing a type partition signature for f , Algorithm 4 will eventually find it.

Proof. In the following, given automata A and A , A ⊆ A denotes that transitions of A are included in transitions of A modulo state renaming. In the same way, A = A means that transitions sets are equal modulo state renaming. If Λ and R # exists, then we can build A # defined with the union of ∆ # and R # . At each cycle i of the procedure, there exists a renaming φ respecting S(A * i , T ) such that φ(A * i ) ⊆ A # . Since the procedure builds the smallest renaming φ respecting S(A * i , T ) (meaning that φ (A * i ) has the smallest number of states), and since there is a finite number of automata for a given number of states, then we will eventually have φ = φ . The procedure stops when φ (A * i ) is IRR-complete, which means we cannot have

φ (A * i ) ⊂ A # , but only φ (A * i ) = A # .
Theorem 10 (Completeness in Refutation). For a given input function symbol f and target type partition T , if there exists a term f (t 1 , . . . , t n ) that rewrites to two different elements of T , Algorithm 4 will eventually find it.

Proof. By adding new terms in the automaton A i at each new cycle in a fair manner (smallest terms first in step (ii) of grow function), we guarantee that at some point we will add the counter-example. It will be detected as a counter-example after the tree automata completion phase.

Recall that our overall goal is to prove safety properties on programs, i.e., properties of the form t → * false. For this, we can identify a subset of forbidden types (such as false # ) in the target type partition, and define a counter-example as a term t typable with one of those forbidden types. In this way we can turn our type inference algorithm into a verification tool for safety properties, as illustrated in the next section.

Experiments

This section details our implementation [START_REF] Haudebourg | Timbuk 4: Regular verification framework based on tree automata and term rewriting systems[END_REF] of the verification technique developed in this chapter and the associated experimental results [START_REF] Genet | Experiments with timbuk 4[END_REF]. It consists of an OCaml program along with several libraries able to resolve together the regular language type inference problem from an input term rewriting system R, pattern p and target type partition T . It outputs an abstraction Λ and R # and all the possible type substitutions π with the associated τ ∈ T such that Λ, R # , π p : τ . This allows us to verify complex properties on programs by expressing the property using a predicate defined in the program itself, and verifying that it can never be typed with false # . For instance to prove that a list sorting function sort is correct (in the sense that the output list is sorted), we first define a sorted predicate as sorted(nil) → true sorted(cons(x, nil)) → true sorted(cons(x, cons(y, z))) → if (leq(x, y), sorted(cons(y, z)), f alse) and use our implementation with the input term sorted(sort(x)) with the target type partition {true # , false # }. If all the output type substitutions π are such that π sorted(sort(x)) : true # , then the property is verified.

Implementation Details

The implementation [START_REF] Haudebourg | Timbuk 4: Regular verification framework based on tree automata and term rewriting systems[END_REF] follows the theoretical algorithms presented in this chapter with some optimizations and limitations. We describe here the various specificities of the implementation.

Preliminary Typing Phases To simplify our argumentation in this chapter, we required every type partition to be a partition of IRR(R), the entire set of possible values. In practice, trying to compute type partitions over IRR(R) would be both inefficient and unnecessary since in most modern programming languages functions can only take given subsets of values (types) as parameters. For this reason, our implementation is equipped with a preliminary Hindley-Milner type inference phase [START_REF] Hindley | The principal type-scheme of an object in combinatory logic[END_REF][START_REF] Milner | A theory of type polymorphism in programming[END_REF] with let-polymorphism. The first-order types used by this inference phase are defined by the user as an input tree automaton where each state is a type. From the input pattern, the term rewriting system is then monomorphized so that we know the input domain of every function call, and thus the domain of each type partition we are looking for. This greatly improves the performances of the analysis.

Constants sub-typing phase Consider the simple term rewriting system defining the equality predicate on natural numbers eq(0, 0) → true eq(s(x), 0) → false eq(0, s(y)) → false eq(s(x), s(y)) → eq(x, y)

together with the initial abstract domain {true # , false # , nat # }. The problem of finding the signature of eq for the target partition {true # , false # } is not regular if we consider the input domains of the arguments of eq to be nat # . In practice however, if eq is applied on a constant value, such as in eq(x, s(0)), we can reduce the domain of the second parameter by adding an abstract value 1 # abstracting s(0) in the initial abstract domain. The only possible partition of 1 # is {1 # }, which transform the problem of finding the (type partition) signature of eq for {true # , false # } into a regular problem. Its solution is ({2+ # , 1 # , 0 # }, {1 # }). In our implementation, in addition to the first-order types given by the user, we build precise types for the constant values used in the program. This allows us to find a solution to otherwise irregular problems. It is however a prototypical optimization that must be used with caution since it may lead to a computational overhead.

Counter-examples When using our implementation to type a given input pattern with a given type partition, it is possible to specify that some types in the partition are invalid. For instance, while typing sorted(sort(L)) with the partition {true # , false # }, if the sort function is correct we expect the pattern to not be typable by false # . In this case, once the analysis is finished, if we find a way to type the pattern with false # , then we are able to generate a counter example: an instantiation of the input pattern that rewrites to false. Note that this implementation choice has two drawbacks: 1. Since the "forbidden type" information is only used once the analysis is done, if there is no regular types that satisfies the target type partition, our implementation may diverge even though there is a counter example to the desired property. 2. In the best case, even if everything is regular, this may still delay the finding of counter examples. It is possible to insert the notion of forbidden type into the analysis to avoid these issues, modulo some adaptations of the presented algorithms.

Test Suite

We tested our implementation over a collection of more than 80 problems [START_REF] Haudebourg | Timbuk 4: Regular verification framework based on tree automata and term rewriting systems[END_REF] coming from Timbuk 3 [START_REF] Genet | Reachability Analysis and Tree Automata Calculations[END_REF], some regular problems from the MoCHi test suite [START_REF] Kobayashi | Mochi: Model checker for higher-order programs[END_REF] and some original challenges created for the occasion inspired by Tons of Inductive Problems [START_REF] Claessen | Tip: Tons of inductive problems[END_REF]. We expect some problem instances to be similar to the ones used in [START_REF] Matsumoto | Automata-based abstraction for automated verification of higher-order tree-processing programs[END_REF], although this cannot be verified since the test suite used in this paper is not publicly available. The test suite, publicly available on Timbuk's website [START_REF] Genet | Experiments with timbuk 4[END_REF], is composed of a variety of problems over first-order and higher-order tree-processing functional programs including:

• Positive tests: regular properties intended to be proved by our implementation. This includes properties such as ∀L.sorted(merge-sort(L)) = true where L is a list of As and Bs, or ∀X, T. member(X, T ) ⇐⇒ member(X, mirror(T )))

where T is a binary tree, etc.

• Negative tests: false properties or buggy programs, where there exists a counter example to the target property.

• Typing challenges with no property to verify but only regular language types to find. E.g., using the input pattern only-As(L) and the target type partition {true # , false # }, our implementation should type L with either the type "lists of As" or "lists with at least one B".

• Intentionally non-regular problems, for which we expect the invariant learning procedure to diverge. For instance, ∀N : nat. N = N is not a regular property.

Even if some of the problems in our test suite are taken from the MoCHi test suite [START_REF] Kobayashi | Mochi: Model checker for higher-order programs[END_REF], MoCHi does not handle regular languages and does not target the same family of properties.

Experimental Results

Table 5.1 and 5.2 give the result of our experiments, comparing our implementation over our test suite against Timbuk 3 which is, to our knowledge, the only higher-order tree-processing program verification tool that is publicly available. In particular, we have not been able to compare to the regular-complete verification procedure presented in [START_REF] Matsumoto | Automata-based abstraction for automated verification of higher-order tree-processing programs[END_REF] which does not offer a public implementation, even if it also targets regular properties. Each table presents the time performances and memory usage (averaged over 10 executions) of the two implementations over the compatible regular test instances on a Intel® i7-7600U CPU, 4 2.80GHz cores. Positive instances (first half of the table) and negative instances (second half of the table) are separated by a line. A timeout is set at 120 seconds.

Execution Time & Completeness

The "Time" columns show that when it succeeds, Timbuk 3 is on average faster than our implementation. This is expected since our preliminary typing phase and sub-typing phase have a cost. However this also shows that in many cases, even on first-order programs our implementation terminates where Timbuk 3 diverges.

In particular this is the case for the non-trivial mergeSort algorithm for which we successfully identify the regular language of sorted lists. This is also the case for binary-tree processing algorithms such as memberTree where we automatically show that it is invariant by mirroring. We see the same tendency on higher-order programs where Timbuk 3 also diverges on the merge-sort algorithm (where this time the comparison operator is a parameter of the sorting function), and on binarytree processing programs such as mapTree, where we succeed. Interestingly, our implementation is unable to find a counter-example to a mergeSortBug property, where we try to check that ∀L. sorted (≤) (merge-sort (≥) L), which is wrong. Timbuk 3 is able to find a counter example, where we reach a timeout. As discussed in Section 5.4.1 this is due to the fact that, in our current modular implementation, counter-examples are searched once the whole analysis is finished.

Memory Usage

Another feature of our implementation is its consistent memory usage (which includes the memory usage of the SMT solver), shown by the "Memory" columns in the tables. Thanks to modularization, the memory footprint of our implementation stays low even on time consuming instances. This is an improvement compared to the non-modular Timbuk 3, where on some problems the memory usage can grow up to several GB of data. The difference is especially visible for instance on the memberAppend problem where the goal is to verify that for all lists L 1 , L 2 and element X, (member X (append L 1 L 2 )) ⇐⇒ ((member X L 1 ) ∨ (member X L 2 )).

Conclusion

We have developed a regular language type inference procedure on top of a regular abstract interpretation of term rewriting systems. This allows us to automatically verify safety properties on higher-order tree-processing functional programs. This improves on existing verification techniques based on type annotations which generally require considerable expertize to determine all the necessary annotations to carry out the proof. For instance in F* it is necessary to give complex type annotations on every intermediate functions in the definition of the merge-sort algorithm (corresponding to intermediate lemma) for the type-checker to accept the program, only to show that its output is sorted. On the other hand we can automatically carry out the same proof with only the expected output type of the main function.

The type inference mechanism uses type partitions to reduce the complexity of the underlying algorithms. For a given input term with variables and output type partition, we compute, for each variable of the input term, the input type partitions needed to respect the output partition. Using a type system allows for modularity: a type partition signature is attached to each symbol, which summarizes the behavior of associated rewriting rules. The type partition signature can be inferred independently for independent functions. It can be inferred directly for non-recursive functions. Recursive functions are handled using a novel invariant learning procedure based on the tree automata completion algorithm, following the precepts of a counterexample guided abstraction refinement procedure (CEGAR). This particular variant of CEGAR allows us to refine the abstractions without the need of a complete spurious counter-example rewriting sequence, at the price of requiring the input TRS to be terminating. The resulting procedure is regularly-complete and complete in refutation, meaning that if it is possible to give a regular language type to a term then we will eventually find it, and if there is no possible type (regular or not) then we will eventually find a counter-example. Regular completeness is achieved by the generation of non-functional abstractions, which Timbuk 3 could not do. Our implementation of this technique shows encouraging performances and is able to verify properties that were not covered by previous similar techniques. In the following we list some possible further improvements.

Using a more restricted type language helps us to enjoy more automation for type inference. In particular, annotations for intermediate functions are automatically inferred. For example, from a TRS encoding the insertion sort algorithm, and the final term sorted(sort(x)), we automatically infer the type annotation for the insert function as insert(any # , sorted # ) → sorted # ∈ R # , where the abstract value sorted # is inferred from the side analysis of the sorted symbol: sorted(sorted # ) → true # ∈ R # . This annotation reveals a necessary intermediate proof step: we first need to prove that when inserting any element into a sorted list, we obtain a sorted list. We believe that starting from the two above generated rules of R # it is possible to infer the adequate type annotations for F* or Liquid Types and automate the proofs, i.e., infer the annotation insert(X : int, Y : {l : int list | sorted(l)}) : {l : int list | sorted(l)}.

Another strand of further work concerns the extension to properties on lazy functional programs. Jones' abstraction technique [START_REF] Jones | Flow analysis of lazy higher-order functional programs[END_REF] can prove properties on functional programs using call-by-name evaluation. With our current invariant inference procedure we need for the input program to be terminating. However, this inference part is not limited to call-by-value evaluation, as it is in [START_REF] Jones | Flow analysis of lazy higher-order functional programs[END_REF]. We could broaden the image computation technique so that unfinished non terminating computations are taken into account. We experimented with this and there are some interesting cases, like call-by-value evaluations, for which this is sufficient to build a correct abstraction automaton.

There are known abstraction inference techniques for domains mixing structures and base types [START_REF] Voronkov | Lattice automata: A representation for languages on infinite alphabets, and some applications to verification[END_REF], e.g., queues of integers. These techniques rely on an extension of word automata called lattice automata. There exists a similar extension for tree automata called lattice tree automata making it possible to abstract recursive structures containing, e.g., integer values [START_REF] Genet | A completion algorithm for lattice tree automata[END_REF].

The main limitation of our technique is the impossibility to express or verify relational properties. There exists ways to use tree automata to represent relations using, for instance, automatic structures [START_REF] Khoussainov | Automatic presentations of structures[END_REF][START_REF] Blumensath | Automatic structures[END_REF]. The idea is to use one automaton to represent multiple terms at once, or convolution of terms. For instance, the equality relation can be recognized by the two automaton transitions

0 ⊗ 0 → q s ⊗ s(q) → q
where ⊗ is a convolution operator. Checking if two terms t 1 , t 2 are equals is then equivalent to testing if the convoluted term t 1 ⊗ t 2 is recognized by the automaton above. Convolutions introduces however new challenges that remains to be solved in order to integrate them into this technique. This is the topic of the next chapter.

Chapter 6

Regular relations

The contribution presented in this chapter has mainly been realized during a three month collaboration with Prof. Naoki Kobayashi at the University of Tokyo.

Introduction

Until now, in the previous chapters we have seen how to use regular languages to tackle regular problems, and in particular regular safety problems. One particularity of regular problems is that they can be solved without exhibiting any relations between the variables of the program. As a consequence, most problems involving arithmetic, or any kind of structural comparison between infinite domain values, is not a regular problem and cannot be handled by the techniques defined in the previous chapters. For instance, consider the following term rewriting system R defining the equality predicate eq over natural numbers: eq(0, 0) → true eq(s(x), 0) → false eq(s(x), s(y)) → eq(x, y) eq(0, s(y)) → false

We want to check that, for all n, eq is reflexive:

∀n. eq(n, n) → * R false

As we have seen throughout the document, verifying this property requires computing, or over-approximating, R * (I) for I = { eq(n, n) | n ∈ N }. Since R is simple, we know that R * (I) is the union of I and { true }. However I is not a regular language, and any regular over-approximation of I would add false to the over-approximation of R * (I). From the point of view of regular language typing, we can say that there exists no regular language signature for eq for the target type { true }. This shows why our problem is not regular. In general, any problem involving non-modulo arithmetic is not regular. This limitation is directly due to the non-regularity of tree languages representing tree relations such as I. A similar problems arises in the world of string languages for string relations. In the case of strings, this problem has been solved using automatic relations [START_REF] Khoussainov | Automatic presentations of structures[END_REF][START_REF] Blumensath | Automatic structures[END_REF]. This chapter discusses how string automatic relations can be extended into tree automatic relations in order to represent relations between trees using regular languages [START_REF] Comon | Tree automata techniques and applications[END_REF][START_REF] Lin | Accelerating tree-automatic relations[END_REF]. We then propose a procedure to learn tree automatic relations using ICE [START_REF] Garg | Ice: a robust framework for learning invariants[END_REF][START_REF] Garg | Learning invariants using decision trees and implication counterexamples[END_REF], a robust counter-example guided learning model applied to constrained Horn clause (CHC) solving. We implemented 125 a regular CHC solver [START_REF] Haudebourg | Regular chc solver[END_REF] based on this procedure and tested it on several case studies.

The rest of the chapter is structured as follows. First we see in Section 6.2 what are automatic relations on strings. We then extend this concept to tree automatic relations in Section 6.3. In Section 6.4 we propose a learning procedure for tree automatic relations based on ICE learning. In Section 6.5 we expose the results of our implementation of this procedure in our Regular CHC solver. Finally, Section 6.6 concludes the chapter.

String Automatic Relations

Automatic Relations on strings have first been introduced in [START_REF] Khoussainov | Automatic presentations of structures[END_REF] as a mean to present algebraic structures (on strings) using finite automata to encode the structure's operations and relations. For instance, consider the following structure N, ≤ where N is the string language of natural numbers (0, s0, ss0, sss0, . . . ) and ≤ the usual comparison operator. One way to represent the relation ≤ is by considering the string language Leq = {s n 0s m 0 |n ≤ m} that encodes every pair of the relation as its concatenation. This is very similar to our previous example with the (tree) language Eq: Leq is not regular as it cannot be recognized by a simple finite automaton (it can be recognized by a finite push-down automaton). In [START_REF] Khoussainov | Automatic presentations of structures[END_REF], authors propose a new way of encoding such relations using regular languages: instead of encoding each pair of the relation by concatenation, each pair is encoded by convolution. Definition 6.2.1 (String convolution). Let Σ be an alphabet. The convolution of two words α, β of Σ * , written α ⊗ β, is recursively defined as:

aα ⊗ bβ = a b (α ⊗ β) aα ⊗ = a • (α ⊗ ) ⊗ bβ = • b ( ⊗ β) ⊗ =
where is the empty string, a b denotes the convolution of the two symbols a, b and • is a special padding symbol not in Σ. The resulting string is defined over the alphabet • . We can show that L(≤) is regular since it can be recognized with the following finite automaton:

q 0 start q 1 q f s s 0 s 0 0 • s • 0
The state q 0 recognizes the common prefix between the two strings (the minimum of the two numbers), while q 1 recognizes the suffix (the difference of the two numbers).

The final state q f only accepts elements of the relation.

Contrarily to the concatenation encoding, Regular relations encoded with convolutions are closed under union (

L(R ∪ R ) = L(R) ∪ L(R ) is regular), intersection (L(R ∩ R ) = L(R) ∩ L(R ) is regular), and under complement (L(Σ * 2 \ R) = L(Σ * 2 ) \ L(R) is regular).
In the rest of this chapter, we explore how we can extend string automatic relations to tree automatic relations, and how to automatically learn such relations.

Tree Automatic Relations

As we have seen in the previous section, the nature of automatic relations is closely related to the definition of a convolution operator on the considered domain. In order to generalize string automatic relations to trees, we must first define a convolution operator on trees.

Standard Convolution

The standard definition of tree convolution [Tata] used to define tree automatic relations consists in overlapping the two convoluted trees as pictured in Figure 6.1. Definition 6.3.1 (Standard Convolution). Let Σ be a ranked alphabet. The standard convolution of two terms s, t ∈ T (Σ) written s ⊕ t is recursively defined by:

f (s 1 , . . . , s n ) ⊕ g(t 1 , . . . , t m ) = f g (s 1 ⊕ t 1 , . . . , s N ⊕ t N )
where N = max(n, m) = ar( f g ), for all i > n, s i = • and for all i > m, t i = •. Once again, • is a special padding symbol not in Σ. The resulting term is defined over the ranked alphabet

Σ ⊕ = { f g | f, g ∈ Σ ∪ { • } } where ar(•) = 0.
Definition 6.3.2 (Regular Tree Relation). A tree relation R is regular (w.r.t. the standard convolution) if the language L(R) = { x ⊕ y | R(x, y) } is regular. Then it can be recognized by a finite tree automaton. Such a tree automaton is a tree automatic relation. This definition extends to any n-ary relations. We name R ⊕ the set of regular relations w.r.t. the standard convolution.

This definition is very similar to regular string relations (Definition 6.2.2), and is also closed under union, intersection and complement. It can be used to represent, with a simple tree automaton, relations that are not regular otherwise.

a a → q b b (q) → q c c (q, q) → q
However this standard definition of convolution cannot capture relations between trees that never overlap. For instance, consider the ranked alphabet Σ = {0 : 0, s : 1, nil : 0, cons : 2, a : 0, a : 0} ready to encode numbers from N and lists of as and as from List. Then the relation R = {(l, n) | n ∈ N, l ∈ List. length(l) = n} that relates every list with its length is not regular. This is because when convoluting a number with a list, the tree branch representing the number does not overlap the tree branch representing the list. One simple way to solve this case is to encode the lists the other way around as shown on Figure 6.2, writing cons(cons(nil, b), a) instead of cons(a, cons(b, nil)). In this case the relation can be represented by the following tree automaton in the final state q R :

cons s (q R , q v ) → q R a • → q v nil 0 → q R b • → q v
This can be generalized to any algebraic datatype with a single recursion site such as numbers and lists. However it doesn't work with tree structure with multiple recursion sites such as binary trees, where each node of the tree has two children (two recursion sites). For instance, if we note BT ree the set of binary trees, the relation

{ (t, n) | t ∈ Btree, n ∈ N.
|t| ≤ n } is not regular w.r.t. the standard convolution. This is because the term n cannot overlap all the branches of the tree t whatever the encoding we choose for BT ree.

Full Convolution

In the previous section we exposed the standard convolution operator definition which can be found in Tata. In this section we extend this definition and define a new convolution operator on trees designed to solve the overlapping problem of the standard convolution operator. The idea of this new convolution is, instead of relating each term of one tree with only one term of the other tree, to relate every term of one tree with every term of the other tree at the same depth.

Definition 6.3.3 (Full Convolution). Let Σ be a ranked alphabet. The (full) convolution of two terms s, t ∈ T (Σ) written s ⊗ t is recursively defined by:

f (s 1 , . . . , s n ) ⊗ g(t 1 , . . . , t m ) =            f g (s 1 ⊗ •, . . . , s n ⊗ •) if m = 0 f g (• ⊗ t 1 , . . . , • ⊗ t m ) if n = 0, otherwise: f g (s 1 ⊗ t 1 , . . . , s 1 ⊗ t m , . . . , s n ⊗ t 1 , . . . , s n ⊗ t m )
where each symbol f g is derived from f and g such that:

ar( f g ) = 0 if ar(f ) = ar(g) = 0 max(1, ar(f )) * max(1, ar(g)) otherwise

Once again, • is a special padding symbol not in Σ. The resulting term is defined over the ranked alphabet

Σ ⊗ = { f g | f, g ∈ Σ ∪ { • } }
where ar(•) = 0. The binary convolution operator can be extended into any n-ary convolution.

In the standard convolution definition, for every two terms f (s 1 , . . . , s n ) and g(t 1 , . . . , t m ) each sub-term s i , for i ∈ [1, n] is convoluted with t i (if it exists). In this definition, each s i is convoluted with every t j for j ∈ [1, m]. This allows us to capture more relations with regular languages. We can hence extend the definition of regular tree relations as follows. Definition 6.3.4 (Regular Tree Relation). A tree relation R is regular w.r.t. the full convolution if there exists a regular language L(R) such that for all terms s, t:

s ⊗ t ∈ L(R) ⇐⇒ R(s, t)
Then it can be recognized by a finite tree automaton. Such an automaton is a tree automatic relation. Again, this definition extends to any n-ary relations. We name R ⊗ the set of regular relations w.r.t. the full convolution. The full convolution is strictly more expressive that the standard convolution as it can be used to encode more relations with regular languages. This extends the concept of regular relations.

Theorem 11 (Standard Regular Relations are Full Regular Relations). The set of standard regular relations is strictly included in the set of full regular relations: R ⊕ ⊂ R ⊗ .

Proof. For every relation R of R ⊕ , by definition L(R) is regular and can be represented by a tree automaton A over ⊕ convoluted terms. To prove that R is also in R ⊗ we proceed as follows. First we define a transformation [•] ⊗ from A to a new tree automaton [A] ⊗ recognizing the same relation with ⊗ convoluted terms. We then show the following property of the transformation:

∀s, t. s ⊗ t ∈ L([A] ⊗ ) ⇐⇒ s ⊕ t ∈ L(A)
Let Σ be an initial (before convolution) ranked alphabet. Let

A = Σ ⊕ , Q ⊕ , Q f , ∆ ⊕ where Σ ⊕ follows Definition 6.3.1. Then [A] ⊗ is defined as Σ ⊗ , Q ⊗ , Q f , ∆ ⊗ ,
where Σ ⊗ follows Definition 6.3.3, where Q ⊗ is the smallest set such that

∈ Q ⊗ q ∈ Q ⊕ ⇐⇒ { q, q , q } ⊆ Q ⊗
The state recognizes any term convolution and is associated to the following transitions in ∆ ⊗ :

f g ∈ Σ ⊕ ⇐⇒ f g ( , . . . , ) → ∈ ∆ ⊗ f ∈ Σ ⇐⇒ f • ( , . . . , ) → ∈ ∆ ⊗ g ∈ Σ ⇐⇒ • g ( , . . . , ) → ∈ ∆ ⊗
Similarly, states of the form q (resp. q ) can recognize every convolution s ⊗ t if and only if s ⊕ • (resp. • ⊕ t) is recognized by q in A. This is defined by the following transitions in ∆ ⊗ :

f • (q 1 , . . . , q n ) → q ∈ ∆ ⊕ ⇐⇒ ∀g ∈ Σ ∪ {•}. f g ( q 1 , . . . , q n ) → q ∈ ∆ ⊗ • g (q 1 , . . . , q m ) → q ∈ ∆ ⊕ ⇐⇒ ∀f ∈ Σ ∪ {•}. f g ( q 1 , . . . , q m ) → q ∈ ∆ ⊗
where m = ar(f ) and n = ar(g). Finally the remaining transitions in ∆ ⊗ define the transformation itself:

f g (q 1 , . . . , q N ) → q ∈ ∆ ⊕ ⇐⇒ f g (q 1 , . . . , q N ) → q ∈ ∆ ⊗ if ar(f ) = 0 ∨ ar(g) = 0
, or: f g (q 1,1 , . . . , q 1,m , . . . , q n,1 , . . . , q n,m ) → q ∈ ∆ ⊗ where m = ar(f ), n = ar(g), N = max(n, m) and for all (i, j), q i,j = q i if i = j, q i,j = q i if i > m, q i,j = q j if j > n and q i,j = in other cases. Here we assume that A is a REFD automaton. The idea of the transformation is to simulate the standard convolution with the full convolution by using the states of A in [A] ⊗ where the terms overlap, and using q and q otherwise when they don't. Let us show that for any term s, t, for any state q ∈ Q, if s ⊕ t ∈ L(A, q) then s ⊗ t ∈ L([A] ⊗ , q). We do that by induction on the term s ⊕ t.

• If s ⊕ t = f g . Then for all q such that s ⊕ t ∈ L(A, q) because A is -free we have

f g → q ∈ ∆ ⊕ . By definition this means that f g → q ∈ ∆ ⊗ . Because s ⊗ t = f g , we have s ⊗ t ∈ L([A] ⊗ , q). • If s ⊕ t = f g (T 1 , . . . , T k ),
then by definition of ⊕, we have s = f (s 1 , . . . , s n ), t = g(t 1 , . . . , t m ) with k = max(n, m) and for each i ∈ [1, k], T i = s i ⊕ t i according to Definition 6.3.1. For all q such that s ⊕ t ∈ L(A, q), since A is -free we have f g (q 1 , . . . , q k ) → q ∈ ∆ ⊕ with for all i, s i ⊕ t i ∈ L(A, q i ). Then by hypothesis of induction we have s i ⊗ t i ∈ L([A] ⊗ , q i ). Here we have two cases:

-If ar(f ) = 0 or ar(g) = 0 then by definition of [A] ⊗ the transition f g (q 1 , . . . , q k ) → q is in ∆ ⊗ . Also by definition we know s ⊗ t is equal to f g (s 1 ⊗ t 1 , . . . , s k ⊗ t k ) (where every s i or every t i is • according to Definition 6.3.1 and 6.3.3). And we already know that s

i ⊗ t i ∈ L([A] ⊗ , q i ), hence we conclude that s ⊗ t ∈ L([A] ⊗ , q).
-If ar(f ) > 0 and ar(g) > 0 then by definition of the transformation we have f g (q 1,1 , . . . , q 1,m , . . . , q n,1 , . . . , q n,m ) → q ∈ ∆ ⊗ . Moreover the term s ⊗ t is by Definition 6.3.3 f g (s 1 ⊗ t 1 , . . . , s 1 ⊗ t m , . . . , s n ⊗ t 1 , . . . , s n ⊗ t m ). For all i ∈ [1, n], j ∈ [1, m], if i = j we have q i,j = q i and we already know s i ⊗ t i ∈ L([A] ⊗ , q i ) which means s i ⊗ t i ∈ L([A] ⊗ , q i,j ). If i > m then by definition of ∆ ⊗ we have q i,j = q i . By definition of the standard convolution, s i ⊕ • ∈ L(A, q i ) which implies s i ⊗ t ∈ L([A] ⊗ , q i,j ) for all t. In particular s i ⊗ t j ∈ L([A] ⊗ , q i,j ). Similarly if j > n then by definition of ∆ ⊗ we have q i,j = q j . By definition of the standard convolution, • ⊕ t j ∈ L(A, q j ) which implies s ⊗ t j ∈ L([A] ⊗ , q i,j ) for all s. In particular s i ⊗ t j ∈ L([A] ⊗ , q i,j ). Finally in any other cases q i,j = which also means s i ⊗ t i ∈ L([A] ⊗ , q i,j ) because recognizes any term. We conclude that s ⊗ t ∈ L([A] ⊗ , q).

We do a similar induction on the term s ⊗ t to show that for any term s, t, for any state q ∈ Q, if s ⊗ t ∈ L([A] ⊗ , q) then s ⊕ t ∈ L(A, q). Let us write s = f (s 1 , . . . , s n ) and t = g(t 1 , . . . , t m ).

• If ar(f ) = ar(g) = 0 then s ⊗ t = f g . Let q be the state such that f g → q ∈ ∆ ⊗ . By definition of the transformation this means that there exists a transition f g → q ∈ ∆ ⊕ . By definition of the standard convolution this means that s ⊕ t ∈ L(A, q).

• If ar(f ) = n = 0 or ar(g) = m = 0 then s ⊗ t = f g (s 1 ⊗ t 1 , . . . , s N ⊗ t N ) with N = max(n, m
). (where every s i or every t i is • according to Definition 6.3.1 and 6.3.3). Let q be the state such that f g (q 1 , . . . , q N ) → q ∈ ∆ ⊗ with s i ⊗t i ∈ q i . By definition of the transformation this means that there exists a transition f g (q 1 , . . . , q N ) → q ∈ ∆ ⊕ . An by hypothesis of induction, s i ⊕ t i ∈ L(A, q). By definition of the standard convolution this means that s ⊕ t ∈ L(A, q).

• If ar(f ) > 0 and ar(g) > 0 then s ⊗ t is equal to f g (s 1 ⊗ t 1 , . . . , s 1 ⊗ t m , . . . , s n ⊗ t 1 , . . . , s n ⊗ t m ). and it is recognized using the following transition in ∆ ⊗ : f g (q 1,1 , . . . , q 1,m , . . . , q n,1 , . . . , q n,m ) → q where s i ⊗ t j ∈ L([A] ⊗ , q i j ). By definition of the transformation this means there exists a transition f g (q 1 , . . . , q N ) → q in ∆ ⊕ . Let us assume that n ≤ m. Then N = m. For all i ∈ [1, n] we have q i,i = q i . By hypothesis of induction this means that s i ⊕ t i ∈ L(A, q i ). For all i ∈ [n + 1, m], we have q i,i = q i , which implies • ⊕ s i ∈ L(A, q i ). By definition of the standard convolution this means that s ⊕ t ∈ L(A, q i ). Similarly if we assume that m ≤ n, then N = n and for all i ∈ [1, m] we have q i,i = q i . By hypothesis of induction this means that s i ⊕ t i ∈ L(A, q i ). For all i ∈ [m + 1, n], we have q i,i = q i , which implies t i ⊕ • ∈ L(A, q i ). By definition of the standard convolution this means that s ⊕ t ∈ L(A, q i ).

This proves that any relation of R ⊕ is a relation of R ⊗ . Besides, we have already exposed a regular relation of R ⊗ that is not in R ⊕ in Example 6.3.2. Overall this proves that R ⊕ ⊂ R ⊗ .

Relations Inference

In this section, we define a procedure to automatically learn regular tree relations using the information gathered from the program definition. For instance, consider the following (first-order) program defined with the following term rewriting system R: has-length(nil, 0) → true has-length(cons(x, l), 0) → f alse has-length(nil, s(y)) → f alse has-length(cons(x, l), s(y)) → has-length(l, y)

The function has-length associates a list with its length. We can easily translate the rewriting system R into a system of constraints over has-length, expressed as first-order logical formulas called Constrained Horn Clauses (CHC):

has-length(nil, 0)

∀y. ¬has-length(nil, s(y))

∀x, l. ¬has-length(cons(x, l), 0) ∀x, y, l. has-length(l, y) ⇐⇒ has-length(cons(x, l), s(y))

Solving this constraint system gives us a definition of has-length that satisfies the formulas. There already exists a plethora of techniques and tools to solve a CHC system, some based on satisfiability modulo theories [dMB08, BCD + 11] machine learning approaches [GLMN14,CCKS18,CKS18], etc. However most of these methods focus on numerical domains and are not well suited to infer regular relations on algebraic data types. We define in this section a regular CHC solving technique based on the ICE framework [GLMN14] that has already successfully been used on numerical domains.

Constrained Horn Clauses Solving

Definition 6.4.1 (Constrained Horn Clause). Let Σ be a ranked alphabet of constructor symbols, and R be a ranked alphabet of relation symbols. Let M be a partial model mapping some elements p of R to its interpretation, a relation M (p) of T (Σ) n where ar(p) = n. We say that p ∈ R is concrete if M (p) is defined, and abstract otherwise. A constrained Horn clause (CHC) over R and M is a first-order formula ϕ of the form

∀X . α 1 ∧ • • • ∧ α n ⇒ α n+1
where X is a set of variables, and each α i is either (i) a concrete formula, i.e. composed of concrete relations and variables of X (ii) an abstract predicate application over variables of X . In our case, a predicate application is a pattern of the form p(t 1 , . . . , t n ) where p ∈ R with t 1 , . . . , t n ∈ T (Σ, X ). We often write t instead of t 1 , . . . , t n and t ⊗ for t 1 ⊗ • • • ⊗ t n . The left hand side of the clause is called the "body" of the clause, while the right hand side is the "head".

We say that a model M satisfies a CHC ϕ, written M |= ϕ, if it is true considering the interpretation of predicates provided by M . Solving a CHC system S (a set of CHCs) defined over R and M consist in extending, or completing, the model M into a new model M by finding interpretations for the abstract predicates such that M satisfies every clause: ∀ϕ ∈ S. M |= ϕ

We then write M |= S. In our case, each predicate in the model, a regular relation, is represented by a regular language. Our goal is to automatically infer the missing regular languages that satisfy the input constraint system. To do that, we design an ICE-based inference procedure for regular relations.

ICE-Based Verification

First introduced in [GLMN14], "Implication Counter-Example" (ICE) is a robust framework for learning invariant that is particularly well suited to the resolution of CHC systems, and has already been used to infer invariants on higher-order functional programs [START_REF] Champion | Ice-based refinement type discovery for higher-order functional programs[END_REF]. It works by combining a learner that iteratively produces candidate models, and a teacher that verifies that the produced model satisfies the CHC system. When a CHC is violated, the teacher produces learning constraints guiding the learner during the process. The procedure can be summarized as follows.

Starting from the empty model M 0 where each abstract predicate is mapped to empty relations (in our case, empty tree automatic relations), the procedure generates a series of models M 1 , M 2 , . . . where M i+1 is given as

Learner ( k∈[0,i] Teacher (M k ))
The learner uses every set of constraints provided by the teacher for the current iteration and the previous ones. The procedure stops when M i+1 = M i . The output of the teacher (and the input of the learner) is a set of constraints extracted from M i . We detail in this section the role of the teacher and the learner. The following definitions are adaptations of the definitions found in [START_REF] Champion | Ice-based refinement type discovery for higher-order functional programs[END_REF] to the theory of terms.

The Teacher

The teacher's role is to ensure that the models produced by the learner come closer and closer to the solution. Given the current model M i , the teacher produces a set of learning constraints to guide the learner. A solution has been found when the output of the teacher is empty. Contrarily to a standard CEGAR procedure, in the ICE framework learning constraints are not solely made of (counter-)examples. ICE defines three different sorts of learning constraints depending on the form of the violated formula.

Positive examples If the violated formula is of the form ∀X .p( t), then the teacher can extract an example of valuation for which p is not satisfied in the current model (given by the learner), where it should.

Definition 6.4.2 (Positive example). Let X be a set of variables. Let Σ be a ranked alphabet of constructor symbols, and R be a ranked alphabet of relation symbols. A learning example is a pair p( t), σ where p ∈ R is an abstract predicate, t are patterns of T (Σ, X ) and σ : X → T (Σ) a substitution. An example p( t), σ represents the fact that p( tσ) should be satisfied in the final model.

For instance, consider again the previous has-length example, and assume the learner just produced a model in which has-length is instantiated as the empty predicate.

Then the CHC has-length(nil, 0) is violated. The teacher then produces the example has-length(nil, 0), ∅ for the learner so that in the next iteration the model satisfies has-length(nil, 0).

Negative constraint If the formula is of the form ∀X .α 1 ∧ • • • ∧ α n ⇒ f alse, then the teacher can extract a negative constraint: a valuation for which the formula is satisfied when it should not. Definition 6.4.3 (Negative constraint). A learning negative constraint is a set of pairs { p 1 ( t 1 ), σ 1 , . . . , p k ( t k ), σ k } where for each i ∈ [1, n], p i ∈ R is an abstract predicate, t i are patterns of T (Σ, X ) and σ i : X → T (Σ) a substitution. A negative constraint represents the fact that there must exists i such that p i (t i σ i ) is not satisfied in the final model.

For instance, assume the learner just produced a model in which has-length is instantiated as the true predicate (such that for all l, n, has-length(l, n) is satisfied). Then the CHC ∀y.¬has-length(nil, s(y)) is violated. The teacher can then produce the following negative constraint has-length(nil, s(y)), {y → 0} for the learner so that in the next iteration the model does not satisfy has-length(nil, s(0)).

Implication constraint

The distinctive feature of the ICE framework is to make use of formula of the form ∀X .α 1 ∧ • • • ∧ α n ⇒ p( t) where the head is an abstract predicate application. If it is violated in the current model, then the teacher extracts an implication constraint. Definition 6.4.4 (Implication constraint). A learning implication constraint is a pair (N, p( t), σ ) where N is a negative constraint. An implication constraint represent the fact that in the final model, if the negative constraint N is not satisfied, then p( tσ) must be satisfied.

For instance, assume the learner just produced a model in which has-length is instantiated as the singleton {(nil, 0)} (which satisfies the constraints given in the previous examples). Then the CHC ∀x, y, l.has-length(l, y) ⇒ has-length(cons(x, l), s(y)) is violated. The teacher can then produce the following implication constraint ({ has-length(l, y), σ }, has-length(cons(x, l), s(y)), σ ) for the learner, where σ = {l → nil, y → 0} and σ = {x → a, l → nil, y → 0}. Then in the next iteration we have either has-length(cons(a, nil), s(0)) satisfied, or not has-length(nil, 0). Of course the later is not possible because of the previous positive example, so the only possibility is to have has-length(cons(a, nil), s(0)). Definition 6.4.5 (ICE Constraints Satisfaction). For an ICE constraint s, we write M |= s when the model M verifies s according to the following:

• If s is a positive example of the form p( t), σ , then M |= s iff M (p)( tσ);

• If s is a negative constraint of the form { p 1 ( t 1 ), σ 1 , . . . , p n ( t n ), σ n }, then M |= s iff there exists i ∈ [1, n] such that ¬M (p i )( t i σ i );
• If s is an implication constraint of the form ({ . . . , p i ( t i ), σ i , . . . }, p( t), σ ), then M |= s iff either there exists i such that ¬M (p i )( t i σ i ) or M (p)( tσ).

For a set of ICE constraints I, we write M |= I when for each s ∈ I we have M |= s

The Learner

The learner's goal is to output a candidate model (M i+1 ) using as input the constraints issued by the teacher during the previous iterations (∪ k∈[0,i] T eacher(M k )). It is completely driven by the teacher and does not have access to the CHC system itself. This simplifies the role of the learner as it only has to focus on concrete examples. In [START_REF] Champion | Ice-based refinement type discovery for higher-order functional programs[END_REF], the learner is implemented using a predefined set of qualifiers [START_REF] Rondon | Liquid types[END_REF] as in the original ICE framework [START_REF] Garg | Ice: a robust framework for learning invariants[END_REF]. Qualifiers are combined throughout the procedure using a decision tree built using the teacher's learning constraints. The qualifiers generally includes usual relations between numerical values which make it particularly well suited to learn numerical relations, but not regular relations on algebraic data types. In Section 6.4.4 we define a new SMT-based learner implementation that can infer tree automatic relations as defined in Section 6.3.

The Teacher

In this section we give some details on our implementation of the teacher in the case of tree automatic relations (cf. Section 6.3). The complete formalization of our implementation of the teacher is not yet ready, but this section tries to give a general idea of the principles underlying our algorithm. Remember that tree automatic relations are defined as regular relations represented using convoluted regular languages. Hence in our setting, the model M given to the teacher is defined as an application that maps each abstract predicate p to a regular language M (p).

From M and a CHC system S, our teacher can extract at least one learning constraint for each violated CHC of S.

Synchronized Convoluted Runs

For a given CHC ϕ, the problem of finding a valuation that violates ϕ can be reduced to synchronized search of terms in a set of languages. For instance, consider again the CHC clause ∀y.¬has-length(nil, s(y)) with the model M such that M (p) = {l⊗n | l ∈ List, n ∈ N}. To find a negative constraint we need to search in the regular language M (p) a term that matches the pattern nil ⊗ s(y). For example the term nil ⊗ s(0) matches this pattern and gives us the negative constraint { has-length(nil, s(y)), {y → 0} }.

To generalize, for any CHC of the form ∀X . p 1 ( t 1 ) ∧ • • • ∧ p n ( t n ) ⇒ f alse, we need to find a substitution σ such that for all i, ( t i σ) ⊗ ∈ M (p i ). The same reasoning can be applied with a little more work to other forms of CHC formula. If ϕ has the form ∀X . p( t), the teacher can find an (counter-)example by looking for a substitution σ such that ( tσ) ⊗ ∈ M (p) which can be done just as before by considering the complement of M (p). What we want is then ( tσ) ⊗ ∈ M (p). Finally, if ϕ has the form ∀X .p 1 ( t 1 ) ∧ • • • ∧ p n ( t n ) ⇒ p( t), we need to find a substitution σ such that ( tσ) ⊗ ∈ M (p) and for all i, ( t i σ) ⊗ ∈ M (p i ). In all of these situations, finding σ is done by finding a synchronized convoluted run in a set of tree automata. Definition 6.4.6 (Synchronized Convoluted Run). Let Ω be a set of pairs A, t where A is a tree automaton and t patterns of T (Σ, X ). Note that t may not be linear. A synchronized convoluted run over Ω is represented by substitution σ : X → T (Σ) such that for all A, t , ( tσ) ⊗ ∈ L(A). We write SR(Ω) the set of synchronized convoluted runs over Ω.

Our teacher works by searching for at least one element of SR(Ω) for each clause of the system. If SR(Ω) is empty, the clause if verified. If SR(Ω) is not empty, the clause is violated and we can use the extracted element of SR(Ω) to build a learning constraint to give to the learner. In the rest of this section we give a constructive definition of SR(Ω) that can be used to design a search algorithm. Note that the algorithm itself is not given in this document. It closely follows the inductive definition of SR(Ω) given below, but require the addition of a loop detection system to cope with the infinite nature of SR(Ω). First, we start by decomposing the problem by defining SR(Ω) as follows:

SR(Ω) = A, t SR(A, t)
where SR(A, t) is the set of synchronized runs described by σ such that tσ ⊗ ∈ L(A).

Theorem 12. Let Σ be a ranked alphabet, with A a tree automatic binary relation for the standard convolution ⊕. For all terms s, t ∈ T (Σ, X ) and state q of A: sσ ⊕ tσ ∈ L(A, q) ⇐⇒ σ ∈ SR ⊕ (A, q, (s, t))

Proof. First, we prove that for all s, t ∈ T (Σ, X ), for all σ, for all state q, sσ ⊕ tσ ∈ L(A, q) ⇒ σ ∈ SR ⊕ (A, q, (s, t)). We proceed by strong induction on the maximum depth of both sσ and tσ, max(|sσ|, |tσ|).

• if max(|sσ|, |tσ|) = 1 then there exists some f and g such that sσ = f and tσ = g. By applying (1) and (2) if s or t are variables we know that SR ⊕ (A, q, (s, t)) includes every substitution σσ with σ ∈ SR ⊕ (A, q, f, g). By (4), we know that ∅ ∈ SR ⊕ (A, q, f, g), hence we deduce that σ ∈ SR ⊕ (A, q, (s, t)).

• if max(|s|, |t|) > 1 then If s = x then we can decompose σ into σ α σ β with σ α = {x → f (x 1 , . . . , x n )}. Using (1) we know that R A (q, (s, t)) includes every σ α σ such that σ ∈ SR ⊕ (A, q, f (x 1 , . . . , x n ), tσ α ). We need to show that σ β ∈ SR ⊕ (A, q, (f (x 1 , . . . , x n ), tσ α )). If tσ α = y then we can decompose σ β into σ γ σ δ with with σ γ = {y → g(y 1 , . . . , y n )}. Using (2) we know that R A (q, (f (x 1 , . . . , x n ), tσ α )) includes every σ γ σ such that σ ∈ SR ⊕ (A, q, f (x 1 , . . . , x n )σ γ , g(y 1 , . . . , y n )). We need to show that the substitution σ δ is in SR ⊕ (A, q, (f (x 1 , . . . , x n )σ γ , g(y 1 , . . . , y n ))). By definition, since f (x 1 , . . . , x n )σ γ σ δ ⊕ g(y 1 , . . . , y n )σ δ ∈ L(A, q), there exists a transition in A of the form f g (q 1 , . . . , q k ) → q such that for each i, x i σ γ σ δ ⊕ y i σ δ ∈ L(A, q i ) (where x i σ γ σ δ = • and y i σ δ = • when undefined, according to Definition 6.3.1). Since max(|x i σ γ σ δ |, |y i σ δ |) < max(|sσ|, |tσ|), by hypothesis of (strong) induction we deduce that σ δ ∈ SR ⊕ (A, q i , (x i σ γ , y i )). By (3) we deduce that σ δ ∈ SR ⊕ (A, q, (f (x 1 , . . . , x n )σ γ , g(y 1 , . . . , y n ))). Going back we deduce that σ β ∈ SR ⊕ (A, q, (f (x 1 , . . . , x n ), tσ α )) and that σ ∈ SR ⊕ (A, q, (s, t)). If s or t are not variables, we can use the same reasoning without using (1) and (2) and still get sσ ⊕ tσ ∈ L(A, q). Now we prove that for all s, t, for all σ, σ ∈ SR ⊕ (A, q, (s, t)) ⇒ sσ ⊕ tσ ∈ L(A, q). We proceed by induction on the maximum depth of both sσ and tσ, max(|sσ|, |tσ|).

• If max(|sσ|, |tσ|) = 1. If s = x, then using (1) σ can be decomposed into σ α σ β with σ β ∈ SR ⊕ (A, q, f, tσ α ) and σ α = {x → f }. If tσ α = y, then using (2) σ β can be decomposed into σ γ σ δ with σ δ ∈ SR ⊕ (A, q, f σ γ , g) and σ γ = {y → g}. By definition we deduce there must exist a transition f g → q in A, which means that f g ∈ L(A, q), f ⊕ g ∈ L(A, q). Because f = f σ γ and g = yσ γ we get f σ γ ⊕ yσ γ ∈ L(A, q). Since both f σ γ and yσ γ are closed, we can apply σ δ ans still have f σ γ σ δ ⊕ yσ γ σ δ ∈ L(A, q). And because σ γ σ δ = σ β we get f σ β ⊕ yσ β ∈ L(A, q). Now because y = tσ α and f = sσ α then sσ α σ β ⊕ tσ α σ β ∈ L(A, q). Finally since σ α σ β = σ we deduce that sσ ⊕ tσ ∈ L(A, q). Again if s or t are not variables, we can use the same reasoning without using (1) and (2) and still get sσ ⊕ tσ ∈ L(A, q).

• If max(|sσ|, |tσ|) > 1 If s = x, then using (1) σ can be decomposed into σ α σ β with σ β ∈ SR ⊕ (A, q, f (x 1 , . . . , x n ), tσ α ) and σ α = {x → f (x 1 , . . . , x n )}.

If tσ α = y, then using (2) σ β can be decomposed into σ γ σ δ with σ δ ∈ SR ⊕ (A, q, f (x 1 , . . . , x n )σ γ , g(y 1 , . . . , y m )) and σ γ = {y → g(x 1 , . . . , y n )}. The by definition, there exists a transition f g (q 1 , . . . , q k ) with k = max(n, m) such that for each i ∈ [1, k] we have σ ∈ SR ⊕ (A, q i , (x i σ γ , y i )) where, following (3),

x i σ γ = • if i > n and y i = • if j > m.
Then by hypothesis of induction we know that x i σ γ ⊕ y i ∈ L(A, q i ), from which we deduce that f (x 1 , . . . , x n )σ γ ⊕ g(y 1 , . . . , y m ) ∈ L(A, q). Because σ γ = {y → g(x 1 , . . . , y n )} we have f (x 1 , . . . , x n )σ γ ⊕yσ α σ γ ∈ L(A, q). Because f (x 1 , . . . , x n )σ γ and yσ α σ γ are closed, we can safely apply σ δ is still have f

(x 1 , . . . , x n )σ γ σ δ ⊕ yσ α σ γ σ δ ∈ L(A, q). Because σ β = σ γ σ δ then f (x 1 , . . . , x n )σ β ⊕ yσ α σ β ∈ L(A, q). Be- cause σ α = {x → f (x 1 , . . . , x n )} we have xσ α σ β ⊕ yσ α σ β ∈ L(A, q).
Finally because σ = σ α σ β we have xσ ⊕ yσ ∈ L(A, q) from which we conclude that sσ ⊕ tσ ∈ L(A, q). Once again if s or t are not variables, we can use the same reasoning without using (1) and (2) and still get sσ ⊕ tσ ∈ L(A, q).

The inductive definition of SR(Ω) is not quite sufficient to be directly translated into an actual algorithm. Such an algorithm would also require the addition of a loop detection system to cope with the infinite nature of SR(Ω), which is not given in this document but implemented in our Regular CHC solver [START_REF] Haudebourg | Regular chc solver[END_REF]. If it follows our definition of SR(Ω), we can still show that the teacher is sound and complete (when it terminates), but have not yet proved its termination. Conjecture 6.4.1 (Teacher Termination). For all CHC constraint system S and model M of S, T eacher(M ) is defined.

Theorem 13 (Teacher is Sound). Let S be a CHC constraint system and M a model for S. For all ICE constraint s ∈ T eacher(M ) we have M |= s.

Proof. If s is a positive example of the form p( t), σ then σ ∈ SR({ M (p), t }) which has been computed because there exists a clause ϕ ∈ S of the form ∀X .p(t). By Theorem 12 this means that ( tσ) ⊕ ∈ L(M (p)) which means that M |= ϕ. Otherwise if s is a negative example of the form { p 1 ( t 1 ), σ , . . . , p n ( t n ), σ } then σ ∈ SR({ M (p 1 ), t 1 , . . . , M (p n ), t n }) which has been computed because there exists a clause ϕ ∈ S of the form ∀X .p 1 (t 1 ) ∧ • • • ∧ p n (t n ) ⇒ false. By Theorem 12 this means that for all i, ( t i σ) ⊕ ∈ L(M (p i )) which means that M |= ϕ. Otherwise s is an implication constraint of the form { p 1 ( t 1 ), σ , . . . , p n ( t n ), σ }, p( t), σ then SR({ M (p 1 ), t 1 , . . . , M (p n ), t n , M (p), t }) which has been computed because there exists a clause ϕ ∈ S of the form ∀X .p 1 (t 1 )∧• • •∧p n (t n ) ⇒ p( t). By Theorem 12 this means that for all i, ( t

i σ) ⊕ ∈ L(M (p i )) and ( tσ) ⊕ ∈ L(M (p)) which means that M |= ϕ.
Theorem 14 (Teacher is Complete). Let S be a CHC constraint system. Let M be a model. If there exists a clause ϕ ∈ S such that M |= ϕ then T eacher(M ) = ∅ Proof. If ϕ is of the form ∀X .p(t) then by Theorem 12 there exists a substitution σ in SR({ M (p), t }). Then p( t), σ ∈ T eacher(M ). Otherwise if ϕ is of the form ∀X .p 1 (t 1 ) ∧ • • • ∧ p n (t n ) ⇒ false then by Theorem 12 there exists σ ∈ SR({ M (p 1 ), t 1 , . . . , M (p n ), t n }). Then { p 1 ( t 1 ), σ , . . . , p n ( t n ), σ } ∈ T eacher(M ). Otherwise ϕ is of the form ∀X .p 1 (t 1 )∧• • •∧p n (t n ) ⇒ p( t) then by Theorem 12 there exists a substitution σ in SR({ M (p 1 ), t 1 , . . . , M (p n ), t n , M (p), t }). Then { p 1 ( t 1 ), σ , . . . , p n ( t n ), σ }, p( t), σ is in T eacher(M ).

A solution φ to an SMT constraint system gives a renaming (or interpretation) of the working automata states into a new set of states Q for which the constraints are respected. It also gives φ(p), the set of Q-states that belong to p according to the SMT constraints. For each predicate p, the automaton φ(A (p)) is Σ, Q, φ(p), φ(∆) where ∆ is the TRS defining A (p), and φ(∆) is obtained by renaming the states in ∆ using φ.

Example 6.4.2. Consider the following CHC system specifying the equality predicate eq on natural numbers: eq(0, 0) ∀y. ¬eq(0, s(y))

∀x. ¬eq(s(x), 0) ∀x, y. eq(x, y) ⇐⇒ eq(s(x), s(y))

Suppose that the teacher already issued to the learner the following ICE constraints I about the eq predicate: one positive example eq(0, 0), one negative constraint { eq(0, s(0)) } and one implication constraint { eq(0, 0) }, eq(s(0), s(0)) . We start by building the working context A along with the SMT constraints γ derived from the ICE constraints. This is done by computing A , γ = Γ(A 0 , I) where A 0 is the empty working context. Let us begin with the positive constraint by computing Γ(A 0 , eq(0, 0)). According to Definition 6.4.10, the positive constraint is translated into the SMT constraint "eq(q 1 )", where q 1 is the state recognizing the term (0 ⊗ 0) in A 1 (eq), where A 1 the new working context derived from A 0 , where we have added the necessary states in A 1 (eq) to recognize (0 ⊗ 0) in q 1 . According to the same definition, The SMT constraint associated with the negative ICE constraint { eq(0, s(0)) } is "¬eq(q 2 )" where q 2 is the state recognizing the term (0 ⊗ s(0)) in A 2 (eq) (derived from A 1 ). Finally the SMT constraint associated with the ICE implication constraint { eq(0, 0) }, eq(s(0), s(0)) is "eq(q 1 ) ⇒ eq(q 3 )" where q 3 the state recognizing (s(0) ⊗ s(0)) in A 3 (eq) (derived from A 2 ). The final working context defines the following automaton for eq:

0 0 → q 1 0 s (q 2 ) → q 2 • 0 → q 2 s s (q 1 ) → q 3
associated with the following SMT constraints in γ: eq(q 1 ) ¬eq(q 2 ) eq(q 1 ) ⇒ eq(q 3 ) A minimal solution φ to this constraint system is composed of two states q and q such that φ(q 1 ) = φ(q 3 ) = q and φ(q 2 ) = φ(q 2 ) = q with φ(eq) = { q } which correspond to set final states of the following solution automaton φ(A (eq)) (once reduced):

0 0 → q s s (q) → q
This tree automatic relation gives a candidate instantiation of the eq predicate that satisfies the ICE constraints given by the teacher. In fact, this tree automatic relation is a solution to our initial CHC system.

Theorem 16 (Learner is sound). Let I be a set of ICE constraints. Then we have Learner (I ) |= I.

Proof. Let M = Learner(I). For each s ∈ I we have M |= s.

• If s is a positive example of the form p( t), σ then by definition of A , γ = Γ(A , I) there exists a unique state q of A (p) such that tσ ⊗ ∈ L(A (p), q), and γ contains the constraint " p(q) ". Then by definition of S(I) and φ I , there exists a unique state q of M (p) such that tσ ⊗ ∈ L(M (p), q) and q is a final state M (p). Then by definition, M |= s.

• If s is a negative constraint of the form { p 1 ( t 1 ), σ 1 , . . . , p n ( t n ), σ n } then by definition of A , γ = Γ(A , I) for each i ∈ [1, n] there exists a unique state q i of A (p i ) such that t i σ i⊗ ∈ L(A (p i ), q i ). And in addition, γ contains the constraint " ¬p 1 (q 1 )∨• • •∨¬p n (q n ) ". Then by definition of S(I) and φ I , for each i ∈ [1, n] there exists a unique state q i of M (p i ) such that t i σ i⊗ ∈ L(M (p i ), q i ), and there exists one i for which q i is not a final state of M (p i ). Then by definition, M |= s.

• If s is an implication constraint of the form { p 1 ( t 1 ), σ 1 , . . . , p n ( t n ), σ n } then by definition of A , γ = Γ(A , I) for each i ∈ [1, n] there exists a unique state q i of A (p i ) such that t i σ i⊗ ∈ L(A (p i ), q i ) and there exists a unique state q of A (p) such that tσ ⊗ ∈ L(A (p), q). And in addition, γ contains the constraint " ¬p 1 (q 1 ) ∨ • • • ∨ ¬p n (q n ) ∨ p(q) ". Then by definition of S(I) and φ I , there exists a unique state q of M (p) such that tσ ⊗ ∈ L(M (p), q) and for each i ∈ [1, n] there exists a unique state q i of M (p i ) such that t i σ i⊗ ∈ L(M (p i ), q i ).

And either there exists one i for which q i is not a final state of M (p i ) or q is a final state of M (p). Then by definition, M |= s.

Note the the learner does not depend on the actual definition of the convolution operator. In fact, we can design the interaction between the teacher and the learner in such a way that learning constraint are already expressed as convoluted terms. This way, only the teacher needs to reason about convolutions. The learner only has to reason about terms in simple tree automata.

Soundness and Completeness

We now show some important properties about the whole procedure combining the defined teacher and learner. We show that it is sound and complete on regular models. Remember however that we are still working under Conjecture 6.4.1.

Theorem 17 (Soundness). Let S be a CHC system and i such that

M i+1 = M i . Then M i |= S.
Proof. If M i+1 = M i then by definition (T eacher)(M i ) = ∅. This means that for each CHC in S, the teacher has not generated any ICE learning constraint. From Theorem 14 we deduce this is because M i |= S.

Theorem 18 (Completeness). Let S be a CHC system. If there exists some regular model M (mapping each abstract predicate to a tree automatic relation) that verifies S, then this procedure will eventually find it.

Proof. If there exists some regular model M such that M |= S, then it must verifies every ICE constraint generated by the teacher and can be represented using a finite number of automata states N . By definition, the learner always build a minimal model (in the number of tree automata states) that verifies the ICE constraints generated by the teacher. Furthermore, because new constraints are introduced after each iteration i if M i |= S and old constraints are preserved, then M i+1 is different than every previously generated model. And since there are a finite number of models with less than N automata states, we know the procedure will eventually produce the solution with N states (or an equivalent solution with N states).

Experiments

We have implemented the tree automatic relation learning procedure described in this chapter in our own Regular CHC Solver [START_REF] Haudebourg | Regular chc solver[END_REF] written in Rust and using CVC4 [BCD + 11] as our SMT solver backend for the learner (cf. Section 6.4.4). For now, only the standard convolution operator has been implemented, but it is enough to explore the practicability of the procedure. To our knowledge, this is the only existing CHC solver designed to handle regular relations on algebraic data types. Hence, the purpose of this section is not to compare our implementation with others, but to simply witness the advantages and limits of our technique.

Test Suite

This time the test suite is different from the one used in the previous chapters. The test suite used in this section, available on the solver's repository [START_REF] Haudebourg | Regular chc solver[END_REF], is limited to positive first-order regular relational problems. Each problem is expressed as a CHC system written in the SMT-LIBv2 specification language format. We consider here two categories of problems: relation inference, and relational safety property verification.

Relation inference Relation inference problems consist in the exact inference of a desired relation described using CHC constraints. It is the direct application of what have been described in this chapter. For instance, one of the considered problems consists in inferring the relation { (x, y) | x ∈ N, y ∈ N, x ≤ y } using the following CHC system:

∀n. 0 ≤ n ∀n. ¬(s + 1 ≤ 0) ∀n, m. n ≤ m ⇐⇒ n + 1 ≤ m + 1
This relation is regular and can be exactly inferred using our CHC solver.

Relational safety property verification In the context of functional program verification, most functions cannot be represented as regular relations. However we can still use our regular CHC solver to infer regular over-approximations of a given function and verify relational properties over it. For instance, consider the add function defined by the following TRS: add(0, y) → y add(s(x), y) → s(add(x, y))

This function can be represented by the ternary relation associating its inputs to the correct output: R = { (x, y, z) | add(x, y) → * z }. This relation is not regular, and cannot be exactly inferred by our CHC solver. However it is possible to infer a regular over-approximation of the relation using the following CHC system: ∀y. add(0, y, y) ∀x, y. add(x, y, z) ⇒ add(s(x), y, s(z))

This CHC can easily be derived from the rewriting system. A solution to this problem is a regular relation that over-approximates R. It can be used to verify safety properties over the rewriting system by adding supplementary clauses to the system. For instance we can verify that add(s(x), s(y)) never rewrites to 0 by adding the following clause: ∀x, y. ¬add(s(x), s(y), 0)

By providing an over-approximation of R, the solution model proves that add respects the last constraint even though this relation is not regular.

Lists alignment

We have seen that with the standard convolution, it is not directly possible to relate the length of a list with an integer because of alignment considerations. As showed in Figure 6.2 it is possible to solve this problem by encoding the lists tail first. We have used this solution in our test suite to verify some properties such as length (make-list n) = n.

Experimental Results

Table 6.1 shows the results of our experiments. The first column gives a short description of the inferred relation (first half of the table) or the regular safety property verified (second half of the table). The second column shows the execution time averaged over 10 runs on a Intel® i7-7600U CPU, 4 2.80GHz cores, with the detailed contribution of the teacher and the learner. We observe that our regular CHC solver is able to verify regular relational properties that were out of the scope of previous techniques with satisfying performances. For now, since only the standard convolution has been implemented, only relations over linear structures such as lists and integers can be tested. However even if a degradation of the performances is to be expected with the full convolution, these preliminary results are promising.

Conclusion

In this chapter we have built a Constrained Horn Clauses solver able to automatically infer regular models. This regular CHC solver is built upon tree automatic relations, an extension of string automatic relations that consists in representing relations as regular languages of convoluted terms. We have seen that in the case of tree automatic relations, multiple definitions of the convolution operator exist. In particular, we have defined a general convolution operator that extends the standard definition and can be used to represent strictly more tree relations as regular languages. Our regular CHC solver is an instance of the ICE [GLMN14, GNMR16, CCKS18] procedure, a robust framework for learning invariant using implication counter examples by conceptually separating a teacher and a learner. In our case the teacher part, in charge of validating candidate model, is reduced to the problem of finding synchronized convoluted runs in multiple automata. We provide a constructive definition of the set of synchronized convoluted runs for a given problem. On the other side, the learner, in charge of generating new models using the teacher generated constraints, is defined as an SMTbased tree automaton abstraction procedure similar to the one defined in Chapter 5 (Section 5.3.4). We have implemented our CHC solving technique in Rust, and used it to both automatically infer regular relations and automatically verify relational safety properties on tree-processing functional programs by over-approximating functions. For now, only the standard convolution has been implemented, but preliminary results are encouraging as it can verify relational properties on non-trivial function (such as the insertion sort) that were out of the scope of previous chapters techniques in a few millisecond. Some work remains to formally define the synchronized convoluted runs finding algorithm used by the teacher to (in)validate candidate models, and to adapt it to support the full convolution definition. Some more work remains to modularize this technique which could allow it scale and would allow us to define a more advanced regular language type system with relations.

Chapter 7

Conclusion and Future Work

In this thesis we have seen in details how regular tree languages and rewriting systems can be used to automatically verify regular safety properties on higherorder tree-processing functional programs. We showed that (1) it is possible to design complete regular abstraction procedures to find an over-approximation of a higher-order program's reachable states suitable to verify a target property (Chapter 4 and 5), (2) it is possible to modularize the procedure to analyze functions separately and handle more complex problems by stating the problem as a type inference procedure (Chapter 5), (3) it is possible to extend our abstraction procedure to automatically verify relational properties on functional programs without leaving the realm of regular languages using tree automatic relations (Chapter 6).

Equational Abstractions In Chapter 4 we pursued the work on the state of the art equational abstraction method based on the Tree Automata Completion algorithm and extended it to handle higher-order programs while preserving termination guarantees. We formally defined an automatic abstraction procedure based on this algorithm and proved that it is complete on a subclass of regular properties and complete in refutation. However we also showed that it is not possible to achieve full regular completeness with equational abstractions.

Regular Type Inference For this reason in Chapter 5 we defined a new verification technique based on the regular abstract interpretation of term rewriting systems. Instead of equations, this new technique uses an SMT-based regular language learning procedure to abstract the program execution, and can build non-functional regular abstractions. This allows the procedure to tackle any regular safety problem. By using this procedure as a component of a regular language types inference procedure, the analysis can be modularized to scale better when facing more complex problems. This technique has been implemented as the fourth version of the Timbuk [START_REF] Haudebourg | Timbuk 4: Regular verification framework based on tree automata and term rewriting systems[END_REF] verification tool that outperform the previous version on our test suite.

Regular Relations Finally in Chapter 6 we explored how regular languages can be used to automatically verify relational properties. To do that we adapted our SMT-based regular language learning procedure to learn regular tree relations, a family of relations that can be encoded with regular languages by applying a convolution operator over each element of the relation. We extended the standard convolution operator into a full convolution operator that can be used to encode more relations with regular languages. We then defined a regular relation inference procedure based on the ICE framework dedicated to CHC solving and implemented it in our own 147

Regular CHC solver [START_REF] Haudebourg | Regular chc solver[END_REF]. To our knowledge this is the first formalization and implementation of a complete regular tree relation inference procedure.

Future Work

In order to focus on the core issues related to regular language inference, we focused on the verification of regular safety properties on terminating programs already expressed as term rewriting systems and without polymorphism. This offers plenty of future research directions to find out how the our techniques can be extended to handle non-safety properties on non-terminating programs, on actual functional programming languages with type polymorphism. In the following we give some leads on how to approach these issues.

Non-Terminating Programs

The strong limitation imposed by our verification procedure so far is to require the TRS encoding the program to be terminating. This is required by the Tree Automata Completion algorithm used in our SMT-based iterative inference procedure in Section 5.3.4. At each iteration, this algorithm is used to compute the set terms reachable from a finite subset of the initial states of the program. Since the computation is exact, termination of the program is needed to guarantee the termination of the TAC algorithm. One possible way to lift this requirement is to perform a finite number of TAC completion steps (cf. Definition 4.2.3) at each iteration. If a term considered in the procedure has a normal form, it will eventually be found at some iteration. By definition, no constraints (other than to ensure determinism) will be generated over states of the completed automaton recognizing terms without normal forms (or for which the normal form is not yet known). This means that the resulting abstraction is not impacted by those states. This would allow us to keep the same completeness properties without requiring the program to terminate. However this may slow down the number of iterations necessary for the procedure to converge to a valid abstraction.

Non-Safety Problems

For now we only have discussed the verification of safety regular properties of the form (µx.¬φ ∧ x) (cf. Definition 2.4.2). As stated in Section 2.4.2, safety properties can be verified using a simpler family of abstraction: collapsing abstractions (cf. Definition 2.4.9). For a program defined by a term rewriting system R, a collapsing abstraction is an R-closed abstraction. In Chapter 4, the equation set E R is responsible for the generation of collapsing abstractions, and it is the foundation of the termination guarantees of the equational TAC algorithm. In Chapter 5, our SMT-based regular language learning procedure is also designed to find collapsing abstractions. However it can be adapted to find non-collapsing abstractions. Remember that in this iterative procedure, a candidate abstraction is built from a finite REFD automaton A Σ, Q, Q f , ∆ representing an R-closed fragment of R * (I) where I is an initial language (the set of initial states), and a target type partition T encoding the safety property. The candidate abstraction is generated by solving the constraint system S(A, T ) (cf. Definition 5.3.10). The most important constraints of S(A, T ) required to solve the safety problem are of the form q = q when ∃τ, τ ∈ T. q ∈ T A (τ ) ∧ q ∈ T A (τ ) ∧ τ = τ where T A (τ ) = { q ∈ Q f | ∃t ∈ γ(τ ). t → * R q }. To simplify, consider T = {true # , false # }. This constraint will separate final states that rewrites to true from the ones that rewrite to false. From a model checking point of view, A defines a model M A = Q, ∆ , V using T as the set of propositions where ∆ = { q → q | q → q ∈ ∆ } (the direction of the arrow is changed to represent the rewriting relation) and V (τ ) = { q ∈ Q γ(τ ) ∩ L(A, q) = ∅ }. From this point of view, the constraints in S(A, T ) will separate states that verifies the following property from those who do not:

¬(µx. (false # ∨ x))

One can recognize the shape of a safety property, which explains why this procedure can verify safety properties. A possible way to extend the abstraction procedure to any property is to replace S(A, T ) by an SMT-constraints system S(A, φ) directly defined from the modal µ-calculus formula φ representing the property to verify. As with S(A, T ), the constraints of S(A, φ) must ensure determinism of the final abstraction automaton, and the separation of the abstraction of the final states for which φ is verified from those for which it is not. Note however that the generated abstraction is still a subterm-collapsing abstraction of the model M R = R * (I), → R , V (cf. Definition 4.2.4). This is because the automaton A from which the constraints are generated is already a subterm-collapsing abstraction of M R . One state of Q does not recognize only one term of R * (I): if q ∈ Q recognizes the term f (t 1 , . . . , t n ), it will also recognize every term of the form f (u 1 , . . . , u n ) where t i → * R u i for each i. This is a property inherited from the Tree Automata Completion algorithm that is also passed on to the final abstraction. This means that even if generalized, this procedure can only verify regular properties that can be verified with subterm-collapsing abstractions. This is the case of regular safety properties. It is also the case of any other regular property as long as the TRS encoding the program is expressed in continuation passing style (CPS).

Example 7.2.1 (Resource usage problem). In this example we use the generalization described above to verify a (non-safety) resource usage problem on a program. Consider the following program first used in Section 3.2.1: let rec g x = if _ then close x else (read x; g x)

The _ expression stands for a non-deterministic boolean value. We want to verify that from the initial state (g file) (where file is an opened file) no read occurs after a close. This program can be translated into a CPS term rewriting system as follows: Of course a lot of work remains to properly formalize this concept and prove its correctness and completeness. It is also worth noticing that as it is defined above, this cannot be directly used in a type oriented verification procedure such as the one defined in Chapter 5, which can only express safety properties. Some more work remains to modularize this idea to have a chance to handle larger programs.

Integration in Higher-Order Functional Languages

The abstraction procedures presented in Chapters 4 and 5 are based on a representation of programs as term rewriting systems. Our goal however is not to impose this representation to programmers but instead use it as an intermediate representation for regular verification. This means that we must provide front-ends to real-world higher-order functional programming languages such as OCaml or Haskell in charge of translating from the original source code to TRS. There are multiple practicable difficulties arising from the translation into TRS:

• Primitive data types and operations must be encoded using terms. For instance, (positive) integers can be encoded using Peano's number representation. The expression x + 1 is translated into s(x).

• It is not possible to use anonymous functions in a rewriting system. Every anonymous function must first be given a name so that rewriting rules can be created for it. Local function definitions must also be given global names. For instance, to translate the following OCaml expression (function x -> x + 1), we must first give a name, f , to the anonymous function and produce the following rewriting rule for it: @(f, x) → s(x). This is commonly referred to as a Lambda-lifting operation.

• Since local closures are lambda-lifted, we must find a way to preserve local bindings. One way is to use function symbols with non-zero arity, where the arity of a functional symbol f correspond to the number of locally bound variables used by the function. For instance we can encode the expression (function y ->

x + y) (where x is a locally bound variable) by lambda-lifting the anonymous function with the symbol f (x) and the rewriting rule @(f (x), y) → add(x, y).

• In a pattern matching control structure, each matching case can be translated as a rewriting rule. However it must first be disambiguated w.r.t. the other cases since all the rules of a TRS have the same priority, whereas languages such as OCaml gives more to priority to some cases depending on the order of declaration in the pattern matching structure. For instance, the expression Such disambiguation can be done, for instance, using Krauss's pattern disambiguation method [START_REF] Krauss | Pattern minimization problems over recursive data types[END_REF].

Having this translation into TRS would allow us to extend our test suite with realworld programs and extensively test its scalability. Of course only purely functional features can be translated into TRS. As a first step toward this, we wish to integrate our verification technique in the OCaml interpreter. The idea is to add a new top level expression of the form forall <variables>. <expression> that automatically checks that the given OCaml expression always return true for all instances of the input variables. Another less straightforward idea would be to allow users to directly or indirectly specify regular language types in the signatures of functions. This could be more flexible for the user and could be used to give hints to the regular language type inference procedure. For instance, the signature of the sort function could be given as follows:

sort : t list -> { l | sorted l }
Here { l | sorted l } indirectly refers to the regular language type of sorted lists of type t list. It can be easily computed by our inference procedure using the definition of sorted, as long as the type of list elements (t) is finite.

Polymorphic Lifting

Polymorphic types can be used in programming languages such as OCaml to define functions working with arbitrary types. For instance, the signature of the higher-order sort function can be generalized into sort : ('a -> 'a -> bool) -> 'a list -> 'a list where 'a -> 'a -> bool is the signature of the comparison function used to sort elements of type 'a, a type variable universally quantified. Such type variables are not currently supported by our verification procedure, every type must be known when the analysis starts. This means that polymorphic functions can only be verified on a finite set of type instances. Furthermore, depending on the considered property, the verification problem may or may not be regular depending on the instantiation of 'a. In this example, if we consider the property forall cmp, l. sorted cmp (sort cmp l) : true, we have seen that the verification problem stays regular as long as 'a is replaced by a finite type but becomes irregular when replaced with an infinite type such as int. Since sort works on polymorphic lists, its definition is independent of the actual structure of the manipulated type. It is never constructed nor destructed inside the function itself, so the correctness of sort and our ability to prove it should not depend on the type variable instantiation. In other words, by proving the above property on some (carefully chosen) finite instantiation T of 'a, we should be able to deduce a proof for any type (even infinite ones): We can formally state our polymorphic problem in the following way. Let Σ be a ranked alphabet and X a set of variables with p a pattern of T (Σ, X ). We want to show that pσ → * R f alse for every substitution σ : X → T (Σ ) where Σ can be any ranked alphabet (it is universally quantified to encode polymorphism). We can safely ignore well-typedness considerations here and assume that pσ is always well-typed (if not it would not rewrite to f alse). This proof cannot be directly handled by our technique since Σ is universally quantified. However we propose here an indirect 3-steps method that works for such polymorphic problem where we (1) choose a finite domain instantiation of each type variable (2) prove that property is verified in this finite domain, (3) show that if the polymorphic property is not verified (pσ → * R f alse), then there exists an abstraction of the counter-example into the chosen finite domain such that the property is not verified on this abstract counter-example either. Since no such counter-example exists thanks to (2), the polymorphic property must be verified.

Choosing a finite domain instantiation

In the first step, we instantiate each type variable with a finite domain type. For that we extend Σ with a finite set of constants Σ # populating the finite domain. For a given set of variables, We write T x ⊆ Σ # the monomorphic regular language type associated to the variable x of polymorphic type 'a. For example, in the sort example we can instantiate the type variable 'a with the finite language type T x = {A, B} ⊆ Σ # . Ideally we want to choose the smallest number of constants that allows the verification of the given polymorphic property. Note that we also need to populate polymorphic arrow types such as 'a -> 'a -> bool (the comparison function's type). This is done by again adding new constants T cmp = {cmp 0 , . . . , cmp f } ⊆ Σ # where each cmp i has type {A, B} → {A, B} → bool. We then add the necessary rules in R so that (cmp i x y) can be evaluated (into a Boolean). To be complete, we must represent every possible function of this type. Thankfully since we only deal with finite languages this can be easily constructed by enumerating every possible combination of inputs and outputs. In this example, we need to generate the following rules for R: Finite domain proof The next step is to show that our polymorphic property is verified on this particular domain.

Property 1: For all substitution σ # : X → Σ # such that σ # (x) ∈ T x , pσ # → * R f alse. This can be verified using the technique described in Chapter 5.

Polymorphic lifting Once we have shown that pσ # → * R f alse for each σ # in our finite domain, the last step is to lift this proof to the polymorphic property. The trick is to first show the following intermediate property over p and R.

Property 2: For all ranked alphabet Σ and substitution σ : X → T (Σ ), if pσ → * R f alse then there exists an abstraction Λ = Σ # , ∆ # of Σ such that α(σ(x)) ∈ T x and pσ → * R∪∆ # f alse.

For instance, consider that the safety property on sort we want to verify is not correct. The sort function preserves the elements of the list, but the output is not sorted. Then there exists a type domain instance for 'a and an initial state of the form sorted (sort l) such that sort l returns an unsorted list of the form e 1 , . . . , e i , e j , . . . , e n where (cmp e i e j = f alse) (which is why the list is not sorted). Then we can abstract every element of the domain into the finite abstract domain { A, B } and the comparison function into cmp # in such a way that (cmp # α(e i ) α(e j ) = f alse) so that the result remains unsorted as pictured below. If we can show this property then we can show by contradiction that ∀σ # . pσ # → * R f alse can be lifted for every Σ into the proof that ∀σ.pσ → * R f alse. The idea of the proof is as follows: if there indeed exists Σ and σ such that pσ → * R f alse, then there exists an abstraction such that pσ → * R # ∪∆ # f alse. Since R has been extended with the appropriate rules, this means there exists σ # such that pσ → * ∆ # pσ # → * R f alse. But we already know by hypothesis that pσ # → * R f alse. This contradiction proves that pσ → * R f alse. Property 2 cannot be proved with our procedure because Σ is universally quantified. Finding a way to show this property is the main difficulty of this future work. We believe that it can be done with a relatively simple static analysis of the program's TRS. To be able to go through the last two steps, it may be needed to iteratively increase the number of constants used in Σ # to abstract the polymorphic type variables.

Regular Relations and Higher-Order

In Chapter 6 we have seen how first-order functions could be encoded as predicates into Horn clauses systems in order to be verified. However this idea only works for first-order functions. Another research direction could consist in finding ways to adapt our relational verification technique to handle higher-order programs. Title: Automatic Verification of Higher-Order Functional Programs using Regular Tree Languages Keywords: Program Verification, Higher-order, Functional languages, Regular tree languages Abstract: This thesis studies how regular tree languages can be used to automatically verify properties on higher-order functional programs. Our goal is to develop new techniques and tools for the programmers to develop safer programs while reducing the time and expertize needed to verify them. In particular, we focus on the automatic verification of regular safety properties, a family of properties for which we show that completely and fully automatic verification can be achieved. Our verification method is build upon a regular abstraction procedure that can automatically learn regular tree languages that over-approximates of the reachable states of a program, allowing the verification of a target property. By using regular languages as types we modularize this procedure to verify complex properties by stating them as type inference problems. In addition we study the performances of the overall technique in our prototype OCaml implementation in the Timbuk 4 verification framework over a test suite of more than 80 verification problems. We then show how our abstraction procedure can be used to verify relational properties that seemed out of the scope of regular tree languages. To do that, we use and extend a convolution operator on trees to represent every element of a relation into a regular tree language. We can then extend our previously defied regular language learning procedure to automatically infer such regular relations. We propose a Rust implementation of this idea as a regular solver for Constrained Horn Clauses systems and study its performance on several relational verification problems.

  let rec sort = function | [] -> [] | x::l -> insert x (sort l) and insert x = function | [] -> x::[] | y::l -> if x y then x::y::l else y::(insert x l)

  sorted(nil) → true sorted(cons(x, nil)) → true sorted(cons(x, cons(y, l))) → and(leq(x, y), sorted(cons(y, l))) Une méthode commune pour vérifier ce type de propriétés dites « de sûreté » est de calculer une abstraction du programme sur-approchant son comportement sur l'ensemble des entrées d'intérêt. Dans notre contexte, l'ensemble des entrées d'intérêt est I = { sorted(sort(l)) | l ∈ List }. Notre objectif est donc de calculer un ensemble de termes O tel que • R * (I) ⊆ O, où R * (I) = { u | s → * R t, s ∈ I } ; et • false ∈ O Si un tel O existe, celui-ci prouve la validité de notre propriété. Dans le cas des problèmes réguliers dans le quel nous nous plaçons, O est régulier et peut être représenté à l'aide d'un automate d'arbre.

  Contrairement aux techniques précédentes, les problèmes considérés sont décrits à l'aide de systèmes de clauses de Horn contraintes (CHC) : un ensemble de clauses de la forme∀X . α 1 ∧ • • • ∧ α n ⇒ α n+1 où chaque α i correspond à l'application d'une relation abstraite p(x 1 , . . . , x m ) sur des variables de X , où m est l'arité de la relation p. Résoudre un tel problème revient à trouver un modèle M (une instanciation de chaque relation) tel que M satisfait le système CHC. Dans notre cas, chaque instanciation de relation est donnée sous forme d'une relation régulière tel que défini plus haut. Par exemple, considérons le problème suivant :

  :l -> if leq x y 9 then x::y::l 10 else y::(insert x l)

  1.2 (Terms as Execution States). Every state of a higher-order functional program execution is an expression waiting to be reduced into a value. For instance, consider the following OCaml expression if a then b else c. It can be represented using the term ite(a, b, c) where ite ∈ Σ is introduced to encode the if-then-else control flow structure.

  let rec sort = function | [] -> [] | x::l -> insert x (sort l) let rec insert x = function | [] -> x::[] | y::l -> if x <= y then x::y::l else y::(insert x l)

For

  instance, the term Cons(Red, Cons(Green, Cons(Blue, N il))) is generated by G because of the following rewriting path in ∆: list → Cons(color, list) → Cons(Red, list) → Cons(Red, Cons(color, list)) → Cons(Red, Cons(Green, list)) → Cons(Red, Cons(Green, Cons(color, list))) → Cons(Red, Cons(Green, Cons(Blue, list)))

Figure 2 . 1 :

 21 Figure 2.1: Hierarchy of tree language families

  Example 2.4.3. The execution of the previous even/odd can be finitely abstracted with, for instance, one of the following three models each exposing different properties of the program (the abstraction relation yielding each abstract model is given below):

  { x: p(x) } used to refine a type τ , authors introduce a new notation 1 { x1, ..., xn: p1(x1), ..., pn(xn) } to define a products refinement on the value x1, ..., xn whose type is the product τ 1 × • • • × τ n . This is further extended to encompass sum types with the notation { x1: p1(x1) | ... | xn: pn(xn) } which refines the sum type τ 1 ∨ • • • ∨ τ n . For instance let's consider the following type definition of lists of integers: type ilist = nil | cons of int * ilist

let

  sorted = function | nil -> true | x::nil -> true | x::y::l -> x <= y && sorted y::l let sort :: l:int list -> { r: sorted(r) }:int list = ...

  let insert :: e:int -> { l: sorted(l) }:int list -> { r: sorted(r) }:int list = ...( * type checking this will fail if not given the signature of insert * ) let sort :: l:int list -> { r: sorted(r) }:int list = ...

HereF

  is a non-terminal of type o → o. The start symbol S is rewritten as follows: S → F c → br c (a (F (b c))) → br c (a (br (b c) (a (F (b (b c)))

  To do that, Kobayashi's technique transforms the program into the following HORS:

  Example 4.2.3. Let us compute one step of completion for our add example. By definition

  Lemma 64 in Gen16 (Termination with Contracting Equations on Constructors). Let R be a first-order function TRS defined over T (Σ) with Σ = F ∪ C, A an initial automaton and E c T (C) a set of contracting equations for T (C). If E ⊇ E c T (C) ∪ E R ∪ E r then the Tree Automaton Completion algorithm terminates on R, A and E. This effectively reduce the search space for E by focusing the search on E c T (C) , ignoring function symbols.

Figure 4 . 1 :

 41 Figure 4.1: Completion termination proof idea. Each cross is a term. Plain arrows are rewriting steps and all terms along rewriting steps are equal w.r.t. E R . Dotted arrows represent contractions over E cL . We show that by using E c L and E R the number of equivalence classes (circled with dashed lines) is bounded by |G|.

  and by hypothesis of induction, s ∈ B 0 . If t = f , as previously we have by definition s ∈ B 0 .

2.

  If L(A * k ) does violates the property, i.e. L(A * k ) ⊆ O, then verification fails: a counterexample is found. 3. Otherwise for all E c of E k c , we try to complete A with R and E = E r ∪ E R ∪ E c and check the property on the completed automaton. If A * ⊆ O then verification succeeds. Otherwise, we try the next E c . 4. If no E c remains, we start again with k = k + 1.

  Example 5.1.4. Let us focus on the following fragment of the TRS R used in the previous examples: even 0 → true even s(x) → odd x odd 0 → false odd s(x) → even x

•

  { a b | a, b ∈ Σ ∪ { • } }. Example 6.2.1. Consider Σ = { a, b } with the two words α = ababa and β = baa. The convolution α ⊗ β is equal to a . It can easily be seen as the superposition of the two strings on top of each other.A string automatic relation, or regular relation, is defined as any relation for which the convolution defines a regular language. Every element of this language represents then a convoluted pair of the relation. A simple deconvolution can reconstruct the two items of the pair. Definition 6.2.2 (Regular Relation). A (binary) relation R over strings is regular if the language L(R) = { x ⊗ y | R(x, y) } is regular. Then it can be recognized by a finite automaton. Such an automaton is called an automatic relation. This definition extends to any n-ary relations.Example 6.2.2. Let us consider again the structure N, ≤ of natural number provided with the comparison operator ≤. the relation ≤ can be encoded with the language L(≤) = { x ⊗ y | x ≤ y }. This language contains for instance 0 0 ,

Figure 6 . 1 :

 61 Figure 6.1: Standard convolution between the terms f (g(a), g(a)) and f (a, f (a, a)).This shows how the convolution can be pictured as the overlapping of the two trees.

Figure 6 . 2 :

 62 Figure 6.2: Standard convolution between the terms s(s(0)) and cons(a, cons(b, nil)) (on top) and the terms s(s(0)) and cons(cons(nil, b), a) (below). The relation between the length of the list and s(s(0)) cannot be captured on top because the two trees do not overlap on relevant positions. This is resolved below by putting the recursion site of lists at the first position of the cons symbol.

  Figure 6.3: Difference between the standard convolution ⊕ (on top) and full convolution ⊗ (bottom) between the terms node(node(leaf, leaf ), leaf ) and s(s(0)). This shows how the full convolution allows each branch to be in relation with every other branch.

Time

  x | x ∈ N, x mod 2 = 0 } 0.01±0.0 0.0±0.0 0.01±0.0 { (x, x) | x ∈ N } 0.0±0.0 0.0±0.0 0.0±0.0 { (x, y) | x ∈ N, y ∈ N, x ≤ y }0.001 0.02±0.0 length (insert-sort l) = length l 0.04±0.008 0.27±0.048 0.31±0.05 Table 6.1: Experimental Results

g

  x k → br(close x k, read x (h x k))) h x k () → g x k read x k → k () br(x, y) → x close x k → k () br(x,y) → y end x → x Each function now takes a new parameter k representing the continuation of the program. The non-deterministic if-then-else control structure has been replaced by the non-deterministic br rules. In this representation, read and close have no actual effect we only want to track their use. The initial state of the program is represented by the term (g f ile end) where end is the final continuation of the program (the end). It is the only member of the initial set of states I. The model represented by R and I is M R = R * (I), → R , V with the two propositions read and close such that V (read) = { (read f k) | f, k ∈ T (Σ) } and V (close) = { (close f k) | f, k ∈ T (Σ) }.

  match l with [] -> true | _ -> false can be translated into m(nil) → true m(x) → false However this is not a deterministic TRS: there is an ambiguity since both rules can be applied on m(nil). A more accurate encoding of the OCaml semantics needs to disambiguate the patterns by generating the following rules: m(nil) → true m(cons(x, y)) → false

  forall cmp, l:T. sorted cmp (sort cmp l) : true ⇓ forall cmp. l:'a. sorted cmp (sort cmp l) : true

  cmp 0 A A → f alse cmp 0 A B → f alse cmp 0 B A → f alse cmp 0 B B → f alse cmp 1 A A → f alse cmp 1 A B → f alse cmp 1 B A → f alse cmp 1 B B → true cmp 2 A A → f alse cmp 2 A B → f alse cmp 2 B A → true cmp 2 B B → f alse . . . . . . . . . . . . cmp d A A → true cmp d A B → true cmp d B A → f alse cmp d B B → true cmp e A A → true cmp e A B → true cmp e B A → true cmp e B B → f alse cmp f A A → true cmp f A B → true cmp f B A → true cmp f B B → trueNote that this can reduce the number of generated rules by removing symmetries.

cmp e 5

 5 e 4 = f alse e 1 e 2 e 3 e 5 e 4 e 6 e 7 e 8 A A A B A B B B cmp # B A = f alse α

Titre:

  Verification automatique de programmes fonctionnels d'ordre supérieur à l'aide de languages réguliers d'arbres Mot clés : Vérification, Ordre-supérieur, Languages fonctionnels, Languages réguliers d'arbres Résumé : Nous étudions comment les langages réguliers d'arbres peuvent être utilisés pour vérifier automatiquement des propriétés sur des programmes fonctionnels d'ordre supérieur. Notre but est de développer de nouvelles techniques et outils pour les programmeurs permettant de développer des programmes plus sûrs tout en réduisant le temps et l'expertise nécessaire pour les vérifier. Cette thèse se concentre sur la vérification de propriétés régulières, famille pour laquelle nous montrons qu'une vérification complète et automatique est possible. Notre méthode de vérification est construite sur une procédure d'abstraction capable d'apprendre des langages réguliers sur-approchant les états atteignables d'un programme. En utilisant les langages réguliers en tant que types, nous montrons comment modulariser cette procédure pour vérifier des propriétés complexes en les formulant en tant que problèmes d'inférence des types. Nous étudions ses performances au travers de notre implémentation OCaml, Timbuk 4, sur plus de 80 problèmes de vérification. Nous montrons ensuite que notre procédure d'abstraction peut être utilisée pour vérifier des propriété relationnelles qui semblaient hors de portée des langages réguliers. Pour cela, nous utilisons et étendons un opérateur de convolution sur les arbres pour représenter une relation par langage régulier. Nous étendons ensuite notre procédure d'apprentissage de langages pour inférer automatiquement ces relations. Nous proposons une implémentation de cette idée en Rust en tant solveur de systèmes de clauses de Horn contraintes et étudions ses performances sur de multiples problèmes relationnels.

  CONTENTS • nous repoussons ces limites en définissant une nouvelle méthode d'abstraction basée sur l'algorithme de complétion d'automate ainsi qu'une procédure de vérification complète sur les propriété régulières basée sur celle-ci, • enfin nous étendons notre méthode d'abstraction pour vérifier des propriété relationnelles non-régulières, toujours à l'aide de langages réguliers Termes, réécriture et vérification régulière Nous posons ici les outils formels utilisés dans cette thèse pour représenter et analyser les programmes fonctionnels d'ordre supérieur et les propriété régulières que nous vérifions sur ceux-ci. L'exécution d'un programme est communément décrite par la succession des états visités pendant le calcul. Dans notre cas, nous utilisons la théorie des termes pour représenter ces états. Un terme est un arbre étiqueté de la forme f (t 1 , . . . , t

• nous étendons la méthode d'abstraction proposée par Genet et al. pour vérifier des propriétés sur les programmes d'ordre supérieur et étudions ses limites, 9 n ) où f est un symbole et chaque t i in sous-terme (sous-arbre). On note T (Σ) l'ensemble des termes étiquetés par les symboles inclus dans Σ. Les termes sont utiles pour représenter à la fois les états de l'exécution d'un programme fonctionnel, et les valeurs manipulées. Pour représenter et raisonner sur un programme fonctionnel, nous utilisons les systèmes de réécriture de termes (TRS). Cette représentation est à la fois proche des langages fonctionnels utilisés en pratique, et assez épurée pour simplifier le raisonnement formel. Un TRS, noté R, est composé de règles de réécritures de la forme g → d où g et d sont des motifs : des termes contenant des variables. Si X est un ensemble de variables, on note T (Σ, X ) l'ensemble des motifs étiquetés par les symboles de Σ et contenant des variables de X . Par exemple, considérons le programme OCaml suivant représentant la fonction de tri par insertion :

  alors l'Algorithme de Complétion d'Automate termine toujours en utilisant l'ensemble d'équations E = E c ∪E R ∪E r . En théorie, résoudre un problème régulier revient alors à trouver le bon ensemble d'équations E

c . En pratique cependant, cette méthode ne marche que dans le cas des programmes de premier ordre. Dans le cas des programmes d'ordre supérieur, les fonctions deviennes des valeurs comme les autres. La séparation entre symboles de fonction (F) et symboles constructeurs (C) n'a plus de sens. Pour garantir la terminaison de la procédure, il est donc nécessaire de trouver un ensemble E c contractant sur tout T (Σ) et non pas seulement T (C). Malheureusement chercher un ensemble d'équations contractantes sur un si grand ensemble est trop coûteux en pratique.

  alse can be reached" →, V is a model, φ a formula and I ⊆ S an set of initial states. The conjunction of φ and I describes the property to verify on M . Solving P consists in deciding if I ⊆ [[φ]] ∅ . If it is, the property is verified.

	Following the semantics of L µ , we are looking for the smallest set of states for which
	we have, for each state, either F alse holds, or φ holds in some of its successors.
	First, we note that [[F alse]] = V (F alse) = { false } (by definition of V ). Hence we
	know that false ∈ [[φ]]. Since false is a successor of (odd 0) we have (odd 0) ∈ [[ x]],
	hence (odd 0) ∈ [[φ]]. Similarly, we have that (even s(0)) ∈ [[φ]] and by induction for
	all k, (even 2k+1) ∈ [[φ]] and (odd 2k) ∈ [[φ]]. Because we used the least fixed point
	operator 2 , no other state is included from which we can conclude that this model
	verifies our property: (even 2k) never returns false.
	Verification Problems
	Using the concepts defined above, we can finally formally define verification problems.
	Definition 2.4.1 (Verification Problem). A verification problem P is a triple M, φ, I
	where M = S,

Table 4 .

 4 1 and 4.2 give the results of our experiments with Timbuk on an Intel® i7-7600U CPU, 4 2.80GHz cores. The first column gives the name of the problem.

	Name		Time (s)		Memory (MiB)
		Completion Equations	Total
	allA	0.0	0.0	0.001 ±0.0	4.93
	iteEvenOdd	0.0	0.0	0.001 ±0.0	5.58
	mult	0.0	0.0	0.001 ±0.0	5.83
	delete	0.0	0.01	0.01 ±0.0	6.24
	plusEven	0.01	0.0	0.01 ±0.0	5.71
	headReverse	0.02	0.0	0.03 ±0.0	8.92
	insertionSort	0.02	0.05	0.07 ±0.03	8.96
	reverse	0.01	0.07	0.08 ±0.01	7.24
	appendTheorem	0.03	0.06	0.09 ±0.0	10.18
	makelist	0.4	0.01	0.42 ±0.08	99.56
	reverseImplies	0.09	0.45	0.54 ±0.12	9.61
	insertionSort2	0.44	0.21	0.65 ±0.07	109.94
	incTree	0.07	0.92	0.99 ±0.08	46.12
	treeDepth	0.45	1.0	1.45 ±0.13	118.89
	orderedTreeTraversal	0.08	1.44	1.52 ±0.12	11.83
	orderedTree	0.1	5.14	5.24 ±0.48	13.8
	memberAppend	18.38	2.38	20.76 ±2.37	2971.76
	square	47.02	0.12	47.15 ±2.77	3997.26
	deleteImplies	-	-	Timeout	-
	equalLength	-	-	Timeout	-
	evenOdd	-	-	Timeout	-
	heightTree	-	-	Timeout	-
	insertTree	-	-	Timeout	-
	memberTree	-	-	Timeout	-
	mergeSort	-	-	Timeout	-
	replaceTree	-	-	Timeout	-
	split	-	-	Timeout	-
	zipUnzip	-	-	Timeout	-
	simple	0.0	0.0	0.001 ±0.0	3.42
	appendTheoremBug	0.01	0.0	0.01 ±0.0	6.36
	memberAppendError	0.01	0.0	0.01 ±0.0	6.69
	plusEvenError	0.01	0.0	0.01 ±0.0	6.06
	reverseBUGimplies	0.01	0.0	0.01 ±0.0	6.09
	deleteBUG	0.06	0.0	0.06 ±0.0	7.41
	insertionSort2BUG	0.09	0.0	0.09 ±0.01	9.18
	orderedTreeTraversalBug	0.18	0.0	0.18 ±0.05	8.96
	orderedTreePredicate2	0.72	0.01	0.73 ±0.04	12.78
	Table 4.1: First-order problems

We consider here a simplified version of Model Checking for non labeled transition systems.

Using the greatest fixed point operator ν, every term of R * (I) would have been included: if x is replaced by T (Σ) in (F alse ∨ x) then it is verified by every state/term.

This is not the actual notation used by the authors[START_REF] Kawaguchi | Type-based data structure verification[END_REF], but one may find it easier to process than the one used in the original paper.

For to really be an abstraction of R according to Definition

2.4.4, some other rules are needed in R # and ∆ # to abstract intermediate states. This will be detailed later on.

More accurately, the typing algorithm analyzes mutually recursive functions at the same time.

Note that we can always build such unified type. In the worst case we can unify two arbitrary types by introducing a new abstract value any # such that γ(any # ) = T (F)

In practice, t could be abstracted into a more precise abstract value w # such that γ(w # ) ⊂ γ(v # ). In this rare case, an epsilon transition w # → v # is added to the automaton to retain the information. The rest of the proof is unchanged.

Remerciements

If K n -coherence is not preserved during completion, then some states in the completed automaton may recognize terms outside of K n . Our goal is to show that it is preserved by C R (•) (Lemma 15) then by S E (•) (Lemma 16).

Lemma 11 (Normalization preserves K n -coherence). For all k ∈ N, k > 0, for all REFD A = Σ, Q, Q f , ∆ , A = Σ, Q∪Q , Q f , ∆∪∆ such that Q∪Q , ∆∪∆ , q = N orm(Q, ∆, t) with t ∈ T (Σ, Q).

Proof. We need to check that for each new state q ∈ Q , L(A , q) is K n -coherent and that for each old state q ∈ Q, L(A , q) is still K n -coherent.

The later one is easy to prove since by definition of N orm, no transitions of the form u → q are created in ∆ with q ∈ Q. For each old state q ∈ Q, L(A , q) is unchanged and remains K n -coherent. As for q ∈ Q , by definition of N orm each such new state recognizes a subset of {tσ | σ ∈ Q → T (Σ)}. By hypothesis, this set is K n -coherent. Since K n -coherence is closed by inclusion, L(A , q) is K n -coherent.

Lemma 12. For all context C and terms s : τ, t : τ such that s, t

Proof. This can be done by induction on the context C by seeing in the definition of K that we can swap a subterm of a K n term as long as the type of the subterm is the same, and that a K n term is not replaced by a Z n term.

Lemma 13 (Linking two states together preserves K n -coherence). Let consider the tree automata A = Σ, Q, Q f , ∆ and A = Σ, Q, Q f , ∆ such that ∆ = ∆ ∪ {q a → q b } and L(A, q b ) ⊆ K n ⇐⇒ L(A, q a ) ⊆ K n , with both languages sharing the same type τ . If A is K n -coherent, then A is K n -coherent.

Proof. For all state q ∈ Q, for all term s, t ∈ K n such that we have

is recognized in q with only one transition q a → q b . Let us prove that L(A, q) ∈ K n ⇐⇒ C[t] ∈ K n . If s ∈ K n (or t ∈ K n ), then since A is K n -coherent, L(A, q b ) ⊆ K n (or L(A, q a ) ⊆ K n ). Then since by hypothesis L(A, q b ) ⊆ K n ⇐⇒ L(A, q a ) ⊆ K n , s ∈ K n and t ∈ K n . Using Lemma 12 we have that C[s] ∈ K n ⇐⇒ C[t] ∈ K n . If L(A, q) ∈ K n then C[s] ∈ K n and then C[t] ∈ K n . We can use the exact same reasoning if s, t ∈ Z n \ K n . We can generalize this reasoning to any number of q a → q b transitions in the derivation paths and see that L(A , q) remains K n -coherent, for any state q. A is K n coherent.

Lemma 14 (Solving a critical-pair preserves

Proof. Let A be K n -coherent. Let A the tree automaton Σ, Q∪Q , Q f , ∆∪∆ result of the normalization. By Lemma 11 we know that A is K n -coherent. By definition of a critical pair we have lσ → * ∆ q and lσ → R rσ.

We can do the opposite demonstration do deduce that L(A, q ) ∈ K n ⇒ L(A, q) ∈ K n by first showing that Z n \ K n is closed by rewriting. So we have L(A, q) ⇐⇒ L(A, q ). Since typed TRSs preserves type by rewriting we know that L(A, q) and L(A, q ) share the same type. Using Lemma 13 we deduce that adding the transition q → q preserve K n -coherence, so A is K n -coherent.

Inference of Type Partitions a type partition signature for f in Λ, R # if for all π, all patterns p i , and

The second auxiliary function merge deduces the final solution

Lemma 22 (Merge). Let p = f (p 1 , . . . , p n ). Let (T 1 , . . . , T n ) → T be a type partitions signature for the symbol

Assume we have a solution Λ i , R # i , Π i to the type partition inference problem with R, Λ * , p i , T i . If Λ = Σ # , ∆ # , R # and Π (the output of merge) are the smallest sets such that 1)

Then Λ, R # and Π are a solution to the type partition inference problem of p with T in Λ * , R.

Proof. We prove that for all π ∈ Π, there exists a type τ ∈ T such that π, Λ, R # p : τ . Let π ∈ Π. By definition of the union of type partition environments, for each i, for each π i ∈ Π i , for each variable x ∈ V ar(p i ) we have either π(x) = π i (x) or there exists an epsilon transition π(x) → π i (x) ∈ ∆ # . Hence we have τ i ∈ T i such that π, Λ, R # p i : τ i . Now by definition, since (T 1 , . . . , T n ) → T is a signature of the symbol f in R # * with R # * ⊆ R # , we know that there exists τ ∈ T such that π, Λ, R # p : τ .

Assuming that analyze-function is correct, we can then prove that the whole partitions-inference algorithm is correct.

Theorem 7. Let R be a TRS, Λ * = Σ # * , ∆ # * be an initial abstract domain, p a pattern with T a partition of Σ # * . If Λ, R # , Π = partitions-inference(R, Λ * , p, T ) then it is a solution to the type partition inference problem of p with T , Λ * and R.

Example 5.3.6. Consider the following rules encoding the if -then-else: ite(true, x, y) → x ite(false, x, y) → y Consider the type nat # with two sub-types even # and odd # and the target type partition T defined as T = {even # , odd # }. The rule signature of the first rule for this target type partition would be ({true # , false # }, {even # , odd # }, {nat # }) → T . For this rule, there is no constraints on y, so the most general partition {nat # } is given. Conversely, the rule signature of the second rule for the same target type partition would be ({true # , false # }, {nat # }, {even # , odd # }) → T . We can then merge both rule-signatures into one input partitions signature for the symbol if by using the product of each sub-partitions. The resulting input partitions signature is

The following gives the formal definition of rule signatures and shows how to merge those rules signatures into a symbol signature for a given target type partition. Note that we do not explicitly give an implementation of the merge-rules function used in the algorithm and assume it strictly follows the merge instructions given by Lemma 24.

Definition 5.3.8 (Rule Type Partitions Signature). Let R be a TRS, Λ = Σ # , ∆ # an abstract domain, and R # an abstraction of R defined over them. Let T be a type partition over Σ # . Let l → r be a rule of R such that l = f (p 1 , . . . , p n ). We say that (T 1 , . . . , T n ) → T is the type partitions signature for this rule in Λ, R # if for all τ 1 , . . . , τ n where τ i ∈ T i there exists τ ∈ T such that for all π such that for all i, π,

Lemma 24 (From rule signatures to symbol signatures). Let R be a TRS, Λ = Σ # , ∆ # an abstract domain, and R # an abstraction of R defined over them. Let T be a type partition over Σ # and f a symbol associated with a non-empty set of rules

Let us write T k,i (τ ) the type τ ∈ T k,i such that τ τ . For all τ ∈ T i , by definition this type should exists and be unique. Let us also write

Since we consider functional TRSs, there exists a rule f (p k,1 , . . . , p k,n ) → r k ∈ R and a substitution σ : X → IRR(R) such that t i = p k,i σ and t → R rσ → * R u. By definition we have p k,i σ ∈ γ(τ ), but also by construction, p k,i σ ∈ γ(T k,i (τ i )). Hence by definition there exists π such that for all i, π, Λ, R # p k,i : T k,i (τ i ). Note that by construction, π ∈ Π k (τ 1 , . . . , τ n ). Since Definition 5.3.9. Bottom-Up Typing Let R be a functional TRS. Let Λ be an abstract domain and R # an abstraction of R defined over it. Let p be an irreducible pattern and Π a type partition environment. Let us name Π| p = {x → Π(x) | x ∈ V ar(p)} the projection of Π on the variables of p (we discard other variables). The function bottom-up-type-pattern returns Λ , T such that there exists a bijection ξ : Π| p → T such that for all π ∈ Π| p , π, Λ, R # p : τ (π).

From this definition, it is possible to show that the rule-signature algorithm is correct if we suppose the partitions-inference algorithm correct. We later see that it is easy to prove this assumption while considering non-recursive TRSs.

Lemma 25 (rule-signature is correct). Let R be a TRS, Λ an abstract domain and R # an abstraction of R on this domain. Let l → r be a rule of R, and T a type

r , Π r be the result of partitions-inference called with R, Λ, R # , r and T . We can show by induction on the cardinal of R that this recursive call to partitions-inference gives a valid solution to the type partition inference problem for the right-hand side. First, if R is empty, then partitions-inference is never called back from analyze-function. If R is not empty, partitions-inference is called after using one rule l → r of R. Then, since R is not recursive, we can consider that the result of partitions-inference will be equivalent if called with R -{l → r}. By hypothesis of induction on the cardinal of R, it is a valid solution. Now for each sub pattern p i of the left-hand side, let's call

Let us show that (T i , . . . , T n ) → T is a type partitions signature for the given rule in Λ , R # r . Let (τ 1 , . . . , τ n ) be a combination of (T 1 , . . . , T n ). Let us prove that there exists τ ∈ T such that for all π such that for all i, π, Λ , R # r p i : τ i then we have π, Λ , R # r p : τ . First we prove that there is only one π such that π, Λ , R # r p i : τ i . Let π 1 , π 2 be two different type environments following this property. Let observe that because ξ i is a bijection we should have π 1 | p i = π 2 | p i , for all i. This can only happen if π 1 = π 2 which contradicts our initial hypothesis that π 1 = π 2 . Now we prove that for this π, there exists τ ∈ T such that π, Λ , R # r p : τ . By definition of bottom-up-type-pattern we have that π ∈ Π r . Hence by construction there exists τ ∈ T such that π, Λ , R # r r : τ .

Note that this definition of analyze-function only works for non-recursive functions. Despite the amount of proofs needed to prove it correct, this algorithm combined with partitions-inference only involves some basic operations on tree automata to directly find a solution to the type partition inference problem. In the next section, we introduce a new definition of analyze-function intended for recursive functions.

Invariant Learning

The main difficulty in functional program analysis is recursion, or in our case, the analysis of functional symbols defined with mutually recursive rewriting rules. In this section, we define an implementation of analyze-function based on an original invariant learning procedure. For a given (recursive function) symbol and target type partition, this procedure finds correct input regular languages partitions that completes the symbol's type partitions signature. It follows the standard outline of a counter-example guided abstraction refinement (CEGAR) procedure, where Lemma 26 (The procedure outputs an abstraction of R). Let Σ = C ∪ F be a ranked alphabet, with R a rewriting system, functional w.r.t. C and F. Let A be a normalized, IRR-complete, and R-closed automaton that is -deterministic (deterministic once -transitions are removed). Let φ be a renaming respecting the constraints S(A, T ).

Proof. First we prove that for all terms t and type τ such that t → * A τ , for all k and all term u such that t → k R u then u → * A τ . We proceed by induction on k. First if k = 0. Then t = u and since we already know that t

Then by definition there exists a rule l → r ∈ R, a substitution σ and a position p such that t| p = lσ and t

A τ and A is normalized, there exists τ p such that t| p → * A τ p . Since A is R-closed and t| p → R rσ this also means that we have rσ → * A τ p . Note that we hence have

. By induction hypothesis on k this means that u → * A τ . Now we prove that R # is an abstraction of R. For soundness, let f (τ 1 , . . . , τ n ) → τ be a rule of R # . Let t = f (t 1 , . . . , t n ) be such that for all i, t i ∈ γ(τ i ). Let u ∈ IRR(R) a term such that t → * R u. Then thanks to what we just proved, we know that u → * A τ . Since u is irreducible, we deduce that u → * ∆ # τ . By definition this means that u ∈ γ(τ ). To prove completeness, let t = f (t 1 , . . . , t n ) be such that for i = 1 . . . n, t i ∈ IRR(R). By assumption, we know that A is IRR-complete, thus there must exist a rule f (τ 1 , . . . , τ n ) → τ such that t i ∈ γ(τ i ) for i = 1 . . . n.

Theorem 8 (Correctness of the invariant learning procedure). Let Λ = Σ # , ∆ # , R # be the output of the invariant learning procedure of R with Λ * = Σ # * , ∆ # * , the pattern p and type partition T . Let Π be the set of all substitutions π such that pπ → * ∆ # ∪R # v # . Then Λ, R, Π is a solution to the type partition inference problem.

Proof. First, since transitions are successively added to the automata A i , the facts Σ # ⊇ Σ # * and ∆ # ⊇ ∆ # * are ensured by construction of A i . Second, thanks to Lemma 26 we know that R # is an abstraction of R. We need to show that this abstraction is complete w.r.t. p and every v # of T (cf. Definition 5.2.3). Consider v # ∈ T , a term t ∈ γ(v # ) and a (concrete) substitution σ such that pσ → * R t. Let A be the last automaton considered during the inference procedure. Since φ(A) is IRR-complete there must exist an (abstract) substitution π such that σ ∈ γ(π). Since each term in A is recognized by a unique state, t will be abstracted in φ(A) by a unique abstract value. By definition of S(A, T ) this abstract value is v # . 4 Hence, if pπ → * φ(A) v # this means that φ(A) is not complete, which contradicts our hypothesis (otherwise the procedure would still continue). We have pπ → * φ(A) v # and by definition, pπ

By Theorem 8, if we let T i denote the set {τ i | f (τ 1 , . . . , τ i , . . . , τ n ) → τ ∈ R # } then this procedure gives (T 1 , . . . , T n ) → T as a correct signature of f in R # and Λ # . We can thus replace the analyze-function algorithm in partition-inference with this procedure when the input symbol is recursive, that is, its associated TRS fragment is recursive. It is worth noticing that this procedure provides two guarantees: 1. regular completeness, if there exists a regular abstraction R # providing a signature for f , N. |t| ≤ n }. We saw earlier that this is not a regular relation with regards to the standard convolution. However it is regular w.r.t. the full convolution. It can be represented using the following tree automaton:

For instance, the convolution of node(node(leaf, leaf ), leaf ) and s(s(0)) pictured on Figure 6.3 is recognized by this automaton. On the contrary, the convolution between node(leaf, leaf ) and 0, which is node 0 ( leaf • , leaf • ), is not recognized by this automaton.

In turn, SR(A, t) can be further decomposed as:

where Q f is the set of final states of A, and SR(A, q, t) the set of synchronized runs described by σ such that tσ ⊗ ∈ L(A, q). Our objective is now to define SR(A, q, t) in a constructive manner. We have seen in Section 6.3 multiple definitions of the convolution operator. The definition of SR(A, q, t) depends on the actual definition of the convolution we use.

Standard Convolution

We focus here on the definition of SR(A, q, t) for the standard convolution operator, written SR ⊕ (A, q, t). To simplify, we only consider binary relations. However all the definitions and proofs can easily be extended to n-ary relations.

Definition 6.4.7. Let A be a tree automatic binary relation. For each q and pair (s, t) ∈ T (Σ, X ) 2 , SR ⊕ (A, q, (s, t)) is defined as the smallest set inductively defined such that for each transition f g (q 1 , . . . , q k ) → q in A:

(1

(

(3) if s = f (s 1 , . . . , s n ) and t = g(t 1 , . . . , t m ) (with n > 0 ∨ m > 0) then if σ ∈ SR ⊕ (A, q i , (s i , t i )) for all i ∈ [1, max(n, m)] then σ ∈ SR ⊕ (A, q, (s, t)).

(4) if s = f and t = g then ∅ ∈ SR ⊕ (A, q, (s, t)) (where ∅ is the empty substitution).

The idea of this definition is to build synchronized convoluted runs by unrolling the input patterns following the transitions defined in A. Variables are either eliminated and unrolled until none is left, in which case a synchronized run has been found and can be added to SR ⊕ (A, q, (s, t)).

Example 6.4.1. Consider the following tree automatic relation A defining the equality between natural numbers:

We want to find a synchronized run in A for the patterns x and s(y)). To do that, we follow the definition of SR ⊕ (A, q, (x, s(y))). Using (1) we start by "unrolling" the variable x along the transition s s (q) → q of A: we build a substitution σ = { x → s(x ) } and search for a synchronized run in SR ⊕ (A, q, (s(x ), s(y))). Since both patterns start with the same symbol (s), we use (3) to consume this symbol and search for a synchronized run in SR ⊕ (A, q, (x , y)). Now we can use (1) and (2) to unroll both variables along the transition 0 0 → q of A: we build a substitution σ = { x → 0, y → 0 } and search for a synchronized run in SR ⊕ (A, q, (0, 0)). According to (4), ∅ ∈ SR ⊕ (A, q, (0, 0)). Now we go back and compose all the produced substitutions to find a synchronized run for our initial problem:

Full Convolution

We can naturally define SR ⊗ (A, q, t) be modifying Definition 6.4.7. The only part of the definition that changes is the rule (3) which actually reflects the behavior of the convolution operator. Definition 6.4.8. Let A be a tree automatic binary relation. For each q and pair (s, t), SR ⊗ (A, q, (s, t)) is defined as the smallest set inductively defined such that for each transition f g (q 1 , . . . , q k ) → q in A, it includes:

(1

(

(

where l(i) = k/m and r(i) = k mod n (following Definition 6.3.3), then σ ∈ SR ⊕ (A, q, (s, t)).

(4) if s = f and t = g then ∅ ∈ SR ⊕ (A, q, (s, t)) (where ∅ is the empty substitution).

Theorem 15. Let Σ be a ranked alphabet, with A a tree automatic binary relation for the full convolution ⊗. For all terms s, t ∈ T (Σ) and state q of A:

The proof of this theorem closely follows the one of Theorem 12.

The Learner

In this section we give an implementation of the learner, that uses learning constraints from the teacher to find a model that verifies the CHC system. In our case, the model maps each abstract predicate to a tree automatic relation, a tree automaton.

To learn those tree automata we use an SMT-based procedure similar to the one developed in Section 5.3.4. However this time, we need to build multiple automata at the same time, one for each abstract predicate. Let R be the considered set of abstract predicates. We first define the working context for the learner at iteration i, A i , as a function that maps each predicate p to an automaton recognizing every term for which we know something about p. Then, the constraints on terms emitted by the teacher are translated into SMT constraints about the states of the automata in the working context. As in Section 5.3.4, this gives us a candidate model for the CHC system that is submitted back to the teacher. Definition 6.4.9 (Learner's Working Context). Let R be a set of abstract predicates. A working context A is a function that maps each predicate p to a tree automaton (a tree automatic relation). We write A i the working context at iteration i of the procedure, with A 0 the empty working context that maps each predicate to the empty automaton.

The language recognized by A (p) contains every term for which the learner knows something. Note that this does not only include the members of the relation p but also some other constrained terms (for instance terms that must not be members of the relation). The automaton A (p) essentially recognizes terms from which the teacher has given the learner some information w.r.t. p. The working context is meant to be enriched at each iteration from the data produced by the teacher starting from A 0 , however to simplify here we can consider it empty at the beginning of each iteration. The model M i+1 outputted by the learner at iteration i + 1 is generated from:

1. The input of the learner, written I, which is the set of ICE constraints accumulated from the teacher, ∪ k∈[0,i] Teacher (M k );

2. A i the working context of the previous iteration;

3. A i+1 , Γ = Γ(A i , I) where Γ is a set of SMT-constraints extracted from I about the states of the automata defined A i+1 derived from A i ; and 4. Ψ(A i+1 ) an additional set of SMT-constraints required to keep the model automata deterministic.

The model generated by the learner is defined as a minimal (with respect to the number of generated constants) solution φ to the SMT-constraints Γ ∪ Ψ(A i+1 ):

SMT constraints are composed of equality constraints between the tree automata states, and of constraints of the form p(q) (or ¬p(q)) deciding if a state q belong to the predicate p. The later constraints are used to decide which tree automata states should be final in the generated model. For instance if (s⊗t) is recognized by the state q in the automaton A i+1 (p), the SMT-constraint "p(q)" means that p(s, t) is true, and q should be instantiated by a final state of M i+1 (p) so that (s ⊗ t) ∈ L(M i+1 (p)).

Definition 6.4.10 (ICE to SMT Constraints). Let R be a set of abstract relations.

Let A be a working context mapping each abstract relation to a tree automaton. Let s be a learning constraint emitted by the teacher. The following function Γ(A , s) computes A , γ where γ is the (unique) SMT constraint associated with the sample s in the new working context A derived from A . SMT-constraints are written between " . " in the following definition of Γ(A , s):

We write A [p → A] for the new working context derived from A where p is now associated with A. If I is a set of ICE constraints, then the set of SMT-constraints associated with I (along with the updated working context), Γ(A , I), is simply defined by successive applications of Γ(A , s) for each constraint s of I.

Definition 6.4.11 (Determinism Constraints). Ψ(A ) is the smallest set such that for all p ∈ R such that f (q 1 , . . . , q n ) → q ∈ A (p) and f (q 1 , . . . , q n ) → q ∈ A (p) then q 1 = q 1 ∨ . . . ∨ q n = q n ∨ q = q ∈ Ψ(A )

Our property can then be expressed as the following modal µ-calculus formula which must hold for any final state: To verify it we use the generalization of the regular language learning procedure described above. The principle is the same: we iterate through growing fragments of R * (I) recognized by the automata A 0 , A 1 , . . . to find candidate abstractions, starting from the initial state (g f ile end) recognized by A 0 in q g . Since R is not terminating here, we use the method described in the previous section and only perform one step of Tree Automata Completion at each iteration. After 4 iterations, A 4 will be as follows: iteration 1 : g q f ile q end → q g f ile → q f ile end → q end iteration 2 : br(q close , q read ) → q br q br → q g close q f ile q end → q close read q f ile q h → q read h q f ile q end → q h iteration 3 : q close → q br q read → q br q end q unit → q 2 q 2 → q close q h q unit → q h q h → q read () → q unit iteration 4 : q unit → q 2 q g → q h

Since this automaton is quite big, it is easier to look at the model M A 4 = Q, ∆ , V encoded by A 4 : q g start q br q read q h q close q end q unit

In this model we have in particular:

The valid abstractions of this model that preserves φ for the final state (starting state in the model) are constrained by S(A, φ), which gives that the final abstract automaton must have at least two states to separate read from close.

The smallest solution is pictured as follows:

q # read start q # close where q g , q br , q read , q h are abstracted into q # read and where q close , q end , q unit are abstracted into q # close . The final abstract automaton is R-closed and IRR-complete, so the procedure stops with an correct abstraction of R * (I) that verifies our property. Note that this abstraction is not a collapsing abstraction and preserve parts of the control flow of the program execution.

INTEGRATION IN HIGHER-ORDER FUNCTIONAL LANGUAGES