Travis CI estas distribuita retservo por konstrui kaj testi programaron, kiu uzas GitHub kiel fontkodan gastigadon. Krom ĉi-supraj operaciaj scenaroj, vi povas aldoni viajn proprajn danke al la ampleksaj agordaj elektoj. En ĉi tiu artikolo ni agordos Travis CI por labori kun PVS-Studio uzante la PPSSPP-kodekzemplon.
Enkonduko
Starigante Travis CI
Ni bezonos deponejon en GitHub, kie troviĝas la projekto, kiun ni bezonas, kaj ankaŭ ŝlosilon por PVS-Studio (vi povas akiri
Ni iru al la retejo
Por la testo, mi forkigis PPSSPP.
Ni aktivigas la deponejon, kiun ni volas kolekti:
Nuntempe, Travis CI ne povas konstrui nian projekton ĉar ne ekzistas instrukcioj por konstrui. Do estas tempo por agordo.
Dum la analizo, iuj variabloj estos utilaj al ni, ekzemple, la ŝlosilo por PVS-Studio, kiu estus nedezirinda specifi en la agorda dosiero. Do ni aldonu mediajn variablojn uzante la konstruajn agordojn en Travis CI:
Ni bezonos:
- PVS_USERNAME - uzantnomo
- PVS_KEY - ŝlosilo
- MAIL_USER - retpoŝto, kiu estos uzata por sendi la raporton
- MAIL_PASSWORD - retpoŝta pasvorto
La lastaj du estas laŭvolaj. Ĉi tiuj estos uzataj por sendi rezultojn per poŝto. Se vi volas disdoni la raporton alimaniere, vi ne bezonas indiki ilin.
Do, ni aldonis la mediajn variablojn, kiujn ni bezonas:
Nun ni kreu dosieron .travis.yml kaj metu ĝin ĉe la radiko de la projekto. PPSSPP jam havis agordan dosieron por Travis CI, tamen ĝi estis tro granda kaj tute maltaŭga por la ekzemplo, do ni devis multe simpligi ĝin kaj lasi nur la bazajn elementojn.
Unue, ni indiku la lingvon, la version de Ubuntu Linukso, kiun ni volas uzi en la virtuala maŝino, kaj la necesajn pakaĵojn por la konstruo:
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'
Ĉiuj pakoj kiuj estas listigitaj estas bezonataj ekskluzive por PPSSPP.
Nun ni indikas la asemblematron:
matrix:
include:
- os: linux
compiler: "gcc"
env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes
- os: linux
compiler: "clang"
env: PPSSPP_BUILD_TYPE=Linux
Iom pli pri la sekcio Matrico. En Travis CI, ekzistas du manieroj krei konstruopciojn: la unua estas specifi liston de kompililoj, operaciumaj tipoj, mediovariabloj, ktp., post kiuj matrico de ĉiuj eblaj kombinaĵoj estas generita; la dua estas eksplicita indiko de la matrico. Kompreneble, vi povas kombini ĉi tiujn du alirojn kaj aldoni unikan kazon, aŭ, male, ekskludi ĝin uzante la sekcion ekskludi. Vi povas legi pli pri tio en
Restas nur provizi projekt-specifajn kunveninstrukciojn:
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 permesas vin aldoni viajn proprajn komandojn por diversaj etapoj de la vivo de virtuala maŝino. Sekcio antaŭ_instali efektivigita antaŭ instali pakaĵojn. Tiam instali, kiu sekvas la instaladon de pakaĵoj el la listo aldonaĵoj.aptkiujn ni supre indikis. La asembleo mem okazas en skripto. Se ĉio iris bone, tiam ni trovas nin en post_sukceso (estas en ĉi tiu sekcio, ke ni funkcios statikan analizon). Ĉi tiuj ne estas ĉiuj paŝoj, kiuj povas esti modifitaj, se vi bezonas pli, tiam vi devus enrigardi
Por facileco de legado, la komandoj estis metitaj en apartan skripton .travis.sh, kiu estas metita ĉe la projekta radiko.
Do ni havas la sekvan dosieron .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
Antaŭ ol instali la pakaĵojn, ni ĝisdatigos la submodulojn. Ĉi tio estas necesa por konstrui PPSSPP. Ni aldonu la unuan funkcion al .travis.sh (notu la etendon):
travis_before_install() {
git submodule update --init --recursive
}
Nun ni rekte agordas la aŭtomatan lanĉon de PVS-Studio en Travis CI. Unue ni devas instali la pakaĵon PVS-Studio en la sistemo:
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
}
Komence de la funkcio travis_install ni instalas la kompililojn ni bezonas uzante mediovariablojn. Tiam se la variablo $PVS_ANALYZE stokas valoron Jes (ni indikis ĝin en la sekcio env dum konstrua matrica agordo), ni instalas la pakaĵon pvs-studio. Aldone al tio, pakoj ankaŭ estas indikitaj libio-socket-ssl-perl и libnet-ssleay-perl, tamen, ili estas postulataj por sendi rezultojn, do ili ne estas necesaj se vi elektis alian metodon por liveri vian raporton.
funkcio download_extract elŝutas kaj malpakigas la specifitan arkivon:
download_extract() {
aria2c -x 16 $1 -o $2
tar -xf $2
}
Estas tempo kunmeti la projekton. Ĉi tio okazas en la sekcio skripto:
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
}
Fakte, ĉi tio estas simpligita originala agordo, krom ĉi tiuj linioj:
if [ "$PVS_ANALYZE" = "Yes" ]; then
CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi
En ĉi tiu sekcio de kodo ni starigis cmake flago por eksporti kompilajn komandojn. Ĉi tio estas necesa por statika kodanalizilo. Vi povas legi pli pri tio en la artikolo "
Se la asembleo sukcesis, tiam ni atingas post_sukceso, kie ni faras statikan analizon:
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
}
Ni rigardu pli detale la sekvajn liniojn:
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 unua linio generas licencdosieron de la uzantnomo kaj ŝlosilo, kiujn ni specifis komence dum agordado de la mediovariabloj de Travis CI.
La dua linio komencas la analizon rekte. Flago -j fiksas la nombron da fadenoj por analizo, flago -l indikas licencon, flagon -o difinas la dosieron por eligi protokolojn, kaj la flagon -disableLicenseExpirationCheck bezonata por provversioj, ĉar defaŭlte pvs-studio-analizilo avertos la uzanton, ke la permesilo estas eksvalidiĝos. Por eviti ke tio okazu, vi povas specifi ĉi tiun flagon.
La protokolo-dosiero enhavas krudan produktaĵon, kiu ne povas esti legita sen konvertiĝo, do vi devas unue igi la dosieron legebla. Ni trapasu la ŝtipojn plog-konvertilo, kaj la eligo estas html-dosiero.
En ĉi tiu ekzemplo, mi decidis sendi raportojn per poŝto uzante la komandon sendi retpoŝton.
Kiel rezulto, ni ricevis la sekvan dosieron .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;
Nun estas tempo puŝi la ŝanĝojn al la git-deponejo, post kio Travis CI aŭtomate rulos la konstruon. Alklaku "ppsspp" por iri al la konstruaj raportoj:
Ni vidos superrigardon de la nuna konstruo:
Se la konstruo estas sukcese finita, ni ricevos retpoŝton kun la rezultoj de la statika analizo. Kompreneble, dissendo ne estas la sola maniero ricevi raporton. Vi povas elekti ajnan efektivigmetodon. Sed gravas memori, ke post kiam la konstruo finiĝos, ne eblos aliri la virtualajn maŝinajn dosierojn.
Resumo de eraroj
Ni sukcese plenumis la plej malfacilan parton. Nun ni certigu, ke ĉiuj niaj klopodoj valoras ĝin. Ni rigardu kelkajn interesajn punktojn el la statika analiza raporto, kiu venis al mi poŝte (ne vane mi indikis ĝin).
Danĝera optimumigo
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 ) );
}
Averto de PVS-Studio:
Ĉi tiu kodo troviĝas en la sekura haĉa modulo, tamen ĝi enhavas gravan sekurecan difekton (
; Line 355
mov r8d, 20
xor edx, edx
lea rcx, QWORD PTR sum$[rsp]
call memset
; Line 356
Ĉio estas en ordo kaj la funkcio memeset estas ekzekutita, tiel anstataŭigante gravajn datumojn en RAM, tamen ne ĝoju ankoraŭ. Ni rigardu la kunigliston de la Eldona versio kun optimumigo:
; 354 :
; 355 : memset( sum, 0, sizeof( sum ) );
; 356 :}
Kiel videblas el la listo, la kompililo ignoris la vokon memeset. Ĉi tio estas pro la fakto, ke en la funkcio sha1 post la voko memeset ne plu referenco al strukturo CTX. Tial, la kompililo vidas nenian signifon malŝpari procesoran tempon anstataŭante memoron kiu ne estas uzita en la estonteco. Vi povas ripari ĉi tion uzante la funkcion RtlSecureZeroMemory aŭ
Ĝuste:
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 ) );
}
Nenecesa komparo
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);
}
}
Averto de PVS-Studio:
Atentu la alian branĉon por la unua if. La kodo estos ekzekutita nur se ĉiuj kondiĉoj leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || dekstravol < 0 montriĝos malvera. Tial, ni ricevas la sekvajn deklarojn, kiuj estos veraj por la else branĉo: maldekstravol <= 0xFFFF, rightvol <= 0xFFFF, leftvol >= 0 и rightvol >= 0. Rimarku la lastajn du deklarojn. Ĉu havas sencon kontroli kio estas necesa kondiĉo por la ekzekuto de ĉi tiu kodo?
Do ni povas sekure forigi ĉi tiujn kondiĉajn deklarojn:
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);
}
}
Alia scenaro. Estas ia eraro kaŝita malantaŭ ĉi tiuj superfluaj kondiĉoj. Eble ili ne kontrolis tion, kio estas postulata.
Ctrl+C Ctrl+V Refrakas
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfData) ||
!Memory::IsValidAddress(psmfData)) {
return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}
....
}
Atentu la ĉekon enen if. Ĉu vi ne pensas, ke estas strange, ke ni kontrolas ĉu la adreso validas? psmfData, duoble pli? Do ĉi tio ŝajnas al mi stranga... Fakte, ĉi tio estas, kompreneble, tajperaro, kaj la ideo estis kontroli ambaŭ enigajn parametrojn.
Ĝusta opcio:
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfStruct) ||
!Memory::IsValidAddress(psmfData)) {
return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}
....
}
Forgesita variablo
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");
}
....
}
Averto de PVS-Studio:
Ĉi tiu eraro troviĝas en la dosierujo ext, do ne vere rilata al la projekto, sed la cimo estis trovita antaŭ ol mi rimarkis ĝin, do mi decidis forlasi ĝin. Post ĉio, ĉi tiu artikolo ne temas pri reviziado de eraroj, sed pri integriĝo kun Travis CI, kaj neniu agordo de la analizilo estis efektivigita.
Variablo grandeco estas pravigita per konstanto, tamen ĝi tute ne estas uzata en la kodo, ĝuste ĝis la funkciigisto if, kiu, kompreneble, donas falsa dum kontrolado de la kondiĉoj, ĉar, kiel ni memoras, grandeco egala al nulo. Postaj kontroloj ankaŭ ne havas sencon.
Ŝajne, la aŭtoro de la kodfragmento forgesis anstataŭigi la variablon grandeco antaŭ tio.
ĉesigi
Ĉi tie ni verŝajne finos kun la eraroj. La celo de ĉi tiu artikolo estas pruvi la laboron de PVS-Studio kune kun Travis CI, kaj ne analizi la projekton kiel eble plej detale. Se vi volas pli grandajn kaj pli belajn erarojn, vi ĉiam povas admiri ilin
konkludo
Uzado de retservoj por konstrui projektojn kune kun la praktiko de pliiga analizo permesas trovi multajn problemojn tuj post kunfandado de kodo. Tamen, unu konstruo eble ne sufiĉas, do starigi testadon kune kun statika analizo signife plibonigos la kvaliton de la kodo.
utilaj ligoj
Se vi volas dividi ĉi tiun artikolon kun anglalingva publiko, bonvolu uzi la tradukan ligilon: Maxim Zvyagintsev.
fonto: www.habr.com