Author: Christian.Queinnec@lip6.fr

## Outils de mise au point

Il y a deux outils de mise au point. Le premier outil est un mécanisme de trace utilisable, sur les fonctions globales, dans la boucle d'interaction. Le second outil est un debug sur les programmes hors boucle d'interaction. Suite à une première exécution, il permet de revenir sur des points d'arrêt, d'explorer des valeurs ou de relancer certaines fonctions avec de nouveaux paramètres. Ce second outil n'est utilisable que sous Unix car il duplique le processus programme via un `fork` (voir page ??).

### Trace

On appelle trace d'une fonction l'affichage de la valeur de ses paramètres d'appels dans le cours d'un programme ainsi que celui de son résultat.
Les commandes de traçage d'exécution sont des directives de la boucle d'interaction. Elles permettent de tracer une fonction, d'arrêter sa trace ou d'arrêter toutes les traces posées. Ces trois directives sont données dans le tableau ci-dessous.

#trace `nom` | trace la fonction `nom`
#untrace `nom` | arrête de tracer la fonction `nom`
#untrace_all | annule toutes les traces

Voici un premier exemple où on définit une fonction `f` :

    # let f x = x + 1;;
    val f : int -> int = <fun>
    # f 4;;
    - : int = 5

On indique maintenant que cette fonction est tracée, c'est-à-dire que le passage d'un argument ou le retour d'un résultat seront affichés.

    #  #trace f;;
    f is now traced.
    # f 4;;
    f <-- 4
    f --> 5
    - : int = 5

Le passage de l'argument 4 à `f` est affiché, la fonction `f` effectue le calcul demandé et le résultat retourné est aussi affiché. Les (valeurs) paramètres d'appels sont indiqués par une flèche vers la gauche et la valeur de retour, par une flèche vers la droite.

#### Fonctions à plusieurs arguments

Les fonctions à plusieurs arguments (ou retournant une fermeture) sont aussi traçables. Chaque passage d'un argument est affiché. Pour distinguer les différentes fermetures, on note avec une `*` le nombre d'arguments déjà passés à la fermeture. Soit la fonction `verif_div` qui prend 4 nombres (a, b, q, r) correspondant à la division entière : a = bq + r.

    # let verif_div a b q r =
       a = b*q + r;;
    val verif_div : int -> int -> int -> int -> bool = <fun>
    # verif_div 11 5 2 1;;
    - : bool = true

Sa trace montrera le passage des 4 arguments :

    #  #trace verif_div;;
    verif_div is now traced.
    # verif_div 11 5 2 1;;
    verif_div <-- 11
    verif_div --> <fun>
    verif_div* <-- 5
    verif_div* --> <fun>
    verif_div** <-- 2
    verif_div** --> <fun>
    verif_div*** <-- 1
    verif_div*** --> true
    - : bool = true

#### Fonctions récursives

La trace apporte de précieuses informations sur les fonctions récursives. Elle permet de détecter aisément un mauvais critère d'arrêt.
On définit la fonction `appartient` qui teste si un entier appartient à une liste d'entiers de la manière suivante :

    # let rec appartient (e : int) l = match l with
       [] -> false
     | t::q -> (e = t) || appartient e q ;;
    val appartient : int -> int list -> bool = <fun>
    # appartient 4 [3;5;7] ;;
    - : bool = false
    # appartient 4 [1; 2; 3; 4; 5; 6; 7; 8] ;;
    - : bool = true

La trace de l'appel de appartient 4 [3;5;7] affichera les quatre appels de cette fonction et les résultats retournés.

    #  #trace appartient ;;
    appartient is now traced.
    # appartient 4 [3;5;7] ;;
    appartient <-- 4
    appartient --> <fun>
    appartient* <-- [3; 5; 7]
    appartient <-- 4
    appartient --> <fun>
    appartient* <-- [5; 7]
    appartient <-- 4
    appartient --> <fun>
    appartient* <-- [7]
    appartient <-- 4
    appartient --> <fun>
    appartient* <-- []
    appartient* --> false
    appartient* --> false
    appartient* --> false
    appartient* --> false
    - : bool = false

À chaque appel de la fonction `appartient` l'argument 4 puis la liste dans laquelle vérifier sont passés. Quand la liste devient vide, alors la fonction retourne la valeur `false` qui est propagée comme valeur de retour de chaque appel récursif en attente.
L'exemple suivant montre le parcours partiel de la liste lorsque l'élément demandé y apparaît :

    # appartient 4 [1; 2; 3; 4; 5; 6; 7; 8] ;;
    appartient <-- 4
    appartient --> <fun>
    appartient* <-- [1; 2; 3; 4; 5; 6; 7; 8]
    appartient <-- 4
    appartient --> <fun>
    appartient* <-- [2; 3; 4; 5; 6; 7; 8]
    appartient <-- 4
    appartient --> <fun>
    appartient* <-- [3; 4; 5; 6; 7; 8]
    appartient <-- 4
    appartient --> <fun>
    appartient* <-- [4; 5; 6; 7; 8]
    appartient* --> true
    appartient* --> true
    appartient* --> true
    appartient* --> true
    - : bool = true

Dès que `4` est tête de liste, la fonction retourne la valeur `true` qui est propagée par les appels récursifs en attente.
Si la fonction `appartient` avait inversé l'ordre du `||`, elle retournerait toujours le bon résultat, mais effectuerait dans tous les cas le parcours complet de la liste.

    # let rec appartient (e : int) = function
         [] -> false
       | t::q ->   appartient e q || (e = t) ;;
    val appartient : int -> int list -> bool = <fun>
    # #trace appartient ;;
    appartient is now traced.
    # appartient 3 [3;5;7] ;;
    appartient <-- 3
    appartient --> <fun>
    appartient* <-- [3; 5; 7]
    appartient <-- 3
    appartient --> <fun>
    appartient* <-- [5; 7]
    appartient <-- 3
    appartient --> <fun>
    appartient* <-- [7]
    appartient <-- 3
    appartient --> <fun>
    appartient* <-- []
    appartient* --> false
    appartient* --> false
    appartient* --> false
    appartient* --> true
    - : bool = true

Bien que `3` soit le premier élément de la liste, celle-ci est entièrement parcourue. Ainsi, le mécanisme de trace permet également une analyse d'efficacité des fonctions récursives.

#### Fonctions polymorphes

La trace d'arguments d'un type paramétré n'affichera pas la valeur correspondant au paramètre de type. Par exemple si la fonction `appartient` avait été écrite sans contrainte explicite de type :

    # let rec appartient e l = match l with
       [] -> false
     | t::q -> (e = t) || appartient e q ;;
    val appartient : 'a -> 'a list -> bool = <fun>

La fonction `appartient` est maintenant d'un type polymorphe. Sa trace n'affiche plus la valeur des arguments, mais les remplace par l'indication (`poly`).

    #  #trace appartient ;;
    appartient is now traced.
    # appartient 3 [2;3;4] ;;
    appartient <-- <poly>
    appartient --> <fun>
    appartient* <-- [<poly>; <poly>; <poly>]
    appartient <-- <poly>
    appartient --> <fun>
    appartient* <-- [<poly>; <poly>]
    appartient* --> true
    appartient* --> true
    - : bool = true

La boucle d'interaction d'Objective CAML ne sait afficher que les valeurs de type monomorphe. De plus, il ne conserve que le type inféré des déclarations globales. Ainsi, après compilation de l'expression appartient 3 [2;3;4], la boucle d'interaction ne dispose plus, en fait d'information de type, que du type 'a -> 'a list -> bool de la fonction `appartient`. Les types (monomorphes) de 3 et [2;3;4] ont été perdus car les valeurs ne conservent pas d'information de type : c'est le typage statique. C'est pourquoi, le mécanisme de trace attribue aux arguments de la fonction `appartient` les types polymorphes 'a et 'a list dont il n'affiche pas les valeurs.
C'est l'absence d'information de type dans les valeurs qui entraîne l'impossibilité de construire une fonction `print` générique de type 'a -> unit.

#### Fonctions locales

Les fonctions locales ne sont pas traçables pour les mêmes raisons liées au typage statique. Seuls les types des déclarations globales sont conservés dans l'environnement de la boucle d'interaction. Pourtant le style de programmation suivant est courant :

    # let appartient e l =
       let rec app_aux l = match l with
         [] -> false
       | t::q -> (e = t) || (app_aux q)
       in
         app_aux l;;
    val appartient : 'a -> 'a list -> bool = <fun>

La fonction globale ne fait qu'appeler une fonction locale effectuant la partie intéressante du travail.

#### Remarques sur la trace

Le mécanisme de trace est actuellement l'unique outil de mise au point multi-plate-formes. Ses deux faiblesses sont l'absence de trace des déclarations locales et le non affichage des paramètres polymorphes des fonctions. Cela restreint fortement son usage, principalement aux premiers pas avec le langage.

### Debug

L'outil de mise au point des programmes, `ocamldebug`, est un débogueur au sens usuel du terme. Il permet l'exécution pas-à-pas, l'insertion de points d'arrêt, l'exploration et la modification des valeurs de l'environnement.
Exécuter pas-à-pas un programme suppose que l'on sache déterminer ce qu'est un pas de programme. En programmation impérative, cette notion est assez simple : grosso modo, un pas de programme est une instruction du langage. Mais cette définition n'a plus grand sens en programmation fonctionnelle; on parle plutôt d'événement (event) de programmes. Ceux-ci seront une application, l'entrée dans une fonction, un filtrage, une alternative, une boucle, un élément de séquence, etc.

### Warning

Cet outil ne fonctionne que sous Unix.

#### Compilation en mode debug

L'option de compilation `-g` engendre un fichier `.cmo` permettant d'engendrer les instructions nécessaires au debug. Cette option n'est connue que du compilateur de code-octet. Il sera nécessaire d'indiquer cette option lors de la compilation des différents fichiers d'une application. Une fois un exécutable produit, son exécution en mode debug s'effectue par la commande `ocamldebug` de la manière suivante :
On prend l'exemple suivant du fichier `fact.ml` de calcul de la fonction factorielle :

    let fact n =
      let rec fact_aux p q n =
        if n = 0 then p
        else fact_aux (p+q) p (n-1)
      in
    fact_aux 1 1 n;;

Le programme principal du fichier `main.ml` part pour une longue récursion suite à l'appel de `Fact.fact` à -1.

    let x = ref 4;;
    let go () =
      x :=  -1;
      Fact.fact !x;;
    go();;

On compile les deux fichiers avec l'option `-g` :

    $ ocamlc -g -i -o fact.exe fact.ml main.ml
    val fact : int -> int
    val x : int ref
    val go : unit -> int

#### Lancement du debug

Une fois un exécutable crée en mode debug, il est possible de démarrer l'exécution dans ce mode.

    $ ocamldebug fact.exe
            Objective Caml Debugger version 2.04

    (ocd)

### Suivi du contrôle d'exécution

Le contrôle d'exécution passe par les événements du programme. On peut soit avancer ou reculer de n éléments de programme, soit avancer ou reculer jusqu'au point d'arrêt le plus proche (ou de n points d'arrêt). On indique un point d'arrêt sur une fonction ou sur un élément de programme. Le choix de l'élément de langage est donné par les coordonnées en ligne, colonne ou numéro de caractères. Ces localisations peuvent être relatives à un module.
Dans l'exemple ci-dessous, on pose un point d'arrêt sur la quatrième ligne du module `Main` :

    (ocd) step 0
    Loading program... done.
    Time : 0
    Beginning of program.
    (ocd)  break @ Main 4
    Breakpoint 1 at 4964 : file Main, line 4 column 3

Les initialisations du module se font avant le programme proprement dit. C'est pour cette raison que le point d'arrêt à la ligne 4 ne se rencontre qu'après 4964 instructions.
On avance ou recule dans l'exécution soit par rapport aux éléments de programme, soit par rapport aux points d'arrêt. `run` et `reverse` exécutent jusqu'au point d'arrêt le plus proche, le premier dans le sens de l'exécution, le second en marche arrière. Le commande `step` avance de 1 ou n éléments de programme en entrant dans les fonctions, `next` n'entre pas dans les appels de fonctions. Respectivement `backstep` et `previous` effectuent les mêmes opérations en marche arrière. Enfin `finish` termine l'appel de la fonction courante alors que `start` retourne à l'élément de programme précédant l'invocation de la fonction.
Pour suivre notre exemple, on avance jusqu'au point d'arrêt puis on exécute trois événements de programme :

    (ocd) run
    Time : 6 - pc : 4964 - module Main
    Breakpoint : 1
    4   <|b|>Fact.fact !x;;
    (ocd) step
    Time : 7 - pc : 4860 - module Fact
    2   <|b|>let rec fact_aux p q n =
    (ocd) step
    Time : 8 - pc : 4876 - module Fact
    6 <|b|>fact_aux 1 1 n;;
    (ocd) step
    Time : 9 - pc : 4788 - module Fact
    3     <|b|>if n = 0 then p

#### Exploration de valeurs

Au niveau d'un arrêt, il est possible d'afficher les valeurs associées aux variables du bloc d'activation. Les commandes `print` et `display` affichent selon des profondeurs différentes les valeurs associées à une valeur.
Nous allons afficher la valeur de `n`, puis remonter de trois pas pour afficher le contenu de `x` :

    (ocd) print n
    n : int = -1
    (ocd) backstep 3
    Time : 6 - pc : 4964 - module Main
    Breakpoint : 1
    4   <|b|>Fact.fact !x;;
    (ocd) print x
    x : int ref = {contents=-1}

Les accès aux champs d'un enregistrement ou via l'indice d'un tableau sont acceptés par les commandes d'affichage.

    (ocd) print x.contents
    $1 : int = -1

#### Pile d'exécution

La pile d'exécution permet de visualiser l'imbrication des appels de fonctions. La commande `backtrace` ou `bt` montre l'empilement des appels. Les commandes `up` et `down` sélectionnent le bloc d'activation suivant ou précédant. Enfin la commande `frame` décrit le bloc courant.