CPPCHECK : Faire l’inventaire des fonctions et leurs tailles


Cppcheck est un analyseur statique de code C : il permet de détecter un certain nombre d’erreurs, comme des écritures en dehors des limites d’un tableau par exemple. Nous nous intéresserons dans cette série d’articles à une autre de ses fonctions : la fonction dump.

Cppcheck avec le paramètre –dump génère un fichier XML contenant le résultat du parsing par cppcheck du fichier c concerné. A l’aide de cette fonctionnalité et un peu de Python nous allons faire l’inventaire des fonctions et de leurs tailles dans un fichier C.

CPPCHECK: Structure du fichier dump

Nous allons utiliser le fichier exemple téléchargeable ici :

https://raw.githubusercontent.com/firehol/netdata/master/src/avl.c

Pour générer le fichier dump, taper simplement :

cppcheck –dump avl.c

Un fichier avlc.c.dump est alors créé.

Les différentes configurations

Les fichiers C peuvent être constitués de directives de compilation (#ifdef par exemple), elles ne sont pas connues par cppcheck à priori, or le code peut être très différent suivant que l’on compile avec l’option __WIN32__ ou __LINUX__.

Cppcheck produit autant de balises dump que de configurations possibles dans le code. Elles sont présentées dans le fichier xml sous forme de liste de tags <dump> comme suit :

<dumps>
 <dump cfg="">[…]</dump>
<dump cfg="CONFIGA">[…]</dump>
<dump cfg="CONFIGB">[…]</dump>
<dump cfg="CONFIGA ;CONFIGB">[…]</dump>
[…]
</dumps>

En python lxml, pour les rechercher le code suivant peut-être écrit :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from lxml import etree

lines = sys.stdin.read()
tree  = etree.fromstring(lines)

for dump in tree.xpath("/dumps/dump"):
    print dump.get("cfg")

Pour utiliser ce programme sur le fichier exemple :

$ cat avl.c.dump | ./configs_display.py
AVL_LOCK_WITH_MUTEX
AVL_WITHOUT_PTHREADS

Trois configurations sont donc possibles dans ce fichier : aucune variable définie, AVL_LOCK_WITH_MUTEX seule et AVL_WITHOUT_PTHREADS seule

La liste des tokens

Les tokens d’un fichier en C sont les mots qui le composent : opérateur, mot clef, constante, définition de fonction, … Pour chaque configuration les tokens sont accessibles dans le fichier dump comme suit :

<dumps><dump cfg="…">
  <tokenlist>
    <token …>
  </tokenlist>

La liste des fonctions globales

En plus des tokens, Cppcheck produit l’inventaire des espaces de définition de variables (scope en Anglais) Ce qui nous interesse ici, c’est le scope global qui contient tous les symboles C dits globaux. On peut trouver ces fonctions ici :

<dumps>
  <dump config = "…">
  <scopes>
    <scope type= "Global">
    <functionList>
   <function id="0x1234" … >
    </functionList>
   </scope>
 </scopes>
</dump>
</dumps>

Chaque fonction ainsi définie est représentée par un identifiant unique (propriété id), cet id peut être récupéré pour déterminer son scope et ainsi déterminer les tokens en marquant le début et la fin. Ce qui nous donnera le nombre de ligne.

Pour obtenir le scope d’une fonction, il faut chercher dans le XML les éléments suivants :

<dumps>
  <dump config = "…">
  <scopes>
    <scope type= "Function" function=FUNCTION_ID classStart=”0x1234” classEnd=”0x1235”>
 </scopes>
</dump>
</dumps>

Les éléments classStart sont les identifiants de token marquant respectivement le début let la fin de la fonction.

Voici le code python complet permettant de retrouver lister l’inventaire des fonctions et de leurs tailles :

#!/usr/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from lxml import etree

def getTokenLine(tokenId):
    tok = tree.xpath("/dumps/dump[@cfg='']/tokenlist/token[@id='" + tokenId + "']")
    return int((tok[0].get("linenr")))

def getFunctionLength(functionId):
    tok = tree.xpath("/dumps/dump[@cfg='']/scopes/scope[@type='Function'][@function='" + functionId + "']")
    return (getTokenLine(tok[0].get("classEnd")) -  getTokenLine(tok[0].get("classStart")))

lines = sys.stdin.read()
tree  = etree.fromstring(lines)

for function in tree.xpath("/dumps/dump[@cfg='']/scopes/scope[@type='Global']/functionList/function"):
    print (function.get("name")), " ", getFunctionLength(function.get("id"))

Le resultat de ce programme sur notre fichier C:

avl_search   17
avl_insert   92
avl_remove   140
avl_walker   20
avl_traverse   2
avl_read_lock   8
avl_write_lock   8
avl_unlock   8
avl_init_lock   16
avl_search_lock   5
avl_remove_lock   5
avl_insert_lock   5
avl_traverse_lock   6
avl_init   3