Travis CI est un service Web distribué pour la création et le test de logiciels qui utilise GitHub comme service d'hébergement de code source. En plus des scripts ci-dessus, vous pouvez ajouter le vôtre, grâce aux nombreuses options de configuration. Dans cet article, nous allons configurer Travis CI pour travailler avec PVS-Studio par l'exemple du code PPSSPP.
Présentation
Travis CI est un service Web de création et de test de logiciels. Il est généralement utilisé en combinaison avec la pratique de l'intégration continue.
PPSSPP est un émulateur de console de jeux PSP. Le programme est capable d'émuler le lancement de n'importe quel jeu avec des images de disques conçus pour Sony PSP. Le programme est sorti le 1er novembre 2012. PPSSPP est distribué sous licence GPL v2. Tout le monde peut apporter des améliorations au
code source du projet.
PVS-Studio - analyseur de code statique pour rechercher les erreurs et les vulnérabilités potentielles dans le code du programme. Dans cet article, nous allons lancer PVS-Studio dans le cloud plutôt que localement sur l'ordinateur du développeur à diverses fins et rechercher des erreurs dans PPSSPP.
Mise en place de Travis CI
Nous aurons besoin d'un référentiel sur GitHub où se trouve le projet dont nous avons besoin, ainsi que d'une clé pour PVS-Studio (vous pouvez obtenir une
clé d'essai ou une
clé gratuite pour les projets Open Source ).
Allons sur
le site
Travis CI . Après autorisation avec l'aide du compte GitHub, nous aurons une liste de référentiels:
Pour le test, j'ai fait une fourche PPSSPP.
Nous activons le référentiel que nous voulons construire:
Pour le moment, Travis CI ne peut pas construire notre projet car il n'y a pas d'instructions pour le construire. C'est pourquoi il est temps pour la configuration.
Au cours de l'analyse, nous aurons besoin de certaines variables, par exemple, la clé de PVS-Studio, qu'il serait indésirable de spécifier dans le fichier de configuration. Ajoutons donc des variables d'environnement en configurant la construction dans Travis CI:
Nous aurons besoin de:
- PVS_USERNAME - nom d'utilisateur
- PVS_KEY - clé
- MAIL_USER - email qui sera utilisé pour envoyer le rapport
- MAIL_PASSWORD - mot de passe de l'e-mail
Les deux derniers sont facultatifs. Ils seront utilisés pour envoyer les résultats par courrier. Si vous souhaitez envoyer le rapport d'une autre manière, vous n'avez pas besoin de les spécifier.
Nous avons donc ajouté les variables d'environnement dont nous avons besoin:
Créons maintenant un fichier
.travis.yml et mettons-le à la racine du projet. PPSSPP avait déjà un fichier de configuration pour Travis CI, cependant, il était trop volumineux et ne convenait pas à l'exemple, nous avons donc dû le simplifier et ne laisser que les éléments de base.
Précisons d'abord le langage de programmation, la version d'Ubuntu Linux que nous voulons utiliser sur la machine virtuelle et les packages nécessaires à la construction:
language: cpp dist: xenial addons: apt: update: true packages: - ant - aria2 - build-essential - cmake - libgl1-mesa-dev - libglu1-mesa-dev - libsdl2-dev - pv - sendemail - software-properties-common sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - sourceline: 'ppa:ubuntu-sdk-team/ppa'
Tous les packages ajoutés ne sont nécessaires que pour PPSSPP.
Spécifiez maintenant la matrice de construction:
matrix: include: - os: linux compiler: "gcc" env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes - os: linux compiler: "clang" env: PPSSPP_BUILD_TYPE=Linux
Un peu plus sur la section
matrice . Dans Travis CI, il existe deux façons de créer des options de construction: la première consiste à spécifier des compilateurs, des types de systèmes d'exploitation, des variables d'environnement, etc. avec la liste, après quoi la matrice de toutes les combinaisons possibles sera générée; le second est une indication explicite de la matrice. Bien sûr, vous pouvez combiner ces deux approches et ajouter un cas unique ou, au contraire, l'exclure en utilisant la section
exclude . Vous pouvez en savoir plus à ce sujet dans la
documentation de Travis CI .
La seule chose qui reste à faire est de spécifier des instructions de construction spécifiques au projet:
before_install: - travis_retry bash .travis.sh travis_before_install install: - travis_retry bash .travis.sh travis_install script: - bash .travis.sh travis_script after_success: - bash .travis.sh travis_after_success
Travis CI vous permet d'ajouter vos propres commandes pour différentes étapes de la vie de la machine virtuelle. La section
before_install s'exécute avant d'installer les packages. Ensuite,
installez , qui suit l'installation des packages de la liste
addons.apt que nous avons spécifiée ci-dessus. La construction elle-même a lieu dans un
script . Si tout a réussi, nous
entrons dans
after_success (c'est là que nous allons commencer l'analyse statique). Ce ne sont pas toutes les étapes que vous pouvez modifier, si vous en avez besoin de plus, vous devriez consulter la
documentation sur Travis CI .
Pour faciliter la lecture, les commandes ont été placées dans un
script séparé
.travis.sh , qui est placé à la racine du projet.
Nous avons donc le fichier suivant
.travis.yml :
language: cpp dist: xenial addons: apt: update: true packages: - ant - aria2 - build-essential - cmake - libgl1-mesa-dev - libglu1-mesa-dev - libsdl2-dev - pv - sendemail - software-properties-common sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - sourceline: 'ppa:ubuntu-sdk-team/ppa' matrix: include: - os: linux compiler: "gcc" env: PVS_ANALYZE=Yes - os: linux compiler: "clang" before_install: - travis_retry bash .travis.sh travis_before_install install: - travis_retry bash .travis.sh travis_install script: - bash .travis.sh travis_script after_success: - bash .travis.sh travis_after_success
Avant d'installer les packages, mettons à jour les sous-modules. Cela est nécessaire pour créer des PPSSPP. Ajoutez la première fonction à
.travis.sh (notez l'extension):
travis_before_install() { git submodule update --init --recursive }
Nous sommes maintenant arrivés directement à la configuration du lancement automatique de PVS-Studio dans Travis CI. Tout d'abord, nous devons installer le package PVS-Studio dans le système:
travis_install() { if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8 fi if [ "$PVS_ANALYZE" = "Yes" ]; then wget -q -O - https://files.viva64.com/etc/pubkey.txt \ | sudo apt-key add - sudo wget -O /etc/apt/sources.list.d/viva64.list \ https://files.viva64.com/etc/viva64.list sudo apt-get update -qq sudo apt-get install -qq pvs-studio \ libio-socket-ssl-perl \ libnet-ssleay-perl fi download_extract \ "https://cmake.org/files/v3.6/cmake-3.6.2-Linux-x86_64.tar.gz" \ cmake-3.6.2-Linux-x86_64.tar.gz }
Au début de la fonction
travis_install , nous installons les compilateurs dont nous avons besoin en utilisant des variables d'environnement. Ensuite, si la variable
$ PVS_ANALYZE stocke la valeur de
Yes (nous l'avons spécifiée dans la section
env lors de la configuration de la matrice de construction), nous installons le
package pvs-studio . En plus de cela, il existe également des
packages libio-socket-ssl-perl et
libnet-ssleay-perl , mais ils sont nécessaires pour envoyer les résultats par courrier, ils ne sont donc pas nécessaires si vous avez choisi un autre moyen de remise des rapports.
La fonction download_extract télécharge et décompresse l'archive spécifiée:
download_extract() { aria2c -x 16 $1 -o $2 tar -xf $2 }
Il est temps de construire un projet. Cela se produit dans la section
script :
travis_script() { if [ -d cmake-3.6.2-Linux-x86_64 ]; then export PATH=$(pwd)/cmake-3.6.2-Linux-x86_64/bin:$PATH fi CMAKE_ARGS="-DHEADLESS=ON ${CMAKE_ARGS}" if [ "$PVS_ANALYZE" = "Yes" ]; then CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" fi cmake $CMAKE_ARGS CMakeLists.txt make }
En fait, il s'agit d'une configuration d'origine simplifiée, à l'exception de ces lignes:
if [ "$PVS_ANALYZE" = "Yes" ]; then CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" fi
Dans cette section du code, nous définissons l'indicateur d'exportation de la commande de compilation pour
cmake . Cela est nécessaire pour un analyseur de code statique. Vous pouvez en savoir plus à ce sujet dans l'article "
Comment lancer PVS-Studio sous Linux et macOS ".
Si la construction a réussi, nous arriverons à
after_success où nous exécuterons une analyse statique:
travis_after_success() { if [ "$PVS_ANALYZE" = "Yes" ]; then pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic \ -o PVS-Studio-${CC}.log \ --disableLicenseExpirationCheck plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html sendemail -t mail@domain.com \ -u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" \ -m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" \ -s smtp.gmail.com:587 \ -xu $MAIL_USER \ -xp $MAIL_PASSWORD \ -o tls=yes \ -f $MAIL_USER \ -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html fi }
Examinons en détail les lignes suivantes:
pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic \ -o PVS-Studio-${CC}.log \ --disableLicenseExpirationCheck plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html
La première ligne génère le fichier de licence à partir du nom d'utilisateur et de la clé que nous avons spécifiés au début de la configuration des variables d'environnement Travis CI.
La deuxième ligne démarre directement l'analyse. L'indicateur
-j <N> définit le nombre de threads d'analyse, l'indicateur
-l <fichier> définit la licence, l'indicateur
-o <fichier> définit le fichier pour afficher les journaux et l'indicateur -
disableLicenseExpirationCheck est nécessaire pour les versions d'essai , car par défaut,
pvs-studio-analyzer avertira l'utilisateur de l'expiration imminente de la licence. Pour éviter que cela ne se produise, vous pouvez spécifier cet indicateur.
Le fichier journal contient une sortie non traitée qui ne peut pas être lue sans conversion, vous devez donc d'abord rendre le fichier lisible.
Exécutons les journaux via
plog-converter et obtenons un fichier html à la sortie.
Dans cet exemple, j'ai décidé d'envoyer des rapports par courrier électronique à l'aide de la commande
sendemail .
Le résultat a été le
fichier .travis.sh suivant:
#/bin/bash travis_before_install() { git submodule update --init --recursive } download_extract() { aria2c -x 16 $1 -o $2 tar -xf $2 } travis_install() { if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8 fi if [ "$PVS_ANALYZE" = "Yes" ]; then wget -q -O - https://files.viva64.com/etc/pubkey.txt \ | sudo apt-key add - sudo wget -O /etc/apt/sources.list.d/viva64.list \ https://files.viva64.com/etc/viva64.list sudo apt-get update -qq sudo apt-get install -qq pvs-studio \ libio-socket-ssl-perl \ libnet-ssleay-perl fi download_extract \ "https://cmake.org/files/v3.6/cmake-3.6.2-Linux-x86_64.tar.gz" \ cmake-3.6.2-Linux-x86_64.tar.gz } travis_script() { if [ -d cmake-3.6.2-Linux-x86_64 ]; then export PATH=$(pwd)/cmake-3.6.2-Linux-x86_64/bin:$PATH fi CMAKE_ARGS="-DHEADLESS=ON ${CMAKE_ARGS}" if [ "$PVS_ANALYZE" = "Yes" ]; then CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" fi cmake $CMAKE_ARGS CMakeLists.txt make } travis_after_success() { if [ "$PVS_ANALYZE" = "Yes" ]; then pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic \ -o PVS-Studio-${CC}.log \ --disableLicenseExpirationCheck plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html sendemail -t mail@domain.com \ -u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" \ -m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" \ -s smtp.gmail.com:587 \ -xu $MAIL_USER \ -xp $MAIL_PASSWORD \ -o tls=yes \ -f $MAIL_USER \ -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html fi } set -e set -x $1;
Il est temps d'ajouter les modifications au référentiel git, puis Travis CI démarrera automatiquement la construction. Cliquez sur "ppsspp" pour créer des rapports:
Nous verrons un aperçu de la version actuelle:
Si la construction est terminée avec succès, nous recevrons un e-mail avec les résultats de l'analyse statique. Bien sûr, l'envoi par courrier n'est pas le seul moyen d'obtenir le rapport. Vous pouvez choisir n'importe quelle méthode de mise en œuvre. Mais il est important de se rappeler qu'il sera impossible d'accéder aux fichiers de la machine virtuelle une fois la construction terminée.
Bref aperçu des erreurs
Nous avons terminé avec succès la partie la plus difficile. Faisons maintenant en sorte que tous nos efforts soient justifiés. Examinons quelques points intéressants du rapport d'analyse statique qui m'est parvenu par mail (ce n'est pas pour rien que je l'ai précisé).
Optimisations dangereuses
void sha1( unsigned char *input, int ilen, unsigned char output[20] ) { sha1_context ctx; sha1_starts( &ctx ); sha1_update( &ctx, input, ilen ); sha1_finish( &ctx, output ); memset( &ctx, 0, sizeof( sha1_context ) ); }
L'avertissement PVS-Studio:
V597 Le compilateur pourrait supprimer l'appel de fonction 'memset', qui est utilisé pour vider le tampon 'sum'. La fonction RtlSecureZeroMemory () doit être utilisée pour effacer les données privées. sha1.cpp 325
Ce fragment de code se trouve dans le module de hachage sécurisé, mais il contient un grave défaut de sécurité (
CWE-14 ). Prenons la liste des assembleurs qui est générée lors de la compilation de la version Debug:
; Line 355 mov r8d, 20 xor edx, edx lea rcx, QWORD PTR sum$[rsp] call memset ; Line 356
Tout va bien et la fonction
memset est exécutée, effaçant ainsi les données importantes dans la RAM, mais vous ne devriez pas encore être content. Prenons la liste des assembleurs de la version Release avec optimisation:
; 354 : ; 355 : memset( sum, 0, sizeof( sum ) ); ; 356 :}
Comme vous pouvez le voir dans la liste, le compilateur a ignoré l'appel de
memset . Cela est lié au fait que la fonction
sha1 n'appelle plus la structure
ctx après avoir appelé
memset . C'est pourquoi le compilateur ne voit aucun sens à perdre du temps processeur sur l'écrasement de la mémoire non utilisée à l'avenir. Vous pouvez le corriger en utilisant la fonction
RtlSecureZeroMemory ou une fonction
similaire .
À droite:
void sha1( unsigned char *input, int ilen, unsigned char output[20] ) { sha1_context ctx; sha1_starts( &ctx ); sha1_update( &ctx, input, ilen ); sha1_finish( &ctx, output ); RtlSecureZeroMemory(&ctx, sizeof( sha1_context ) ); }
Comparaison inutile
static u32 sceAudioOutputPannedBlocking (u32 chan, int leftvol, int rightvol, u32 samplePtr) { int result = 0;
L'avertissement PVS-Studio: l'expression
V547 'leftvol> = 0' est toujours vraie. sceAudio.cpp 120
Faites attention à la branche else pour le premier
if . Le code ne sera exécuté que si toutes les conditions
leftvol> 0xFFFFF || rightvol> 0xFFFF || leftvol <0 || rightvol <0 sont faux. Par conséquent, nous obtenons les instructions suivantes qui seront vraies pour la branche else:
leftvol <= 0xFFFFF, rightvol <= 0xFFFFF, leftvol> = 0 et rightvol> = 0 . Faites attention aux deux dernières déclarations. Est-il raisonnable de vérifier quelle est la condition nécessaire à l'exécution de ce fragment de code?
Nous pouvons donc calmement supprimer ces opérateurs conditionnels:
static u32 sceAudioOutputPannedBlocking (u32 chan, int leftvol, int rightvol, u32 samplePtr) { int result = 0;
Un autre scénario. Derrière ces conditions redondantes, il y a une erreur. Peut-être avons-nous vérifié ce qui n'est pas ce dont nous avons besoin ...
Ctrl + C Ctrl + V contre-attaque
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) { if (!Memory::IsValidAddress(psmfData) || !Memory::IsValidAddress(psmfData)) { return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address"); } .... }
V501 Il existe des sous-expressions identiques '! Memory :: IsValidAddress (psmfData)' à gauche et à droite de '||' opérateur. scePsmf.cpp 703
Notez le chèque à l'intérieur
si . Ne vous semble-t-il pas étrange que nous
vérifions si l'adresse
psmfData est valide deux fois plus? Donc je trouve ça étrange ... En fait, nous avons une erreur d'impression devant nous, bien sûr, et l'idée était de vérifier les deux paramètres d'entrée.
La bonne variante est:
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) { if (!Memory::IsValidAddress(psmfStruct) || !Memory::IsValidAddress(psmfData)) { return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address"); } .... }
Variable oubliée
extern void ud_translate_att( int size = 0; .... if (size == 8) { ud_asmprintf(u, "b"); } else if (size == 16) { ud_asmprintf(u, "w"); } else if (size == 64) { ud_asmprintf(u, "q"); } .... }
L'avertissement PVS-Studio: l'expression
V547 'taille == 8' est toujours fausse. syn-att.c 195
Cette erreur se trouve dans le dossier
ext , elle ne s'applique donc pas vraiment au projet, mais l'erreur a été trouvée avant de la remarquer, j'ai donc décidé de la conserver. Pourtant, cet article ne concerne pas la révision des erreurs mais l'intégration avec Travis CI et aucune configuration d'analyseur n'a été effectuée.
La variable de
taille est initialisée avec une constante, mais elle n'est pas utilisée du tout dans le code jusqu'à l'opérateur
if qui, bien sûr, génère de
fausses informations lors de la vérification de la condition car, comme nous nous en souvenons, la
taille est égale à zéro. Les vérifications ultérieures n'ont pas non plus de sens.
Apparemment, l'auteur du fragment de code a oublié de remplacer la variable de
taille avant cela.
Arrêter
C'est là que nous allons arrêter avec les erreurs. Le but de cet article est de montrer comment PVS-Studio fonctionne avec Travis CI et non d'analyser le projet aussi complètement que possible. Si vous voulez des erreurs plus grandes et plus belles, vous pouvez toujours les voir
ici :).
Conclusion
L'utilisation de services Web pour la construction de projets avec une pratique d'analyse incrémentielle vous permet de détecter de nombreux problèmes juste après la fusion du code. Cependant, une génération peut ne pas être suffisante, donc la configuration des tests avec l'analyse statique améliorera considérablement la qualité du code.
Liens utiles