Interprétation abstraite dans l'analyse statique du code

Explication de l'interprétation abstraite : de la théorie des treillis à l'inférence et à l'astrée

Considérons deux programmes qui réussissent tous deux tous les tests que vous écrivez. L'un est correct. L'autre présente une erreur de division par zéro qui ne se produit que lorsqu'une combinaison spécifique d'entrées arrive simultanément, une combinaison que vos tests ne produisent jamais. Les tests traditionnels ne permettent pas de déterminer lequel est lequel. L'interprétation abstraite, si.

L'interprétation abstraite est le cadre mathématique qui permet aux outils d'analyse statique de raisonner sur tous les comportements possibles d'un programme sans l'exécuter. C'est la technique sous-jacente à Infer de Facebook, qui détecte les bogues de pointeur nul à grande échelle ; à l'analyseur Astrée, qui vérifie formellement les logiciels de contrôle de vol d'Airbus ; et à tout analyseur statique revendiquant la stabilité, c'est-à-dire la garantie que si un programme réussit l'analyse, il est réellement exempt des erreurs vérifiées. Comprendre son fonctionnement explique pourquoi certains outils détectent des bogues que d'autres ne détectent pas, et pourquoi ces garanties impliquent des compromis.

Analyser le code sans l'exécuter

SMART TS XL applique l'analyse statique structurelle à tous les langages de votre portefeuille simultanément.

En savoir plus

Qu'est-ce que l'interprétation abstraite ?

L'interprétation abstraite est une théorie de l'approximation de programmes, développée par Patrick Cousot et Radhia Cousot en 1977. L'idée centrale : au lieu de calculer l'ensemble exact de tous les états possibles du programme, ce qui est généralement indécidable, calculer une sur-approximation sûre en utilisant un domaine mathématique simplifié qui est traitable à analyser.

Le mot « abstrait » ici ne signifie pas vague ou conceptuel. Il se réfère à une opération mathématique spécifique : abstraire un ensemble de valeurs concrètes en une représentation plus simple qui conserve les propriétés qui vous intéressent tout en éliminant les détails inutiles. Une valeur entière concrète comme 42 Dans une abstraction d'analyse de signe, cela devient simplement « positif ». L'abstraction perd de l'information (on ne connaît plus la valeur exacte) mais gagne en maniabilité (le signe de tout entier est l'une des trois possibilités suivantes : positif, négatif ou nul).

Ce qui rend cette méthode utile pour l'analyse de programmes, c'est la garantie qu'elle offre : si l'analyse ne détecte aucune erreur dans le domaine abstrait, aucune erreur n'existe lors de l'exécution concrète. Si elle détecte une erreur potentielle, celle-ci peut se produire ou non en pratique, mais aucune erreur réelle ne peut être dissimulée. C'est le principe de la fiabilité.

Interprétation abstraite vs. Analyse AST vs. Analyse dynamique

Ces termes sont souvent confondus ; « analyse de code AST » apparaît dans les données de recherche pour cet article, et ils décrivent des choses différentes.

Un arbre de syntaxe abstraite (AST) est une structure de données représentant la structure grammaticale du code source. Chaque compilateur et analyseur de code statique en construit un. Il constitue la base de l'analyse syntaxique, des outils de refactorisation et de l'analyse statique basée sur les modèles. L'analyse basée sur l'AST détecte les modèles : le code correspondant à une règle (une fonction avec trop de paramètres, une chaîne SQL construite par concaténation) est signalé. Elle ne tient pas compte des valeurs ni du comportement à l'exécution.

L'interprétation abstraite raisonne sur le comportement à l'exécution sans exécuter le programme. Elle utilise l'AST comme entrée, mais va bien au-delà : elle modélise le flux des valeurs dans le programme, les plages de valeurs possibles des variables, la possibilité qu'un pointeur soit nul à un endroit précis de l'appel, et la terminaison des boucles. L'analyse de l'AST est une recherche de motifs. L'interprétation abstraite est un raisonnement comportemental.

La plupart des linters (ESLint, Checkstyle, Pylint) sont principalement basés sur l'AST. La plupart des outils de vérification formelle (Infer, Astrée, Polyspace) utilisent l'interprétation abstraite. L'analyse dynamique (exécution du programme et observation du comportement réel) ne détecte que les bogues déclenchés par des entrées spécifiques. L'interprétation abstraite détecte les bogues pour toutes les entrées possibles sans même exécuter le programme.

Les principes mathématiques sous-jacents à l'analyse statique

La requête « Quels sont les principes mathématiques qui sous-tendent les outils d’analyse statique ? » apparaît directement dans les résultats de recherche. Voici la réponse en toute simplicité.

L'interprétation abstraite repose sur trois structures mathématiques :

Treillis. Un treillis est un ensemble partiellement ordonné où chaque paire d'éléments possède une borne supérieure (jointure) et une borne inférieure (intersection). En analyse statique, le treillis représente le domaine abstrait, l'ensemble des valeurs abstraites possibles, ordonnées selon la quantité d'information qu'elles véhiculent. Pour l'analyse des signes, le treillis se présente ainsi :

        ⊤ (unknown -- could be anything)
       / \
   pos   neg
       \ /
        0
        |
        ⊥ (unreachable -- no possible value)

Remonter dans le réseau signifie perdre en précision (en savoir moins). Descendre signifie en gagner (en savoir plus). L'élément supérieur ⊤ signifie « nous ne savons rien d'utile ». L'élément inférieur ⊥ signifie « cet état est inaccessible ».

Connexions de Galois. Une connexion de Galois est la relation formelle entre le domaine concret (les valeurs réelles du programme) et le domaine abstrait (la représentation simplifiée). Elle se compose de deux fonctions : une fonction d’abstraction α qui associe aux valeurs concrètes leur représentation abstraite, et une fonction de concrétisation γ qui associe aux valeurs abstraites l’ensemble des valeurs concrètes qu’elles représentent.

La propriété essentielle : le domaine abstrait doit constituer une sur-approximation sûre. γ(α(S)) ⊇ S Pour tout ensemble concret S, l'abstraction peut inclure plus de valeurs que celles qui existent réellement (ce qui produit des faux positifs), mais elle ne doit jamais exclure des valeurs existantes. Exclure des valeurs réelles reviendrait à passer à côté de véritables bogues.

Itération à point fixe. Pour les programmes comportant des boucles, l'analyse doit itérer jusqu'à atteindre un état stable. Prenons l'exemple d'une boucle comme :

c

int x = 0;
while (condition) {
    x = x + 1;
}

Lors de la première itération, x is {0}Après une boucle, x pourrait être {0, 1}Après deux, {0, 1, 2}Cet ensemble ne cesse de croître, il ne se stabilise jamais de lui-même. La solution est : élargissement: un opérateur qui force la convergence en passant à une approximation plus large (généralement [0, +∞) (pour l'analyse d'intervalles). L'analyse utilise ensuite rétrécissement pour retrouver une certaine précision.

Ce calcul à virgule fixe est ce qui rend l'interprétation abstraite complète sur tous les chemins d'exécution, y compris les boucles, et ce qui la rend plus coûteuse en termes de calcul qu'une simple correspondance de motifs.

Domaines abstraits : choisir ce qu’il faut approximer

Le domaine abstrait détermine ce que l'analyse peut et ne peut pas trouver. Différents domaines répondent à différentes questions sur le comportement du programme.

Domaine abstraitCe qu'il suitExemple d'utilisationCe qui manque
Analyse des signesQue les valeurs soient positives, négatives ou nullesDétection de division par zéroValeurs exactes, conditions de dépassement
Analyse d'intervallelimites supérieures et inférieures des valeurs numériquesDépassement de tampon, sécurité d'accès aux tableauxLiens entre les variables
Domaine octogonalRelations linéaires entre paires de variablesDétection de débordement plus préciseRelations non linéaires
Analyse des pointeursQue les pointeurs puissent être nuls ou être des alias les uns des autresDéréférencement nul, utilisation après libérationDurée de vie des objets, forme du tas
Analyse de la contaminationQue les valeurs proviennent de sources non fiablesDétection d'injection SQL et de XSSLes flux implicites passent par le contrôle
Domaine polyédriqueContraintes arithmétiques linéaires arbitrairesvérification des limites de boucleLes coûts liés à la performance augmentent de façon exponentielle.

Le compromis entre les domaines se situe toujours entre précision et performance. Le domaine des intervalles est rapide et détecte la plupart des erreurs numériques. Le domaine polyédrique est beaucoup plus précis, mais sa complexité augmente exponentiellement avec le nombre de variables. Les outils d'analyse statique pratiques choisissent des domaines qui offrent un bon compromis pour leur application cible ; les systèmes embarqués critiques pour la sécurité peuvent se permettre une analyse plus lente, mais plus précise ; les linters intégrés à l'intégration continue/déploiement continu (CI/CD) doivent s'exécuter en quelques secondes.

Comment trois outils concrets utilisent l'interprétation abstraite

Plutôt que de décrire la théorie isolément, des outils concrets permettent de clarifier son application.

Facebook Infer utilise une forme d'interprétation abstraite appelée bi-abduction pour analyser le code Java, C, C++ et Objective-C et détecter les déréférencements de pointeurs nuls, les fuites de ressources et les conditions de concurrence. La bi-abduction identifie automatiquement les préconditions et postconditions des fonctions, permettant ainsi une analyse interprocédurale sans spécification manuelle. Infer est déployé en intégration continue chez Facebook, Spotify, Mozilla et des dizaines d'autres grandes entreprises, car il peut gérer des bases de code de plusieurs millions de lignes tout en restant robuste face aux types d'erreurs qu'il détecte.

Astrée utilise l'interprétation abstraite avec des domaines abstraits numériques pour prouver l'absence d'erreurs d'exécution dans les programmes C. Airbus l'a employée pour vérifier formellement le logiciel de commande de vol principal de l'A380, prouvant ainsi l'absence d'erreurs d'exécution dans l'ensemble du système de commande, une garantie qu'aucun autre programme de test ne pouvait fournir. Astrée ne détecte aucun faux négatif pour les classes d'erreurs qu'elle vérifie, mais peut produire des faux positifs nécessitant une vérification manuelle.

Polyspace (MathWorks) applique l'interprétation abstraite au code C et C++ embarqué dans les applications critiques. Il classe chaque opération comme « verte » (absence d'erreur prouvée), « rouge » (erreur avérée) ou « orange » (erreur potentielle nécessitant une vérification). La classification verte constitue une preuve formelle : aucune exécution ne peut provoquer d'erreur d'exécution lors de cette opération.

Le triangle Fiabilité-Précision-Performance

Les outils d'interprétation abstraite doivent composer avec un triangle fondamental de propriétés antagonistes. Aucun outil ne peut maximiser les trois simultanément.

La fiabilité implique l'absence de faux négatifs : chaque bogue réel de la classe analysée est détecté. Les outils fiables offrent des garanties ; les outils non fiables peuvent passer à côté de bogues.

La précision se traduit par un faible taux de faux positifs : les résultats correspondent à des problèmes réels et non à des problèmes théoriques impossibles. Une haute précision exige des domaines abstraits plus précis et une analyse interprocédurale.

La performance se mesure à la rapidité avec laquelle l'analyse s'effectue. Une analyse plus précise est plus coûteuse. Prouver l'absence de toutes les erreurs d'exécution dans un code source d'un million de lignes prend des heures ; une analyse statique de code (linter) ne prend que quelques secondes.

Différentes applications nécessitent différents points dans ce triangle :

  • Linting IDE et CI/CDLa performance d'abord, la précision ensuite, la fiabilité en option.
  • Analyse de sécurité: la précision avant tout (pour réduire la fatigue des développeurs face aux alertes), la fiabilité étant importante pour les classes critiques.
  • Certification critique pour la sécuritéPriorité à la fiabilité (il est impératif de ne pas manquer les véritables bogues), puis aux performances ; les faux positifs sont acceptables avec un processus de vérification manuelle.

Interprétation abstraite dans le développement de systèmes embarqués et critiques pour la sécurité

La question « avantages de l’analyse statique dans le développement embarqué » met en lumière l’un des domaines d’application les plus importants de l’interprétation abstraite. Les systèmes embarqués, les calculateurs automobiles, les micrologiciels de dispositifs médicaux et les logiciels de commande de vol aérospatiaux présentent des contraintes qui rendent l’interprétation abstraite particulièrement précieuse.

Il n'existe pas de banc d'essai pour tous les états. Un calculateur automobile réagit en temps réel à des milliers de combinaisons de capteurs. Il est impossible de concevoir des tests pour chaque combinaison. L'interprétation abstraite couvre simultanément tous les états.

Exigences de certification. Les normes DO-178C (aérospatiale), ISO 26262 (automobile) et IEC 62443 (contrôle industriel) exigent de démontrer le bon fonctionnement du logiciel en toutes circonstances. La vérification formelle par interprétation abstraite permet de satisfaire cette exigence, contrairement aux rapports de couverture de test.

Contraintes de ressources. Les logiciels embarqués sont souvent dépourvus d'allocateur de mémoire, de gestion des exceptions et de mécanisme de repli du système d'exploitation. Une erreur d'exécution, une déréférencement de pointeur nul ou un dépassement de capacité d'un tableau entraînent une défaillance système critique. Le coût de la non-détection de ces bogues ne se limite pas à un rapport d'incident et à un correctif d'urgence ; il s'agit d'un incident de sécurité.

Les analyseurs Astrée et Polyspace sont conçus spécifiquement pour ce contexte. Leur conception accepte un taux élevé de faux positifs et une analyse lente en échange de la garantie qu'aucun faux négatif ne soit détecté.

Les faux positifs et l’aggravation du problème

Le principal reproche adressé aux outils d'interprétation de résumés concerne les faux positifs : des alertes concernant des erreurs potentielles qui ne peuvent en réalité se produire lors de l'exécution. Comprendre que ces faux positifs sont inhérents au système et non le signe d'un défaut de qualité permet de mieux les gérer.

Les faux positifs proviennent de deux sources :

Sur-approximation dans le domaine abstrait. Si le domaine d'intervalle suit x ∈ [0, 100], il ne peut pas faire de distinction entre les cas où x est toujours inférieur à 50 en pratique. Une division par x peut être signalé comme potentiellement diviser par zéro même lorsque la logique du programme le garantit x > 0Un domaine plus précis (suivant la valeur exacte ou une contrainte de liaison) x (à une autre variable) permettrait d'éliminer le faux positif, mais au prix d'un coût de calcul plus élevé.

Élargissement. L'opérateur de convergence qui rend l'analyse des boucles traitable perd nécessairement des informations. Après élargissement x à partir de [0, 5] à [0, +∞), l'analyseur ne sait plus que x reste délimité. Si le code vérifie assert(x < 1000) Après la boucle, cette assertion ne peut plus être prouvée, même si en pratique x reste toujours bien en dessous de 1000.

Stratégies pratiques pour gérer les faux positifs : configurer l’analyse pour utiliser des domaines plus précis pour les modules critiques (en acceptant une analyse plus lente), supprimer les faux positifs confirmés avec des annotations ciblées et traiter les résultats orange/inconnus de l’outil comme une file d’attente d’examen prioritaire plutôt que comme des bogues confirmés.

Comment SMART TS XL Applique l'analyse statique à l'échelle de l'entreprise

SMART TS XL opère dans l'espace où la théorie de l'interprétation abstraite rencontre la réalité de l'entreprise : des bases de code qui couvrent plusieurs langages, des décennies de développement et des frontières organisationnelles qui rendent la vérification formelle par programme impraticable.

Plutôt que d'appliquer un seul domaine abstrait à tous les programmes, SMART TS XL's analyse de code statique Elle combine des techniques d'analyse structurelle adaptées à chaque langage de l'environnement (COBOL, JCL, Java, Python, RPG, PL/I, SQL et les piles modernes) et produit simultanément des indicateurs de qualité, des données de dépendance et des résultats de sécurité sur l'ensemble du portefeuille.

La fonctionnalité de cartographie des dépendances applicatives applique une analyse de type graphe au graphe d'appels interlangages, identifiant ainsi comment les programmes, les ensembles de données et les flux de tâches sont connectés par-delà les frontières linguistiques. Il s'agit d'une analyse systémique globale que les outils monolangages ne peuvent réaliser. C'est un raisonnement structurel au niveau du système : non pas démontrer les propriétés des programmes individuels, mais celles de leurs interconnexions.

La fonctionnalité d'analyse d'impact applique une analyse d'accessibilité au graphe de dépendances : étant donné une modification proposée à un nœud, elle calcule l'ensemble de tous les nœuds accessibles depuis celui-ci. Il s'agit de la question d'analyse statique « qu'est-ce qui sera affecté ? » à laquelle on répond à partir de la structure du code plutôt que par observation à l'exécution ou estimation humaine.

Pour les équipes menant modernisation de l'héritage programmes, SMART TS XLL'analyse structurelle de [nom de l'entreprise] comble le fossé entre les outils d'interprétation abstraite formels (qui sont spécifiques à un langage et nécessitent une expertise du domaine pour être configurés) et le besoin pratique de comprendre ce que font réellement les grands systèmes hérités multilingues non documentés, condition préalable à tout programme de modernisation qui ne veut pas découvrir ses surprises les plus coûteuses en cours d'exécution.

Questions fréquemment posées

Quelle est la différence entre l'interprétation abstraite et la vérification de modèles ? Ce sont deux méthodes formelles de vérification de programmes. L'interprétation abstraite surestime l'ensemble des états possibles (correcte mais potentiellement imprécise). La vérification de modèles explore exhaustivement l'espace d'états (complète mais applicable uniquement aux systèmes finis et bornés). L'interprétation abstraite est adaptée aux grands programmes. La vérification de modèles est adaptée à la détection de propriétés complexes sur des modèles plus petits. Ces deux méthodes sont complémentaires, et non concurrentes.

L'interprétation abstraite est-elle réservée aux logiciels critiques ? Non, même si c'est dans ce contexte qu'elle révèle toute sa valeur. Infer est intégré aux pipelines CI/CD standard des grandes entreprises technologiques, détectant les déréférencements de pointeurs nuls et les fuites de ressources dans le code Java et C courant. Le niveau de rigueur appliqué est modulable : d'une part, une analyse rigoureuse avec garanties formelles ; d'autre part, une analyse heuristique légère, la plupart des outils pratiques se situant entre les deux.

L'interprétation abstraite peut-elle analyser le COBOL ? En tant que théorie, l'interprétation abstraite est indépendante du langage. Son application au COBOL requiert l'implémentation des fonctions de transfert abstraites pour les opérations COBOL, l'arithmétique des champs PIC, les clauses REDEFINES, les noms de conditions de niveau 88, etc. Les outils d'interprétation abstraite généralistes (Infer, Astrée) ne prennent pas en charge le COBOL. Les plateformes d'analyse structurelle d'entreprise qui comprennent nativement le COBOL appliquent des techniques d'analyse statique associées pour détecter les problèmes de qualité, le code mort et les problèmes d'architecture dans les bases de code COBOL.