Kako nastaviti PVS-Studio v Travis CI z uporabo emulatorja PSP kot primera

Kako nastaviti PVS-Studio v Travis CI z uporabo emulatorja PSP kot primera
Travis CI je distribuirana spletna storitev za gradnjo in testiranje programske opreme, ki uporablja GitHub kot gostovanje izvorne kode. Poleg zgornjih scenarijev delovanja lahko dodate svojega, zahvaljujoč obsežnim konfiguracijskim možnostim. V tem članku bomo Travis CI konfigurirali za delo s PVS-Studio z uporabo primera kode PPSSPP.

Predstavitev

Travis CI je spletna storitev za gradnjo in testiranje programske opreme. Običajno se uporablja v povezavi s praksami stalne integracije.

PPSSPP — Emulator igralne konzole PSP. Program lahko posnema zagon katere koli igre s slik diskov, namenjenih Sony PSP. Program je bil izdan 1. novembra 2012. PPSSPP je licenciran pod GPL v2. Vsakdo lahko izboljša izvorna koda projekta.

PVS-Studio — statični analizator kode za iskanje napak in potencialnih ranljivosti v programski kodi. V tem članku bomo za spremembo zagnali PVS-Studio ne lokalno na stroju razvijalca, ampak v oblaku, in iskali napake v PPSSPP.

Nastavitev Travis CI

Potrebovali bomo repozitorij na GitHubu, kjer se nahaja projekt, ki ga potrebujemo, ter ključ za PVS-Studio (lahko dobite poskusni ključ ali brezplačno za odprtokodne projekte).

Pojdimo na stran Travis CI. Po avtorizaciji z vašim računom GitHub bomo videli seznam repozitorijev:

Kako nastaviti PVS-Studio v Travis CI z uporabo emulatorja PSP kot primera
Za test sem razcepil PPSSPP.

Aktiviramo repozitorij, ki ga želimo zbrati:

Kako nastaviti PVS-Studio v Travis CI z uporabo emulatorja PSP kot primera
Trenutno Travis CI ne more zgraditi našega projekta, ker ni navodil za gradnjo. Torej je čas za konfiguracijo.

Med analizo nam bodo koristne nekatere spremenljivke, na primer ključ za PVS-Studio, ki ga ne bi bilo zaželeno navesti v konfiguracijski datoteki. Zato dodajmo spremenljivke okolja z uporabo nastavitev gradnje v Travis CI:

Kako nastaviti PVS-Studio v Travis CI z uporabo emulatorja PSP kot primera
Potrebovali bomo:

  • PVS_USERNAME - uporabniško ime
  • PVS_KEY - ključ
  • MAIL_USER - e-pošta, ki bo uporabljena za pošiljanje poročila
  • MAIL_PASSWORD - geslo elektronske pošte

Zadnja dva sta neobvezna. Ti bodo uporabljeni za pošiljanje rezultatov po pošti. Če želite poročilo distribuirati na drug način, vam jih ni treba navesti.

Torej, dodali smo spremenljivke okolja, ki jih potrebujemo:

Kako nastaviti PVS-Studio v Travis CI z uporabo emulatorja PSP kot primera
Sedaj pa ustvarimo datoteko .travis.yml in ga postavite v koren projekta. PPSSPP je že imel konfiguracijsko datoteko za Travis CI, vendar je bila prevelika in popolnoma neprimerna za primer, zato smo jo morali močno poenostaviti in pustiti le osnovne elemente.

Najprej označimo jezik, različico Ubuntu Linuxa, ki jo želimo uporabiti v virtualnem stroju, in potrebne pakete za gradnjo:

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'

Vsi navedeni paketi so potrebni izključno za PPSSPP.

Zdaj navedemo montažno matriko:

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

Še malo o rubriki matrica. V Travis CI obstajata dva načina za ustvarjanje možnosti gradnje: prvi je podajanje seznama prevajalnikov, vrst operacijskega sistema, spremenljivk okolja itd., nakar se ustvari matrika vseh možnih kombinacij; drugi je izrecna navedba matrike. Seveda lahko združite ta dva pristopa in dodate edinstven primer ali pa ga, nasprotno, izključite z uporabo razdelka izključujejo. Več o tem si lahko preberete v Dokumentacija Travis CI.

Vse, kar ostane, je zagotoviti navodila za montažo, specifična za projekt:

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 vam omogoča dodajanje lastnih ukazov za različne faze življenja virtualnega stroja. Razdelek pred_namestitvijo izvede pred namestitvijo paketov. Potem namestitev, ki sledi namestitvi paketov s seznama addons.aptki smo jih navedli zgoraj. Sama montaža poteka v script. Če je šlo vse po sreči, potem se znajdemo v po_uspehu (v tem razdelku bomo izvajali statično analizo). To niso vsi koraki, ki jih je mogoče spremeniti, če jih potrebujete več, si oglejte Dokumentacija Travis CI.

Zaradi lažjega branja so bili ukazi umeščeni v ločen skript .travis.sh, ki je postavljen v koren projekta.

Tako imamo naslednjo datoteko .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

Pred namestitvijo paketov bomo posodobili podmodule. To je potrebno za izdelavo PPSSPP. Dodajmo prvo funkcijo .travis.sh (upoštevajte razširitev):

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

Zdaj pridemo neposredno do nastavitve samodejnega zagona PVS-Studio v Travis CI. Najprej moramo v sistem namestiti paket PVS-Studio:

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
}

Na začetku funkcije travis_install namestimo prevajalnike, ki jih potrebujemo, z uporabo spremenljivk okolja. Potem, če spremenljivka $PVS_ANALYZE hrani vrednost Da (navedli smo ga v razdelku env med konfiguracijo matrike gradnje), namestimo paket pvs-studio. Poleg tega so navedeni tudi paketi libio-socket-ssl-perl и libnet-ssleay-perl, vendar so potrebni za pošiljanje rezultatov po pošti, zato niso potrebni, če ste za dostavo poročila izbrali drug način.

Funkcija download_extract prenese in razpakira navedeni arhiv:

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

Čas je, da sestavimo projekt. To se zgodi v razdelku 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
}

Pravzaprav je to poenostavljena izvirna konfiguracija, razen teh vrstic:

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

V tem delu kode, ki smo ga določili za cmake zastavica za izvoz ukazov prevajanja. To je potrebno za statični analizator kode. Več o tem si lahko preberete v članku “Kako zagnati PVS-Studio v sistemih Linux in macOS".

Če je bila montaža uspešna, potem pridemo do po_uspehu, kjer izvajamo statične analize:

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
}

Oglejmo si podrobneje naslednje vrstice:

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

Prva vrstica generira licenčno datoteko iz uporabniškega imena in ključa, ki smo ju določili na samem začetku pri nastavljanju okoljskih spremenljivk Travis CI.

Druga vrstica neposredno začne analizo. Zastava -j nastavi število niti za analizo, zastavica -l označuje licenco, zastavo -o definira datoteko za izpis dnevnikov in zastavico -disableLicenseExpirationCheck potrebno za preizkusne različice, saj je privzeto pvs-studio-analizator bo uporabnika opozoril, da bo licenca kmalu potekla. Če želite preprečiti, da bi se to zgodilo, lahko določite to zastavico.

Datoteka dnevnika vsebuje neobdelane rezultate, ki jih ni mogoče prebrati brez pretvorbe, zato morate datoteko najprej narediti berljivo. Podajmo polena skozi plog-pretvornik, rezultat pa je datoteka html.

V tem primeru sem se odločil za pošiljanje poročil po pošti z ukazom Pošlji sporočilo.

Kot rezultat smo dobili naslednjo datoteko .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;

Zdaj je čas, da potisnete spremembe v repozitorij git, nato pa bo Travis CI samodejno zagnal gradnjo. Kliknite »ppsspp«, da odprete poročila o gradnji:

Kako nastaviti PVS-Studio v Travis CI z uporabo emulatorja PSP kot primera
Videli bomo pregled trenutne zgradbe:

Kako nastaviti PVS-Studio v Travis CI z uporabo emulatorja PSP kot primera
Če je gradnja uspešno zaključena, bomo prejeli e-pošto z rezultati statične analize. Seveda pošiljanje po pošti ni edini način prejema poročila. Izberete lahko kateri koli način izvedbe. Pomembno pa si je zapomniti, da po končani gradnji ne bo več mogoče dostopati do datotek navideznega stroja.

Povzetek napak

Najtežji del smo uspešno opravili. Zdaj pa se prepričajmo, da je ves naš trud vreden. Oglejmo si nekaj zanimivih točk iz poročila o statični analizi, ki sem ga prejel po pošti (ni zaman, da sem ga navedel).

Nevarna optimizacija

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

Opozorilo PVS-Studio: V597 Prevajalnik bi lahko izbrisal klic funkcije 'memset', ki se uporablja za izpiranje medpomnilnika 'sum'. Za brisanje zasebnih podatkov je treba uporabiti funkcijo RtlSecureZeroMemory(). sha1.cpp 325

Ta del kode se nahaja v modulu varnega zgoščevanja, vendar vsebuje resno varnostno napako (CWE-14). Oglejmo si seznam sestavov, ki se ustvari pri prevajanju različice za odpravljanje napak:

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

Vse je v redu in deluje memeset se izvede in s tem prepiše pomembne podatke v RAM, vendar se še ne veselite. Poglejmo seznam sestavov različice Release z optimizacijo:

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

Kot je razvidno iz seznama, je prevajalnik ignoriral klic memeset. To je posledica dejstva, da v funkciji sha1 po klicu memeset nič več sklicevanja na strukturo ctx. Zato prevajalnik ne vidi smisla v izgubljanju procesorskega časa s prepisovanjem pomnilnika, ki v prihodnosti ne bo uporabljen. To lahko popravite s funkcijo RtlSecureZeroMemory ali podobno njej.

Pravilno:

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

Nepotrebna primerjava

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

Opozorilo PVS-Studio: V547 Izraz 'leftvol >= 0' je vedno resničen. sceAudio.cpp 120

Najprej bodite pozorni na vejo else if. Koda bo izvedena le, če bodo izpolnjeni vsi pogoji levi vol > 0xFFFF || desni vol > 0xFFFF || levi vol < 0 || desni vol < 0 se bo izkazalo za lažno. Zato dobimo naslednje izjave, ki bodo veljale za vejo else: levi vol <= 0xFFFF, desni vol <= 0xFFFF, levi vol >= 0 и desni vol >= 0. Bodite pozorni na zadnji dve izjavi. Ali je smiselno preveriti, kaj je nujen pogoj za izvedbo te kode?

Tako lahko varno odstranimo te pogojne izjave:

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

Še en scenarij. Za temi odvečnimi pogoji se skriva nekakšna napaka. Morda niso preverili zahtevanega.

Ctrl+C Ctrl+V vrača udarec

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 Obstajata enaka podizraza '!Memory::IsValidAddress(psmfData)' levo in desno od '||' operater. scePsmf.cpp 703

Bodite pozorni na ček v notranjosti if. Se vam ne zdi čudno, da preverjamo, ali je naslov veljaven? psmfData, dvakrat toliko? Tako da se mi to zdi nenavadno... Pravzaprav je to seveda tipkarska napaka in ideja je bila preveriti oba vhodna parametra.

Pravilna možnost:

static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
  if (!Memory::IsValidAddress(psmfStruct) ||
      !Memory::IsValidAddress(psmfData)) {
    return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
  }
  ....
}

Pozabljena spremenljivka

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

Opozorilo PVS-Studio: V547 Izraz 'size == 8' je vedno napačen. syn-att.c 195

Ta napaka se nahaja v mapi ext, tako da ni res pomembno za projekt, vendar je bil hrošč najden, preden sem ga opazil, zato sem se odločil, da ga zapustim. Navsezadnje ta članek ne govori o pregledovanju napak, temveč o integraciji s Travis CI, in konfiguracija analizatorja ni bila izvedena.

Spremenljivka velikost se inicializira s konstanto, vendar se v kodi sploh ne uporablja, vse do operatorja if, ki seveda daje false med preverjanjem pogojev, saj, kot se spomnimo, velikost enako nič. Naknadna preverjanja tudi nimajo smisla.

Očitno je avtor fragmenta kode pozabil prepisati spremenljivko velikost pred tem.

stop

Tukaj bomo verjetno končali z napakami. Namen tega članka je prikazati delo PVS-Studio skupaj s Travis CI in ne čim bolj temeljito analizirati projekt. Če želite večje in lepše napake, jih lahko vedno občudujete tukaj :).

Zaključek

Uporaba spletnih storitev za gradnjo projektov skupaj s prakso inkrementalne analize vam omogoča, da najdete številne težave takoj po združitvi kode. Vendar ena zgradba morda ne bo dovolj, zato bo nastavitev testiranja skupaj s statično analizo bistveno izboljšala kakovost kode.

Uporabne povezave

Kako nastaviti PVS-Studio v Travis CI z uporabo emulatorja PSP kot primera

Če želite ta članek deliti z angleško govorečim občinstvom, uporabite povezavo za prevod: Maxim Zvyagintsev. Kako nastaviti PVS-Studio v Travis CI na primeru emulatorja igralne konzole PSP.

Vir: www.habr.com

Dodaj komentar