PVS-Studio dans les nuages: CircleCI

Image 2

Ceci est une nouvelle partie de notre série d'articles sur l'utilisation de l'analyseur statique PVS-Studio avec des systèmes CI cloud. Aujourd'hui, nous allons examiner un autre service, CircleCI. Nous prendrons l'application Kodi Media Player comme projet de test et verrons si nous pouvons trouver des bogues intéressants dans son code source.
Remarque Les articles précédents sur l'intégration de PVS-Studio avec les systèmes CI cloud:


Avant de mettre en place l'environnement de travail et d'examiner le rapport d'analyse, je voudrais dire quelques mots sur le logiciel que nous allons utiliser et vérifier.

CircleCI est un service cloud CI pour la création, le test et le déploiement de logiciels automatisés. Il prend en charge la création de projets à la fois dans des conteneurs et sur des machines virtuelles sous Windows, Linux et macOS.

Kodi est une application de lecture multimédia multiplateforme gratuite et open-source. Il permet aux utilisateurs de lire et d'afficher la plupart des médias en streaming, tels que des vidéos, de la musique, des podcasts et des vidéos sur Internet, ainsi que tous les fichiers multimédias numériques courants à partir de supports de stockage locaux et réseau. Il prend en charge l'utilisation de thèmes et de skins et d'extensions de fonctionnalités via des plugins. Kodi est disponible pour Windows, Linux, macOS et Android.

PVS-Studio est un analyseur statique pour détecter les bogues et les vulnérabilités potentielles dans le code source des applications écrites en C, C ++, C # et Java. L'analyseur fonctionne sous Windows, Linux et macOS.

Mise en place


Nous devons d'abord aller sur la page principale de CircleCI et cliquer sur "S'inscrire"

Image 1

Sur la page suivante, nous vous proposons d'autoriser avec un compte GitHub ou Bitbucket. Nous choisissons GitHub et accédons à la page d'autorisation CircleCI.

Image 3

Après avoir autorisé l'application (en cliquant sur le bouton vert "Autoriser circleci"), nous sommes redirigés vers le "Bienvenue chez CircleCI!" page:

Image 4

Ici, nous pouvons spécifier immédiatement quels projets nous voulons que CircleCI construise. Nous cochons notre référentiel et cliquez sur "Suivre".

Après avoir ajouté le référentiel, CircleCI démarrera automatiquement le processus de génération, mais comme nous n'avons pas encore de fichier de configuration dans notre référentiel, le travail de génération sera abandonné avec un message d'erreur.

Image 5

Avant d'ajouter un fichier de configuration, nous devons ajouter quelques variables contenant les données de licence de l'analyseur. Pour ce faire, nous cliquons sur "Paramètres" dans la barre latérale gauche, sélectionnons "Projets" dans la section "ORGANISATION" et cliquons sur le bouton à crémaillère à droite du nom de notre projet. Une fenêtre de paramètres apparaît.

Image 6

Nous allons à la page "Variables d'environnement". Ici, nous créons deux variables, PVS_USERNAME et PVS_KEY , qui contiennent le nom d'utilisateur et la clé de licence de l'analyseur.

Image 7

Au démarrage de la génération, CircleCI lit la configuration du travail à partir du fichier stocké dans le référentiel à .circleci / config.yml. Ajoutons-le.

Nous devons d'abord spécifier l'image de la machine virtuelle sur laquelle l'analyseur s'exécutera. La liste complète des images est disponible ici .

version: 2 jobs: build: machine: image: ubuntu-1604:201903-01 

Ensuite, nous ajoutons les référentiels nécessaires à apt et installons les dépendances du projet:

 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget 

Ajout du référentiel PVS-Studio et installation de l'analyseur:

 - run: 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 - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" 

Ensuite, nous construisons les dépendances:

 - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local 

Après cela, nous générons des Makefiles dans le répertoire de construction:

 - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. 

L'étape suivante consiste à configurer et à démarrer une analyse du projet.

Nous créons d'abord un fichier de licence d'analyseur. Une autre commande commencera à tracer la construction du projet par le compilateur.

La commande suivante qui suit le suivi exécute l'analyse en tant que telle. Si vous utilisez une version de démonstration de PVS-Studio, lancez-la avec le paramètre:

--disableLicenseExpirationCheck .

La commande finale convertit le fichier de rapport de l'analyseur en un rapport html:

 - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log 

Une fois les tests terminés, nous enregistrons les rapports:

 - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

Voici le texte complet du fichier .circleci / config.yml:

 version: 2.1 jobs: build: machine: image: ubuntu-1604:201903-01 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget - run: 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 - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

Une fois ce fichier téléchargé dans le référentiel, CircleCI démarrera automatiquement la construction.

Image 12

Une fois le travail terminé, les fichiers contenant les résultats de l'analyse peuvent être téléchargés dans l'onglet "Artefacts".

Image 11

Résultats d'analyse


OK, regardons maintenant certains des avertissements émis par l'analyseur.

Avertissement PVS-Studio: V504 Il est très probable que le point-virgule ';' est manquant après le mot clé "return". AdvancedSettings.cpp: 1476

 void CAdvancedSettings::SetExtraArtwork(const TiXmlElement* arttypes, std::vector<std::string>& artworkMap) { if (!arttypes) return artworkMap.clear(); const TiXmlNode* arttype = arttypes->FirstChild("arttype"); .... } 

La mise en forme du code suggère la logique d'exécution suivante:

  • si arttypes est un pointeur nul, la méthode retourne;
  • si arttypes est un pointeur non nul, le vecteur artworkMap est effacé et certaines actions sont alors effectuées.

Mais les disparus ';' caractère casse tout, et la logique d'exécution réelle est la suivante:

  • si arttypes est un pointeur nul, le vecteur artworkMap est effacé et la méthode retourne;
  • si arttypes est un pointeur non nul, le programme exécute les actions suivantes, mais le vecteur artworkMap n'est pas effacé.

Pour faire court, cette situation ressemble à un bug. Après tout, vous vous attendez à peine à ce que quelqu'un écrive des expressions comme return artworkMap.clear (); :).

Avertissements de PVS-Studio:

  • V547 L' expression 'lastsector' est toujours fausse. udf25.cpp: 636
  • V547 L' expression 'lastsector' est toujours fausse. udf25.cpp: 644
  • V571 Contrôle récurrent. La condition 'if (lastsector)' a déjà été vérifiée à la ligne 636. udf25.cpp: 644

 int udf25::UDFGetAVDP( struct avdp_t *avdp) { .... uint32_t lastsector; .... lastsector = 0; // <= .... for(;;) { .... if( lastsector ) { // <= V547 lbnum = lastsector; terminate = 1; } else { //! @todo Find last sector of the disc (this is optional). if( lastsector ) // <= V547 lbnum = lastsector - 256; else return 0; } } .... } 

Notez les taches marquées // // = . La variable lastsector reçoit la valeur 0, puis est utilisée comme expression conditionnelle dans deux instructions if . Étant donné que la valeur ne change ni dans la boucle ni entre les affectations, le contrôle n'entrera jamais dans les branches else des deux instructions if .

Cependant, cela pourrait également signifier que les développeurs n'ont tout simplement pas encore implémenté la fonctionnalité prévue (notez la remarque à faire).

Soit dit en passant, comme vous l'avez probablement remarqué, cet extrait de code a déclenché trois avertissements à la fois. Mais même si de nombreux avertissements pour un morceau de code ne sembleraient pas suffisamment convaincants pour certains utilisateurs, ils continueraient à croire que l'analyseur est erroné ... Cet aspect est discuté en détail dans un article de l'un de mes coéquipiers: " Un jour à partir de Support utilisateur PVS-Studio ":).

Avertissement PVS-Studio: V547 L' expression 'values.size ()! = 2' est toujours fausse. GUIControlSettings.cpp: 1174

 bool CGUIControlRangeSetting::OnClick() { .... std::vector<CVariant> values; SettingConstPtr listDefintion = settingList->GetDefinition(); switch (listDefintion->GetType()) { case SettingType::Integer: values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorUpper)); break; case SettingType::Number: values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorUpper)); break; default: return false; } if (values.size() != 2) return false; SetValid(CSettingUtils::SetList(settingList, values)); return IsValid(); } 

La vérification values.size ()! = 2 est redondante ici car cette expression conditionnelle sera toujours évaluée à false . En effet, si l'exécution entre dans l'une des branches case de l'instruction switch , deux éléments seront ajoutés au vecteur, et comme il était initialement vide, sa taille deviendra naturellement égale à 2; sinon (c'est-à-dire si la branche par défaut est exécutée), la méthode retournera.

Avertissement PVS-Studio: L' expression V547 'prio == 0x7fffffff' est toujours vraie. DBusReserve.cpp: 57

 bool CDBusReserve::AcquireDevice(const std::string& device) { .... int prio = INT_MAX; .... res = dbus_bus_request_name( m_conn, service.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE | (prio == INT_MAX ? 0 : DBUS_NAME_FLAG_ALLOW_REPLACEMENT), // <= error); .... } 

La variable prio est initialisée à la valeur INT_MAX puis utilisée comme opérande de l'opérateur ternaire dans la comparaison prio == INT_MAX , bien que sa valeur ne change pas après l'initialisation. Cela signifie que l'expression prio == INT_MAX est vraie et que l'opérateur ternaire retournera toujours 0.

Avertissements de PVS-Studio:

  • V575 Le pointeur null potentiel est passé dans la fonction 'memcpy'. Inspectez le premier argument. Vérifiez les lignes: 39, 38. DVDOverlayImage.h: 39
  • V575 Le pointeur null potentiel est passé dans la fonction 'memcpy'. Inspectez le premier argument. Vérifiez les lignes: 44, 43. DVDOverlayImage.h: 44

 CDVDOverlayImage(const CDVDOverlayImage& src) : CDVDOverlay(src) { Data = (uint8_t*)malloc(src.linesize * src.height); memcpy(data, src.data, src.linesize * src.height); // <= if(src.palette) { palette = (uint32_t*)malloc(src.palette_colors * 4); memcpy(palette, src.palette, src.palette_colors * 4); // <= } .... } 

Les deux avertissements ont le même modèle: un pointeur renvoyé par la fonction malloc est utilisé plus loin dans la fonction memcpy sans être d'abord vérifié pour NULL .

Certains peuvent affirmer que malloc ne renverra jamais un pointeur nul, et si c'est le cas, il serait préférable que l'application plante. C'est un sujet de discussion séparé, mais quelle que soit votre opinion, je recommande de lire ce post de mon coéquipier: " Pourquoi il est important de vérifier ce que la fonction malloc a renvoyé ".

Si vous le souhaitez, vous pouvez personnaliser l'analyseur afin qu'il ne suppose pas que malloc pourrait renvoyer un pointeur nul - cela l'empêchera d'émettre ce type d'avertissements. Plus de détails peuvent être trouvés ici .

Avertissement PVS-Studio: V522 Il pourrait y avoir un déréférencement d'une potentielle entrée de pointeur nul. Vérifiez les lignes: 985, 981. emu_msvcrt.cpp: 985

 struct dirent *dll_readdir(DIR *dirp) { .... struct dirent *entry = NULL; entry = (dirent*) malloc(sizeof(*entry)); if (dirData->curr_index < dirData->items.Size() + 2) { if (dirData->curr_index == 0) strncpy(entry->d_name, ".\0", 2); .... } 

Cet exemple est similaire au précédent. Le pointeur renvoyé par la fonction malloc est stocké dans la variable d' entrée , et cette variable est ensuite utilisée sans vérification nulle préalable ( entry-> d_name ).

Avertissement PVS-Studio: V773 La portée de visibilité du pointeur 'progressHandler' a été fermée sans libérer la mémoire. Une fuite de mémoire est possible. PVRGUIChannelIconUpdater.cpp: 94

 void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const { .... CPVRGUIProgressHandler* progressHandler = new CPVRGUIProgressHandler(g_localizeStrings.Get(19286)); for (const auto& group : m_groups) { const std::vector<PVRChannelGroupMember> members = group->GetMembers(); int channelIndex = 0; for (const auto& member : members) { progressHandler->UpdateProgress(member.channel->ChannelName(), channelIndex++, members.size()); .... } progressHandler->DestroyProgress(); } 

La valeur du pointeur progressHandler a été renvoyée par le nouvel opérateur. Mais il n'y a pas d'opérateur de suppression pour ce pointeur. Cela signifie une fuite de mémoire.

Avertissement PVS-Studio: le dépassement de la matrice V557 est possible. L'index 'idx' pointe au-delà de la limite du tableau. PlayerCoreFactory.cpp: 240

 std::vector<CPlayerCoreConfig *> m_vecPlayerConfigs; bool CPlayerCoreFactory::PlaysVideo(const std::string& player) const { CSingleLock lock(m_section); size_t idx = GetPlayerIndex(player); if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size()) return false; return m_vecPlayerConfigs[idx]->m_bPlaysVideo; } 

L'instruction if limite la taille du vecteur m_vecPlayerConfigs à une certaine plage en renvoyant la méthode si la condition de vérification de la taille est vraie. Par conséquent, lorsque l'exécution atteint la dernière instruction de retour , la taille du vecteur m_vecPlayerConfigs sera dans la plage spécifiée, [1; idx]. Mais quelques lignes plus tard, le programme indexe le vecteur à idx : m_vecPlayerConfigs [idx] -> m_bPlaysVideo . Cela signifie que si idx est égal à la taille du vecteur, nous indexerons au-delà de la plage valide.

Terminons cet article avec quelques exemples du code de la bibliothèque Platinum .

Avertissement PVS-Studio: V542 Envisagez d'inspecter un cast de type impair: 'bool' en 'char *'. PltCtrlPoint.cpp: 1617

 NPT_Result PLT_CtrlPoint::ProcessSubscribeResponse(...) { .... bool subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE"); .... NPT_String prefix = NPT_String::Format(" PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for service \"%s\" (result = %d, status code = %d)", (const char*)subscription?"S":"Uns", // <= (const char*)service->GetServiceID(), res, response?response->GetStatusCode():0); .... } 

Les développeurs ont émis des hypothèses erronées sur la priorité des opérations. Ce qui est converti en const char * n'est pas le résultat retourné par l'opérateur ternaire ( abonnement? "S": "Uns" ) mais la variable d' abonnement . Cela semble pour le moins étrange.

Avertissement PVS-Studio: V560 Une partie de l'expression conditionnelle est toujours fausse: c == '\ t'. NptUtils.cpp: 863

 NPT_Result NPT_ParseMimeParameters(....) { .... case NPT_MIME_PARAMETER_PARSER_STATE_NEED_EQUALS: if (c < ' ') return NPT_ERROR_INVALID_SYNTAX; // END or CTLs are invalid if (c == ' ' || c == '\t') continue; // ignore leading whitespace .... } 

Le code du caractère espace est 0x20 et le code du caractère tabulation est 0x09. Par conséquent, la sous-expression c == '\ t' sera toujours évaluée à false car ce cas est déjà couvert par la vérification c <'' (qui, si elle est vraie, provoquera le retour de la fonction).

Conclusion


Comme le montre cet article, nous avons réussi à mettre en place une analyse par PVS-Studio sur un autre système CI (CircleCI). Je vous invite à télécharger et à essayer l'analyseur sur votre propre projet. Si vous avez des questions sur la configuration ou l'utilisation de PVS-Studio, n'hésitez pas à nous contacter - nous serons heureux de vous aider.

Et, bien sûr, nous vous souhaitons un code sans code. :)

Source: https://habr.com/ru/post/fr470465/


All Articles