Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple d'un émulateur de console de jeu PSP

Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple d'un émulateur de console de jeu PSP
Travis CI est un service Web distribué permettant de créer et de tester des logiciels qui utilisent GitHub comme hébergement de code source. En plus des scénarios de fonctionnement ci-dessus, vous pouvez ajouter les vôtres grâce aux nombreuses options de configuration. Dans cet article, nous allons configurer Travis CI pour qu'il fonctionne avec PVS-Studio à l'aide de l'exemple de code PPSSPP.

introduction

Travis CI est un service Web permettant de créer et de tester des logiciels. Il est généralement utilisé en conjonction avec des pratiques d’intégration continue.

PPSSPP - Émulateur de console de jeu PSP. Le programme est capable d'émuler le lancement de n'importe quel jeu à partir d'images disque destinées à Sony PSP. Le programme est sorti le 1er novembre 2012. PPSSPP est sous licence GPL v2. N'importe qui peut apporter des améliorations à code source du projet.

PVS-Studio — un analyseur de code statique pour rechercher des erreurs et des vulnérabilités potentielles dans le code du programme. Dans cet article, pour changer, nous lancerons PVS-Studio non pas localement sur la machine du développeur, mais dans le cloud, et rechercherons les erreurs dans PPSSPP.

Configuration 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 clé d'essai ou gratuit pour les projets Open Source).

Allons sur le site Travis CI. Après autorisation à l'aide de votre compte GitHub, nous verrons une liste de référentiels :

Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple d'un émulateur de console de jeu PSP
Pour le test, j'ai forké PPSSPP.

Nous activons le référentiel que nous souhaitons collecter :

Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple d'un émulateur de console de jeu PSP
Pour le moment, Travis CI ne peut pas construire notre projet car il n'y a pas d'instructions pour la construction. Il est donc temps de procéder à la configuration.

Lors de l'analyse, certaines variables nous seront utiles, par exemple la clé de PVS-Studio, qu'il ne serait pas souhaitable de préciser dans le fichier de configuration. Ajoutons donc des variables d'environnement en utilisant les paramètres de construction dans Travis CI :

Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple d'un émulateur de console de jeu PSP
Nous avons 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. Ceux-ci serviront à envoyer les résultats par mail. Si vous souhaitez diffuser le rapport d'une autre manière, vous n'avez pas besoin de les indiquer.

Nous avons donc ajouté les variables d'environnement dont nous avons besoin :

Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple d'un émulateur de console de jeu PSP
Créons maintenant un fichier .travis.yml et placez-le à la racine du projet. PPSSPP disposait déjà d'un fichier de configuration pour Travis CI, cependant, il était trop volumineux et totalement inadapté à l'exemple, nous avons donc dû le simplifier considérablement et ne laisser que les éléments de base.

Tout d'abord, indiquons la langue, la version d'Ubuntu Linux que nous souhaitons utiliser dans la machine virtuelle et les packages nécessaires à la build :

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 répertoriés sont nécessaires exclusivement pour PPSSPP.

Indiquons maintenant la matrice d'assemblage :

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 manières de créer des options de construction : la première consiste à spécifier une liste de compilateurs, de types de système d'exploitation, de variables d'environnement, etc., après quoi une matrice de toutes les combinaisons possibles est générée ; la seconde est une indication explicite de la matrice. Bien entendu, vous pouvez combiner ces deux approches et ajouter un cas unique, ou au contraire l'exclure grâce à la section exclure. Vous pouvez en savoir plus à ce sujet dans Documentation Travis CI.

Il ne reste plus qu'à fournir des instructions de montage 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 d'une machine virtuelle. Section avant_installation exécuté avant d’installer les packages. Alors installer, qui suit l'installation des packages de la liste addons.aptque nous avons indiqué plus haut. L'assemblée elle-même a lieu à scénario. Si tout s'est bien passé, alors nous nous retrouvons dans après_succès (c'est dans cette section que nous effectuerons une analyse statique). Ce ne sont pas toutes les étapes qui peuvent être modifiées, si vous en avez besoin de plus, alors vous devriez regarder dans Documentation 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.

On a 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, nous mettrons à jour les sous-modules. Ceci est nécessaire pour construire PPSSPP. Ajoutons la première fonction à .travis.sh (notez l'extension):

travis_before_install() {
  git submodule update --init --recursive
}

Nous arrivons maintenant directement à la configuration du lancement automatique de PVS-Studio dans Travis CI. Nous devons d’abord installer le package PVS-Studio sur 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. Alors si la variable $PVS_ANALYZE stocke la valeur Oui (nous l'avons indiqué dans la section env lors de la configuration de la matrice de construction), nous installons le package pvs-studio. En plus de cela, les forfaits sont également indiqués libio-socket-ssl-perl и libnet-ssleay-perlCependant, ils sont requis pour l'envoi des résultats et ne sont donc pas nécessaires si vous avez choisi une autre méthode pour envoyer votre rapport.

Fonction téléchargement_extrait 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 monter le projet. Cela se produit dans la section scénario:

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 originale 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 de code, nous avons défini pour cmake indicateur pour exporter les commandes de compilation. Ceci est nécessaire pour un analyseur de code statique. Vous pouvez en savoir plus à ce sujet dans l'article «Comment exécuter PVS-Studio sur Linux et macOS«.

Si l'assemblage a réussi, alors nous arrivons à après_succès, où nous effectuons 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 [email protected] 
              -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
}

Regardons de plus près 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 un fichier de licence à partir du nom d'utilisateur et de la clé que nous avons spécifiés au tout début lors de la configuration des variables d'environnement Travis CI.

La deuxième ligne démarre directement l'analyse. Drapeau -j définit le nombre de threads pour l'analyse, indicateur -l indique la licence, le drapeau -o définit le fichier de sortie des journaux et le drapeau -disableLicenseExpirationCheck requis pour les versions d'essai, car par défaut analyseur-pvs-studio avertira l'utilisateur que la licence est sur le point d'expirer. Pour éviter que cela ne se produise, vous pouvez spécifier cet indicateur.

Le fichier journal contient une sortie brute qui ne peut pas être lue sans conversion. Vous devez donc d'abord rendre le fichier lisible. Passons les journaux plog-convertisseur, et la sortie est un fichier html.

Dans cet exemple, j'ai décidé d'envoyer des rapports par mail en utilisant la commande envoyeremail.

En conséquence, nous avons obtenu le fichier suivant .travis.sh:

#/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 [email protected] 
              -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 maintenant temps d'appliquer les modifications au référentiel git, après quoi Travis CI exécutera automatiquement la build. Cliquez sur « ppsspp » pour accéder aux rapports de build :

Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple d'un émulateur de console de jeu PSP
Nous verrons un aperçu de la version actuelle :

Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple d'un émulateur de console de jeu PSP
Si la construction est terminée avec succès, nous recevrons un e-mail avec les résultats de l'analyse statique. Bien entendu, l’envoi par courrier n’est pas le seul moyen de recevoir un rapport. Vous pouvez choisir n’importe quelle méthode de mise en œuvre. Mais il est important de se rappeler qu'une fois la construction terminée, il ne sera plus possible d'accéder aux fichiers de la machine virtuelle.

Résumé des erreurs

Nous avons terminé avec succès la partie la plus difficile. Assurons-nous maintenant que tous nos efforts en valent la peine. Regardons 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 indiqué).

Optimisation dangereuse

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 ) );
}

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 bout de code se trouve dans le module de hachage sécurisé, il contient cependant une sérieuse faille de sécurité (CWE-14). Examinons la liste des assemblys 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 est en ordre et la fonction jeu de mémoire est exécuté, écrasant ainsi les données importantes dans la RAM, mais ne vous réjouissez pas pour l'instant. Regardons la liste des assemblys de la version Release avec optimisation :

; 354  :
; 355  :  memset( sum, 0, sizeof( sum ) );
; 356  :}

Comme le montre la liste, le compilateur a ignoré l'appel jeu de mémoire. Cela est dû au fait que dans la fonction sha1 après l'appel jeu de mémoire plus de référence à la structure ctx. Par conséquent, le compilateur ne voit aucun intérêt à perdre du temps processeur à écraser de la mémoire qui ne sera pas utilisée à l'avenir. Vous pouvez résoudre ce problème en utilisant la fonction RtlSecureZeroMémoire ou similaire sa.

Correctement:

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;
  // For some reason, this is the only one that checks for negative.
  if (leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0) {
    ....
  } else {
    if (leftvol >= 0) {
      chans[chan].leftVolume = leftvol;
    }
    if (rightvol >= 0) {
      chans[chan].rightVolume = rightvol;
    }
    chans[chan].sampleAddress = samplePtr;
    result = __AudioEnqueue(chans[chan], chan, true);
  }
}

Avertissement PVS-Studio : V547 L'expression 'leftvol >= 0' est toujours vraie. sceAudio.cpp 120

Faites attention à la branche else pour la première fois if. Le code ne sera exécuté que si toutes les conditions vol gauche > 0xFFFF || vol droit > 0xFFFF || vol gauche < 0 || vol droit < 0 se révélera faux. Par conséquent, nous obtenons les déclarations suivantes, qui seront vraies pour la branche else : vol gauche <= 0xFFFF, vol droit <= 0xFFFF, vol gauche >= 0 и vol droit >= 0. Notez les deux dernières déclarations. Est-il judicieux de vérifier quelle est une condition nécessaire à l’exécution de ce morceau de code ?

Nous pouvons donc supprimer en toute sécurité ces instructions conditionnelles :

static u32 sceAudioOutputPannedBlocking
(u32 chan, int leftvol, int rightvol, u32 samplePtr) {
  int result = 0;
  // For some reason, this is the only one that checks for negative.
  if (leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0) {
    ....
  } else {
    chans[chan].leftVolume = leftvol;
    chans[chan].rightVolume = rightvol;

    chans[chan].sampleAddress = samplePtr;
    result = __AudioEnqueue(chans[chan], chan, true);
  }
}

Un autre scénario. Derrière ces conditions redondantes se cache une sorte d’erreur. Peut-être n’ont-ils pas vérifié ce qui était demandé.

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 du '||' opérateur. scePsmf.cpp 703

Faites attention au chèque à l'intérieur if. Ne trouvez-vous pas étrange que nous vérifiions si l'adresse est valide ? psmfData, deux fois plus? Cela me semble donc étrange... En fait, il s'agit bien sûr d'une faute de frappe, et l'idée était de vérifier les deux paramètres d'entrée.

Bonne option :

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");
  }
  ....
}

Avertissement PVS-Studio : V547 L'expression 'size == 8' est toujours fausse. syn-att.c 195

Cette erreur se trouve dans le dossier poste, donc pas vraiment pertinent pour le projet, mais le bug a été trouvé avant que je ne le remarque, j'ai donc décidé de le laisser. Après tout, cet article ne concerne pas l'examen des erreurs, mais l'intégration avec Travis CI, et aucune configuration de l'analyseur n'a été effectuée.

Variable Taille est initialisé par une constante, cependant, il n'est pas du tout utilisé dans le code, jusqu'à l'opérateur if, ce qui donne bien sûr non tout en vérifiant les conditions, car, on s'en souvient, Taille égal à zéro. Les contrôles ultérieurs n’ont également aucun sens.

Apparemment, l'auteur du fragment de code a oublié d'écraser la variable Taille avant ça.

Arrêter

C'est là que nous en finirons probablement avec les erreurs. Le but de cet article est de démontrer le travail de PVS-Studio avec Travis CI, et non d'analyser le projet de manière aussi approfondie que possible. Si vous voulez des erreurs plus grandes et plus belles, vous pouvez toujours les admirer ici :).

Conclusion

L'utilisation de services Web pour créer des projets ainsi que la pratique de l'analyse incrémentielle vous permettent de détecter de nombreux problèmes immédiatement après la fusion du code. Cependant, une seule version peut ne pas suffire, donc la mise en place de tests avec une analyse statique améliorera considérablement la qualité du code.

Liens utiles

Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple d'un émulateur de console de jeu PSP

Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien de traduction : Maxim Zvyagintsev. Comment configurer PVS-Studio dans Travis CI en utilisant l'exemple de l'émulateur de console de jeu PSP.

Source: habr.com

Ajouter un commentaire