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
Nastavitev Travis CI
Potrebovali bomo repozitorij na GitHubu, kjer se nahaja projekt, ki ga potrebujemo, ter ključ za PVS-Studio (lahko dobite
Pojdimo na stran
Za test sem razcepil PPSSPP.
Aktiviramo repozitorij, ki ga želimo zbrati:
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:
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:
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
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
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 “
Č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:
Videli bomo pregled trenutne zgradbe:
Č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:
Ta del kode se nahaja v modulu varnega zgoščevanja, vendar vsebuje resno varnostno napako (
; 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
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:
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");
}
....
}
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:
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
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
Če želite ta članek deliti z angleško govorečim občinstvom, uporabite povezavo za prevod: Maxim Zvyagintsev.
Vir: www.habr.com