• Aucun résultat trouvé

3.4 Conclusion

3.4.3 Discussion

Ce cadre théorique est très puissant dans le sens où il fournit un langage d’une grande expressivité englobant un sous-ensemble de la logique du premier ordre et permettant de décrire nombre de systèmes paramétrés. Un second point crucial de ce cadre est qu’il existe un algorithme effectif pour vérifier des propriétés de ces systèmes. En tirant parti des capacités de raisonnement des solveurs SMT modernes, il peut se décharger d’une partie de son travail au travers d’outils annexes en perpétuelle évolution. Les théories supportées par ces solveurs sont directement accessibles au model checker et à l’utilisateur (sous certaines conditions).

Bien que ce cadre général soit assez libéral, les restrictions imposées sur les théories pour garantir la terminaison sont tout de même limitantes en pratique. Par exemple la condition de non intersection des signatures des théories des éléments et des processus nous empêche formellement d’écrire un tableau indicé par des identificateurs de processus et contentant lui-même des identificateurs de processus. Ce cas arrive en pratique si on veut modéliser une variable locale à un processus ou un canal de communication dans lequel on enregistre le destinataire d’un éventuel message. L’exemple suivant sort du cadre des systèmes à tableaux :

type msg = M1 | M2 | M3

array Channel_msg[proc] : msg

array Channel_dest[proc] : proc ...

transition recieve (from to)

requires { Channel_dest[from] = to && Channel_msg[from] = M1 } {

(* Effectuer action correspondant à la réception de M1 et accuser réception au processus from*)

}

Une autre limitation assez handicapante en pratique est la condition qui force TEà être localement finie. Si on utilise la théorie des entiers naturels munis de l’addition de signature ΣE = {=, +, 0, 1, 2, 3, . . .} on sort également du cadre des systèmes à tableaux et on ne pourra pas par exemple modéliser l’incrémentation d’un compteur.

var Counter : int

array Flag[proc] : bool ...

transition incr (i)

requires { Flag[i] = False } {

Flag[i] := True;

Counter := Counter + 1; }

Même si dans certains cas la terminaison de l’atteignabilité arrière est garantie à coup sûr, rien n’assure que le model checker n’aura pas besoin de « l’âge de l’univers » pour décider de la sûreté d’un système. Au contraire, il existe des exemples pour lesquels aucune garantie n’est faite quand à la décidabilité d’une propriété mais pour lesquels Cubicle atteint un point fixe global et termine quand même. Si on se trouve dans un cas défavorable, parce que l’algorithme ne termine pas ou bien parce que la complexité de l’analyse est trop grande, l’utilisateur ne pourra pas faire la différence. Pour toutes ces raisons, dans Cubicle on n’interdit pas l’utilisation de combinaisons particulières de théories du moment que la syntaxe les autorise. On résume dans le tableau en figure 3.12 les restrictions qui sont forcées (X) par la théorie du model checking modulo théories et par Cubicle. Par exemple la théorie TP est locallement finie dans Cubicle donc on ne fournit pas d’opération d’addition + sur les identificateurs de processus.

Restriction Théorie Cubicle

TP locallement finie X X

TP close par sous-structures X

SMT (TP), SMT (TE) décidables X X

ΣP ∩ΣE = ∅ X

I ≡ ∀¯i. φ (¯i) X X

Θ ≡disjonction de cubes X X Gardes ∀ interdites X

Figure 3.12 – Différences de restrictions (X) entre la théorie du model checking modulo théories et Cubicle

Les algorithmes 4 et 5 sont en réalité assez naïfs et bien trop inefficaces pour être utilisés sur des problèmes non triviaux. Le chapitre suivant détaille une implémentation effective en pratique de ce cadre théorique dans Cubicle.

4

Optimisations et implémentation

Sommaire

4.1 Architecture . . . 80 4.2 Optimisations . . . 82 4.2.1 Appels au solveur SMT . . . 82 4.2.2 Tests ensemblistes . . . 85 4.2.3 Instantiation efficace . . . 88 4.3 Suppressions a posteriori . . . 91 4.4 Sous-typage . . . 96 4.5 Exploration parallèle . . . 99 4.6 Résultats et conclusion . . . 102

Il est tout à fait possible de prendre tels quels les algorithmes présentés dans le chapitre 3 pour obtenir un model checker pour systèmes paramétrés correct. Cependant, un tel outil n’a que peu de chances de fonctionner en pratique. Dans cette partie, on décrit en détail l’architecture du model checker Cubicle. On justifie les choix qui ont été faits pour cette implémentation et on explicite également les optimisations les plus cruciales.

Plusieurs composants de l’algorithme peuvent faire l’objet d’une attention particulière. On redonne ci-dessous l’algorithme 4 dans son intégralité en précisant quelles parties section de ce chapitre traite plus spécifiquement.

Algorithme 4 : Analyse d’atteignabilité par chaînage arrière Entrées : un système paramétré S = (Q, I , τ ) et un cube Θ Variables : V : cubes visités Q: �le de travail 1 function BWD(S, Θ) : begin 2 V := ;; 3 push(Q, Θ); 4 while not_empty(Q) do 5 φ := pop(Q); 6 if φ ^ I satis�able then 7 returnunsafe 8 else if φ 6|= V then 9 V := V [ {φ}; 10 push(Q, P��τ(φ)); 11 returnsafe Section 4.4 Section 4.5 Section 4.2.1 Sections 4.2.2 et 4.2.3 Section 4.3

Les optimisations traitant des tests de satisfiabilité sont abordées dans la section 4.2. On développe en section 4.4 une analyse statique simple de sous-typage qui peut apporter des informations supplémentaires en début d’algorithme. L’ajout des nœuds dans l’ensemble V est potentiellement source de simplifications expliquées en section 4.3. On donne en outre une technique de parallélisation de la boucle d’atteignabilité (Section 4.5). Enfin, les expérimentations exposées en fin de chapitre confirment que les optimisations présentées ici permettent à Cubicle d’être compétitif avec les model checkers pour systèmes paramétrés de l’état de l’art. Une partie des travaux présentés dans ce chapitre ont étés publiés dans [43] et [45].

4.1 Architecture

La figure 4.1 présente l’architecture du model checker Cubicle sous forme de graphe de dépendance entre modules. Les modules en pointillés représentent des interfaces ou modules abstraits fournissant une signature. Les flèches pointillées dénotent les modules implémentant les signatures requises par ces interfaces.

Le moduleBWDcontient l’algorithme d’atteignabilité (algorithme 4) de la section 3.1.1 du chapitre précédent. Il est paramétré par une structure de file à prioritéPriorityQueue dont la signature est donnée ci-contre.

4.1 Architecture

BWD

Pre Safety Fixpoint

PriorityQueue

Prover Instantiation

SMT

Queue Stack Heap

Figure 4.1 – Architecture de Cubicle

priorityqueue.mli

module type PriorityNodeQueue = sig type t

val create : unit -> t

val pop : t -> Node.t

val push : Node.t -> t -> unit

val push_list : Node.t list -> t -> unit

val clear : t -> unit

val length : t -> int

val is_empty : t -> bool

end

Les structures de données qui implémentent cette signature doivent manipuler des nœuds (cubes riches) de type Node.t et définissent la stratégie d’exploration du graphe d’attei-gnabilité arrière. Par exemple une structure de file générera une exploration en largeur alors qu’une structure de pile générera une exploration en profondeur. Bien évidemment la taille du graphe d’atteignabilité dépend de la stratégie d’exploration utilisée. L’expérience montre qu’en pratique, une exploration en largeur (ou une variante) est souvent plus efficace qu’une exploration en profondeur pour les systèmes sûrs.

para-métrées (dans le modulePre), des tests de sûreté (moduleSafety) et des tests de points fixes (moduleFixpoint). L’instantiation des quantificateurs, dont une variante naïve est présentée dans l’algorithme 5 (voir page 56), est réalisée de manière efficace par le module Instantiation. Enfin les tests logiques sont déchargés par un solveur SMT dont l’interface avec le model checker est faite au travers du moduleProver.