Com configurar PVS-Studio a Travis CI utilitzant l'emulador PSP com a exemple

Com configurar PVS-Studio a Travis CI utilitzant l'emulador PSP com a exemple
Travis CI és un servei web distribuït per crear i provar programari que utilitza GitHub com a allotjament de codi font. A més dels escenaris operatius anteriors, podeu afegir els vostres propis gràcies a les àmplies opcions de configuració. En aquest article configurarem Travis CI perquè funcioni amb PVS-Studio utilitzant l'exemple de codi PPSSPP.

Introducció

Travis C.I. és un servei web per crear i provar programari. Normalment s'utilitza conjuntament amb pràctiques d'integració contínua.

PPSSPP — Emulador de consola de jocs PSP. El programa és capaç d'emular el llançament de qualsevol joc a partir d'imatges de disc destinades a Sony PSP. El programa es va publicar l'1 de novembre de 2012. PPSSPP té llicència sota GPL v2. Qualsevol pot fer-hi millores codi font del projecte.

Estudi PVS — un analitzador de codi estàtic per cercar errors i vulnerabilitats potencials en el codi del programa. En aquest article, per variar, llançarem PVS-Studio no localment a la màquina del desenvolupador, sinó al núvol, i buscarem errors a PPSSPP.

Configuració de Travis CI

Necessitarem un repositori a GitHub, on es troba el projecte que necessitem, així com una clau per a PVS-Studio (podeu obtenir clau de prova o gratuït per a projectes de codi obert).

Anem al lloc Travis C.I.. Després de l'autorització utilitzant el vostre compte de GitHub, veurem una llista de repositoris:

Com configurar PVS-Studio a Travis CI utilitzant l'emulador PSP com a exemple
Per a la prova, vaig bifurcar PPSSPP.

Activem el repositori que volem recollir:

Com configurar PVS-Studio a Travis CI utilitzant l'emulador PSP com a exemple
De moment, Travis CI no pot construir el nostre projecte perquè no hi ha instruccions per construir. Així que és el moment de la configuració.

Durant l'anàlisi, algunes variables ens seran útils, per exemple, la clau de PVS-Studio, que no seria desitjable especificar en el fitxer de configuració. Per tant, afegim variables d'entorn mitjançant la configuració de compilació de Travis CI:

Com configurar PVS-Studio a Travis CI utilitzant l'emulador PSP com a exemple
necessitem:

  • PVS_USERNAME - nom d'usuari
  • PVS_KEY - clau
  • MAIL_USER: correu electrònic que s'utilitzarà per enviar l'informe
  • MAIL_PASSWORD: contrasenya de correu electrònic

Els dos últims són opcionals. Aquests s'utilitzaran per enviar els resultats per correu. Si voleu distribuir l'informe d'una altra manera, no cal que els indiqueu.

Per tant, hem afegit les variables d'entorn que necessitem:

Com configurar PVS-Studio a Travis CI utilitzant l'emulador PSP com a exemple
Ara anem a crear un fitxer .travis.yml i situar-lo a l'arrel del projecte. PPSSPP ja tenia un fitxer de configuració per a Travis CI, però, era massa gran i totalment inadequat per a l'exemple, així que vam haver de simplificar-lo molt i deixar només els elements bàsics.

En primer lloc, indiquem l'idioma, la versió d'Ubuntu Linux que volem utilitzar a la màquina virtual i els paquets necessaris per a la compilació:

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'

Tots els paquets que es mostren són necessaris exclusivament per PPSSPP.

Ara indiquem la matriu de muntatge:

matrix:
  include:
    - os: linux
      compiler: "gcc"
      env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes
    - os: linux
      compiler: "clang"
      env: PPSSPP_BUILD_TYPE=Linux

Una mica més sobre la secció matriu. A Travis CI, hi ha dues maneres de crear opcions de compilació: la primera és especificar una llista de compiladors, tipus de sistemes operatius, variables d'entorn, etc., després de la qual es genera una matriu de totes les combinacions possibles; el segon és una indicació explícita de la matriu. Per descomptat, podeu combinar aquests dos enfocaments i afegir un cas únic o, per contra, excloure-lo mitjançant la secció excloure. Podeu llegir més sobre això a Documentació de Travis CI.

Només queda proporcionar les instruccions de muntatge específiques del projecte:

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 us permet afegir les vostres pròpies ordres per a diverses etapes de la vida d'una màquina virtual. Secció abans_instal·lar executat abans d'instal·lar paquets. Aleshores instal · lar, que segueix la instal·lació de paquets de la llista complements.aptque hem indicat més amunt. L'assemblea pròpiament dita té lloc a script. Si tot ha anat bé, ens hi trobem després de l'èxit (és en aquesta secció on realitzarem l'anàlisi estàtica). Aquests no són tots els passos que es poden modificar, si en necessiteu més, hauríeu de mirar Documentació de Travis CI.

Per facilitar la lectura, les ordres es van col·locar en un script separat .travis.sh, que es col·loca a l'arrel del projecte.

Així que tenim el següent fitxer .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

Abans d'instal·lar els paquets, actualitzarem els submòduls. Això és necessari per construir PPSSPP. Afegim la primera funció a .travis.sh (tingueu en compte l'extensió):

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

Ara anem directament a configurar el llançament automàtic de PVS-Studio a Travis CI. Primer hem d'instal·lar el paquet PVS-Studio al sistema:

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
}

A l'inici de la funció travis_install instal·lem els compiladors que necessitem utilitzant variables d'entorn. Aleshores si la variable $PVS_ANALYZE emmagatzema valor (ho vam indicar a l'apartat env durant la configuració de la matriu de compilació), instal·lem el paquet pvs-estudi. A més d'això, també s'indiquen els paquets libio-socket-ssl-perl и libnet-ssleay-perl, però, són necessaris per enviar els resultats, de manera que no són necessaris si heu triat un altre mètode per enviar l'informe.

Funció descàrrega_extracte descarrega i desempaqueta l'arxiu especificat:

download_extract() {
  aria2c -x 16 $1 -o $2
  tar -xf $2
}

És hora de muntar el projecte. Això passa a la secció 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
}

De fet, es tracta d'una configuració original simplificada, llevat d'aquestes línies:

if [ "$PVS_ANALYZE" = "Yes" ]; then
  CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi

En aquesta secció de codi que hem establert cmake marca per exportar ordres de compilació. Això és necessari per a un analitzador de codi estàtic. Podeu llegir més sobre això a l'article "Com executar PVS-Studio a Linux i macOS".

Si el muntatge ha tingut èxit, ho farem després de l'èxit, on realitzem anàlisi estàtica:

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
}

Mirem més de prop les línies següents:

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 primera línia genera un fitxer de llicència a partir del nom d'usuari i la clau que vam especificar al principi quan vam configurar les variables d'entorn de Travis CI.

La segona línia comença directament l'anàlisi. Bandera -j estableix el nombre de fils per a l'anàlisi, marca -l indica llicència, bandera -o defineix el fitxer per a la sortida de registres i el senyalador -disableLicenseExpirationCheck necessària per a les versions de prova, ja que per defecte pvs-studio-analitzador advertirà a l'usuari que la llicència està a punt de caducar. Per evitar que això passi, podeu especificar aquesta marca.

El fitxer de registre conté una sortida en brut que no es pot llegir sense conversió, de manera que primer heu de fer que el fitxer sigui llegible. Passem els troncs Plog-conversor, i la sortida és un fitxer html.

En aquest exemple, vaig decidir enviar informes per correu mitjançant l'ordre Envia un correu electrònic.

Com a resultat, hem obtingut el següent fitxer .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;

Ara és el moment d'impulsar els canvis al repositori git, després del qual Travis CI executarà automàticament la compilació. Feu clic a "ppsspp" per anar als informes de compilació:

Com configurar PVS-Studio a Travis CI utilitzant l'emulador PSP com a exemple
Veurem una visió general de la construcció actual:

Com configurar PVS-Studio a Travis CI utilitzant l'emulador PSP com a exemple
Si la compilació s'ha completat correctament, rebrem un correu electrònic amb els resultats de l'anàlisi estàtica. Per descomptat, l'enviament per correu no és l'única manera de rebre un informe. Podeu triar qualsevol mètode d'implementació. Però és important recordar que un cop finalitzada la compilació, no serà possible accedir als fitxers de la màquina virtual.

Resum d'errors

Hem completat amb èxit la part més difícil. Ara assegurem-nos que tots els nostres esforços valen la pena. Vegem alguns punts interessants de l'informe d'anàlisi estàtica que em va arribar per correu (no va ser debada que ho vaig indicar).

Optimització perillosa

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

Advertència de PVS-Studio: V597 El compilador podria suprimir la crida a la funció "memset", que s'utilitza per esborrar el buffer "sum". La funció RtlSecureZeroMemory() s'ha d'utilitzar per esborrar les dades privades. sha1.cpp 325

Aquest fragment de codi es troba al mòdul de hashing segur, però conté un greu error de seguretat (CWE-14). Vegem la llista de muntatge que es genera en compilar la versió de depuració:

; Line 355
  mov r8d, 20
  xor edx, edx
  lea rcx, QWORD PTR sum$[rsp]
  call memset
; Line 356

Tot està en ordre i la funció memset s'executa, sobreescriure així dades importants a la memòria RAM, però, encara no us alegreu. Vegem la llista de muntatge de la versió de llançament amb optimització:

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

Com es pot veure a la llista, el compilador va ignorar la trucada memset. Això es deu al fet que en la funció sha1 després de la trucada memset no hi ha més referència a l'estructura ctx. Per tant, el compilador no veu cap sentit en perdre el temps del processador sobreescriure la memòria que no s'utilitzarà en el futur. Podeu solucionar-ho mitjançant la funció RtlSecureZeroMemory o similar a ella.

Dret:

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

Comparació innecessària

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

Advertència de PVS-Studio: V547 L'expressió 'leftvol >= 0' sempre és certa. sceAudio.cpp 120

Pareu atenció a la branca altra per a la primera if. El codi s'executarà només si es compleixen les condicions leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || dretvol < 0 resultarà fals. Per tant, obtenim les següents afirmacions, que seran certes per a la branca else: leftvol <= 0xFFFF, rightvol <= 0xFFFF, leftvol >= 0 и dretvol >= 0. Observeu les dues últimes afirmacions. Té sentit comprovar quina és una condició necessària per a l'execució d'aquest fragment de codi?

Així que podem eliminar amb seguretat aquestes declaracions condicionals:

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 altre escenari. Hi ha algun tipus d'error amagat darrere d'aquestes condicions redundants. Potser no van comprovar el que calia.

Ctrl+C Ctrl+V contraataca

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 Hi ha subexpressions idèntiques '!Memory::IsValidAddress(psmfData)' a l'esquerra i a la dreta del '||' operador. scePsmf.cpp 703

Preste atenció al xec dins if. No us sembla estrany que comprovem si l'adreça és vàlida? psmfData, el doble? Així que això em sembla estrany... De fet, això és, per descomptat, una errada d'ortografia, i la idea era comprovar els dos paràmetres d'entrada.

Opció correcta:

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 oblidada

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

Advertència de PVS-Studio: V547 L'expressió "mida == 8" sempre és falsa. syn-att.c 195

Aquest error es troba a la carpeta ext, així que no és realment rellevant per al projecte, però l'error es va trobar abans que me n'adonés, així que vaig decidir deixar-lo. Al cap i a la fi, aquest article no tracta de revisar els errors, sinó de la integració amb Travis CI, i no es va dur a terme cap configuració de l'analitzador.

Variable mida s'inicializa amb una constant, però no s'utilitza en absolut al codi, fins a l'operador if, que, per descomptat, dóna false tot comprovant les condicions, perquè, com recordem, mida igual a zero. Les comprovacions posteriors tampoc tenen sentit.

Pel que sembla, l'autor del fragment de codi es va oblidar de sobreescriure la variable mida abans que.

Stop

Aquí és on probablement acabarem amb els errors. L'objectiu d'aquest article és demostrar el treball de PVS-Studio juntament amb Travis CI, i no analitzar el projecte el més a fons possible. Si vols errors més grans i bells, sempre pots admirar-los aquí :).

Conclusió

L'ús de serveis web per crear projectes juntament amb la pràctica de l'anàlisi incremental us permet trobar molts problemes immediatament després de combinar el codi. Tanmateix, pot ser que una compilació no sigui suficient, de manera que configurar les proves juntament amb l'anàlisi estàtica millorarà significativament la qualitat del codi.

links útils

Com configurar PVS-Studio a Travis CI utilitzant l'emulador PSP com a exemple

Si voleu compartir aquest article amb un públic de parla anglesa, utilitzeu l'enllaç de traducció: Maxim Zvyagintsev. Com configurar PVS-Studio a Travis CI utilitzant l'exemple de l'emulador de consola de jocs PSP.

Font: www.habr.com

Afegeix comentari