C++, dénicher les bugs et assainir son code


Sommaire



Contexte

... Et tu te dis qu'il serait pas mal de voir s'il n'y a pas de fuites mémoire ni de fonctions inutilisées.

Pour ce faire, nous utiliserons deux outils qui ont fait leurs preuves : Valgrind et callcatcher.
Le premier est assez complet dans son genre : détection des fuites mémoires et espaces alloués non libérés entre autres.
Le second, quant à lui, est un spécialiste assez méconnu qui aide à la découverte de fonctions (ou méthodes) inutilisées.



Pré-requis

Lors des phases de compilation, ne pas laisser le compilo faire d'optimisations et générer les symboles de debogage :

$ gcc -O0 -g ...
gcc ou g++, à ta guise.

Comme cela, les traceurs pourront facilement indiquer à quel ligne l'erreur est survenue ou la fonction appelée.



Fonctions inutilisées (callcatcher)

Dans le cas d'une reprise d'un gros projet tel que LibreOffice, commencer par faire le ménage dans les fonctions me semble prioritaire.
Pour faire simple et parce que je n'ai pas la motive de télécharger les sources du monstre LibreOffice, je ferai les tests sur mon projet perso ; j'ai nommé Cracker-ng.

callcatcher s'utilise comme programme à appeler avant le compilateur :

$ callcatcher g++ -O0 -g shared/functions.cc -o cptfunctions.o
# (...)
callcatcher - detecting compiling: 
	collecting cptfunctions.o
 'g++' '-g' 'shared/functions.cc' '-o' 'cptfunctions.o' '-O0' '-S'
		non-virtual functions_ng::substr(...)
		non-virtual functions_ng::file_exists(char const*)
		non-virtual functions_ng::basename(...)
		non-virtual std::operator|(std::_Ios_Openmode, std::_Ios_Openmode)
		non-virtual functions_ng::format_number(unsigned long const&)
	6 methods (0 virtual)
# (...)
callcatcher - dump currently unused:
	Use "callanalyse" to manually analyse a set of compiler output files
	autoanalysing /stock/projets/cracker-ng/src/cptcracker-ng
	Currently unused functions are...
		functions_ng::format_number(unsigned long const&)
		functions_ng::substr(...)
Ici nous pouvons voir que functions_ng::format_number et functions_ng::substr peuvent être supprimées sans soucis.
Il ne faudra pas oublier de supprimer les prototypes de celles-ci.

Pour en revenir à LibreOffice, les dév. ont fait en sorte de générer un fichier unusedcode.easy qui contient les fonctions faciles à nettoyer -- contributeurs & contributeuses si vous me lisez ;)



Dénicher les erreurs mémoire (Valgrind)

Avant de commencer, voici les options de Valgrind que nous utiliserons :


Pour les exemples, j'utilise une version qui date un peu -- CPT Cracker-ng version 0.1-2 :
$ valgrind --leak-check=full --show-reachable=yes --track-origins=yes ./bin/cptcracker-ng
# (...)
==14093== HEAP SUMMARY:
==14093==     in use at exit: 32 bytes in 1 blocks
==14093==   total heap usage: 24 allocs, 23 frees, 14,624 bytes allocated
==14093== 
==14093== 32 bytes in 1 blocks are definitely lost in loss record 1 of 1
==14093==    at 0x4C2A26B: malloc (in .../vgpreload_memcheck-amd64-linux.so)
==14093==    by 0x402D34: ccrypt_crack(...) (cptcracker-ng.cpp:74)
==14093==    by 0x402C3B: main (cptcracker-ng.cpp:38)
==14093== 
==14093== LEAK SUMMARY:
==14093==    definitely lost: 32 bytes in 1 blocks
==14093==    indirectly lost: 0 bytes in 0 blocks
==14093==      possibly lost: 0 bytes in 0 blocks
==14093==    still reachable: 0 bytes in 0 blocks
==14093==         suppressed: 0 bytes in 0 blocks
==14093== 
==14093== For counts of detected and suppressed errors, rerun with: -v
==14093== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
Dans cptcracker-ng.cpp, ligne 74 il y a un espace alloué pour une variable qui n'a pas été libéré.

Voyons la partie fâcheuse de ce fichier :
/* ligne 74 */ b = (ccrypt_stream_s*)malloc(sizeof(ccrypt_stream_s));
/* ligne 97 */ delete[] encryption_header, b;
OOOOkkkkk, erreurs de jeunesse !
Il faut savoir que les allocations mémoire vont de paire : malloc/free en C et new/delete en C++.
De plus, ici je tente de faire un delete sur deux variables, la seconde est donc ignorée.
Là j'ai clairement fais n'importe quoi...

Si j'ajoute un autre delete pour l'exemple :
==14472== Mismatched free() / delete / delete []
==14472==    at 0x4C2850C: operator delete[](void*) (in .../vgpreload_memcheck-amd64-linux.so)
==14472==    by 0x402E17: ccrypt_crack(...) (cptcracker-ng.cpp:98)
==14472==    by 0x402C3B: main (cptcracker-ng.cpp:38)
==14472==  Address 0x5b78360 is 0 bytes inside a block of size 32 alloc'd
==14472==    at 0x4C2A26B: malloc (in .../vgpreload_memcheck-amd64-linux.so)
==14472==    by 0x402D34: ccrypt_crack(...) (cptcracker-ng.cpp:74)
==14472==    by 0x402C3B: main (cptcracker-ng.cpp:38)
C'est clair, ne pas utiliser malloc avec delete et new avec free.
En utilisant free, Valgrind dit que tout va bien, ouf :)

À partir de maintenant, j'utilise le module ZIP en cours de développement (2013-02.14_dev) :
$ ./bin/zipcracker-ng -f fichier.zip
 ! Bad ZIP file (wrong headers).
*** glibc detected *** ./bin/zipcracker-ng: double free or corruption (fasttop): 0x... ***
# (...)

$ valgrind ./bin/zipcracker-ng -f fichier.zip
# (..)
==15336== Invalid free() / delete / delete[] / realloc()
==15336==    at 0x4C2850C: operator delete[](void*) (in .../vgpreload_memcheck-amd64-linux.so)
==15336==    by 0x407594: main (main.cc:61)
==15336==  Address 0x5b76040 is 0 bytes inside a block of size 72 free'd
==15336==    at 0x4C2850C: operator delete[](void*) (in .../vgpreload_memcheck-amd64-linux.so)
==15336==    by 0x407513: main (main.cc:57)
# (...)
Il semblerait que j'essaie de libérer un espace déjà libéré.
Pour corriger ça, il faut réperer la ligne 57 de main.c et trouver la 2ème libération. Çela peut vite devenir un casse tête.



Bonus

Pour avoir un aperçu global d'un projet (lignes de code, langages utilisés, ...) :

$ sloccount cracker-ng/
SLOC	Directory	SLOC-by-Language (Sorted)
917     cpt             cpp=917
883     shared          cpp=883
715     zip             cpp=715
186     top_dir         cpp=186

Totals grouped by language (dominant language first):
cpp:           2701 (100.00%)

Voilà, n'hésite pas à me contacter pour compléter ou demander un complément.



Historique


Contenu modifié le 01/10/2013.
moc.liamg@gitobob - Philosophie.

congregational