Travis CI — размеркаваны вэб-сэрвіс для зборкі і тэсціравання праграмнага забеспячэння, які выкарыстоўвае GitHub у якасці хостынгу зыходнага кода. Апроч паказаных вышэй сцэнараў працы, можна дадаць уласныя, дзякуючы шырокім магчымасцям для канфігурацыі. У дадзеным артыкуле мы наладзім Travis CI для працы з PVS-Studio на прыкладзе кода PPSSPP.
Увядзенне
Настройка Travis CI
Нам спатрэбіцца рэпазітар на GitHub, дзе ляжыць патрэбны нам праект, а таксама ключ для PVS-Studio (можаце атрымаць
Пяройдзем на сайт
Для тэсту я зрабіў форк PPSSPP.
Актывуем рэпазітар, які хочам збіраць:
На дадзены момант Travis CI не можа сабраць наш праект, бо няма інструкцый для зборкі. Таму надышоў час для канфігурацыі.
Падчас аналізу нам спатрэбяцца некаторыя зменныя, напрыклад, ключ для PVS-Studio, якія было б непажадана ўказваць у файле канфігурацыі. Так што дадамо зменныя асяроддзі пры дапамозе налады зборкі ў Travis CI:
Нам спатрэбяцца:
- PVS_USERNAME - імя карыстальніка
- PVS_KEY - ключ
- MAIL_USER - email, які будзе выкарыстаны для адпраўкі справаздачы
- MAIL_PASSWORD - пароль ад email
Апошнія дзве неабавязковыя. Яны будуць выкарыстоўвацца для адпраўкі вынікаў па пошце. Калі вы хочаце разаслаць справаздачу іншым спосабам, то не трэба іх указваць.
Такім чынам, мы дадалі патрэбныя нам зменныя асяроддзі:
Цяпер створым файл .travis.yml і змесцім яго ў корань праекта. У PPSSPP ужо існаваў файл канфігурацыі для Travis CI, аднак, ён быў занадта вялікі і зусім не падыходзіў для прыкладу, таму прыйшлося значна яго спрасціць і пакінуць толькі асноўныя элементы.
Спачатку пакажам мову, версію Ubuntu Linux, якую мы жадаем выкарыстоўваць у віртуальнай машыне, і неабходныя пакеты для зборкі:
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'
Усе пакеты, якія пазначаны, патрэбны выключна для PPSSPP.
Цяпер пакажам матрыцу зборак:
matrix:
include:
- os: linux
compiler: "gcc"
env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes
- os: linux
compiler: "clang"
env: PPSSPP_BUILD_TYPE=Linux
Трохі падрабязней пра секцыю матрыца. У Travis CI існуе два спосабу для стварэння варыянтаў зборкі: першы - паказаць спісам кампілятары, тыпы аперацыйных сістэм, зменныя асяроддзі і г.д, пасля чаго згенеруецца матрыца ўсіх магчымых камбінацый; другі - відавочнае ўказанне матрыцы. Зразумела, можна камбінаваць гэтыя два падыходы і дадаць унікальны выпадак, ці ж, наадварот, выключыць пры дапамозе секцыі. выключаць. Падрабязней пра гэта можна пачытаць у
Засталося ўказаць спецыфічныя для праекта інструкцыі па зборцы:
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 дазваляе дадаць свае каманды для розных этапаў жыцця віртуальнай машыны. Секцыя before_install выконваецца перад усталёўкай пакетаў. Затым ўсталёўваць, якая ідзе за ўстаноўкай пакетаў з спісу addons.apt, які мы ўказалі вышэй. Сама зборка адбываецца ў скрыпт. Калі ўсё прайшло паспяхова, то мы трапляем у after_success (менавіта ў гэтай секцыі мы і будзем запускаць статычны аналіз). Гэта не ўсе этапы, якія можна мадыфікаваць, калі трэба больш, то варта пашукаць у
Для зручнасці чытання каманды былі вынесены ў асобны скрыпт. .travis.sh, які змешчаны ў корань праекта.
Такім чынам, мы маем наступны файл .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
Перад усталёўкай пакетаў абновім падмодулі. Гэта неабходна для зборкі PPSSPP. Дадамо першую функцыю ў .travis.sh (звярніце ўвагу на пашырэнне):
travis_before_install() {
git submodule update --init --recursive
}
Цяпер мы падышлі непасрэдна да наладзе аўтаматычнага запуску PVS-Studio ў Travis CI. Перш нам трэба ўсталяваць пакет 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
}
У пачатку функцыі travis_install мы ўсталёўваем неабходныя нам кампілятары, выкарыстоўваючы зменныя асяроддзі. Затым, калі пераменная $PVS_ANALYZE захоўвае значэнне ды (мы паказалі яго ў секцыі env падчас канфігурацыі матрыцы зборак), мы ўсталёўваем пакет pvs-studio. Акрамя яго яшчэ пазначаны пакеты libio-socket-ssl-perl и libnet-ssleay-perl, аднак, яны патрэбныя для адпраўкі вынікаў па пошце, таму ў іх няма неабходнасці, калі вы выбралі іншы спосаб дастаўкі справаздачы.
Функцыя download_extract спампоўвае і распакоўвае паказаны архіў:
download_extract() {
aria2c -x 16 $1 -o $2
tar -xf $2
}
Надышоў час сабраць праект. Гэта адбываецца ў секцыі скрыпт:
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
}
Фактычна, гэта спрошчаная арыгінальная канфігурацыя, за выключэннем гэтых радкоў:
if [ "$PVS_ANALYZE" = "Yes" ]; then
CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi
У гэтым участку кода мы ўстанаўліваем для cmake сцяг экспарту каманд кампіляцыі. Гэта неабходна для статычнага аналізатара кода. Падрабязней пра гэта можна пачытаць у артыкуле «
Калі зборка прайшла паспяхова, то мы трапляем у after_success, дзе выканаем статычны аналіз:
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
}
Разгледзім падрабязней наступныя радкі:
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
Першы радок генеруе файл ліцэнзіі з імя карыстальніка і ключа, які мы паказалі ў самым пачатку падчас наладкі зменных асяроддзя Travis CI.
Другі радок запускае непасрэдна аналіз. Сцяг -j устанаўлівае колькасць патокаў для аналізу, сцяг. -l паказвае ліцэнзію, сцяг -o вызначае файл для вываду логаў, а сцяг -disableLicenseExpirationCheck неабходны для трыяльных версій, бо па змаўчанні pvs-studio-analyzer папярэдзіць карыстальніка аб хуткім заканчэнні ліцэнзіі. Каб гэтага не было - можна паказаць гэты сцяг.
Файл логаў утрымоўвае неапрацаваны выснову, які не атрымаецца прачытаць без канвертавання, таму спачатку неабходна зрабіць файл чытэльным. Прапусцім логі праз plog-converter, і на выхадзе атрымліваем html файл.
У дадзеным прыкладзе я вырашыў адправіць справаздачы па пошце, выкарыстаўшы каманду sendemail.
У выніку ў нас атрымаўся наступны файл .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;
Надышло час дадаць змены на git-рэпазітар, пасля чаго Travis CI аўтаматычна запусціць зборку. Клікнем на "ppsspp", каб перайсці да справаздач па зборцы:
Перад намі з'явіцца агляд бягучай зборкі:
У выпадку паспяховага завяршэння зборкі мы атрымаем на пошту ліст з вынікамі статычнага аналізу. Зразумела, адпраўка па пошце - не адзіны спосаб атрымаць справаздачу. Вы можаце абраць любы спосаб рэалізацыі. Але важна памятаць, што пасля завяршэння зборкі будзе немагчыма атрымаць доступ да файлаў віртуальнай машыны.
Кароткі агляд памылак
Самую складаную частку мы паспяхова завяршылі. Цяпер давайце пераканаемся, што ўсе нашы намаганні апраўдаліся. Разгледзім некаторыя цікавыя моманты са справаздачы па статычным аналізе, якія дашлі мне па пошце (нездарма ж я паказаў яе).
Небяспечная аптымізацыя
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 ) );
}
Папярэджанне PVS-Studio:
Дадзены фрагмент кода знаходзіцца ў модулі бяспечнага хэшавання, аднак, у ім крыецца сур'ёзны дэфект бяспекі (
; Line 355
mov r8d, 20
xor edx, edx
lea rcx, QWORD PTR sum$[rsp]
call memset
; Line 356
Усё ў поўным парадку, і функцыя мемсет выконваецца, тым самым заціраючы важныя дадзеныя ў аператыўнай памяці, аднак, не варта пакуль радавацца. Разгледзім асэмблерны лістынг Release-версіі з аптымізацыяй:
; 354 :
; 355 : memset( sum, 0, sizeof( sum ) );
; 356 :}
Як відаць з лістынга, кампілятар праігнараваў выклік мемсет. Гэта звязана з тым, што ў функцыі sha1 пасля выкліку мемсет больш няма звароту да структуры ctx. Таму кампілятар не бачыць сэнсу марнаваць працэсарны час на перазапіс невыкарыстоўванай у далейшым памяці. Можна выправіць гэта, скарыстаўшыся функцыяй RtlSecureZeroMemory або
правільна:
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 ) );
}
Лішняе параўнанне
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);
}
}
Папярэджанне PVS-Studio:
Звярніце ўвагу на else-галінку для першага if. Код будзе выкананы толькі ў тым выпадку, калі ўсе ўмовы leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0 апынуцца ілжывымі. Такім чынам, мы атрымліваем наступныя сцвярджэнні, якія будуць праўдзівыя для else-галінкі: leftvol <= 0xFFFF, rightvol <= 0xFFFF, leftvol >= 0 и rightvol >= 0. Звярніце ўвагу на апошнія два сцвярджэнні. Няўжо мае сэнс правяраць тое, што з'яўляецца неабходнай умовай выканання гэтага фрагмента кода?
Так што мы можам са спакойнай душой выдаліць гэтыя ўмоўныя аператары:
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);
}
}
Іншы сцэнар. За гэтымі залішнімі ўмовамі хаваецца нейкая памылка. Магчыма, праверылі не тое, што патрабуецца.
Ctrl+C Ctrl+V наносіць зваротны ўдар
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfData) ||
!Memory::IsValidAddress(psmfData)) {
return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}
....
}
Звярніце ўвагу на праверку ўнутры if. Вам не здаецца дзіўным, што мы правяраем, ці валідны адрас psmfData, цэлых два разы? Вось і мне падаецца гэта дзіўным… Насамрэч, перад намі, вядома, памылка друку, і ідэя была ў тым, каб праверыць абодва ўваходных параметру.
Карэктны варыянт:
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfStruct) ||
!Memory::IsValidAddress(psmfData)) {
return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}
....
}
Забытая пераменная
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");
}
....
}
Папярэджанне PVS-Studio:
Гэтая памылка знаходзіцца ў тэчцы доб, таму не зусім адносіцца да праекту, але памылка была знойдзена да таго, як я звярнуў увагу на гэта, так што вырашыў пакінуць. Усёткі гэты артыкул не пра агляд памылак, а пра інтэграцыю з Travis CI, і ніякай налады аналізатара не праводзілася.
пераменная памер ініцыялізуецца канстантай, аднак, зусім не выкарыстоўваецца ў кодзе, аж да аператара if, які, само сабой, выдае ілжывы падчас праверкі ўмовы, бо, як мы памятаем, памер роўна нулю. Наступныя праверкі сэнсу таксама ня маюць.
Судзячы па ўсім, аўтар фрагмента кода забыўся аб тым, каб перазапісаць зменную памер перад гэтым.
Стоп
На гэтым, мабыць, скончым з памылкамі. Мэта дадзенага артыкула прадэманстраваць працу PVS-Studio сумесна з Travis CI, а не як мага старанней правесці аналіз праекта. Калі жадаецца памылак пабольш і прыгажэй, то на іх заўсёды можна палюбавацца.
Заключэнне
Выкарыстанне вэб-сэрвісаў для зборкі праектаў сумесна з практыкай інкрыментальнага аналізу дазваляе выявіць шмат праблем адразу пасля зліцця кода. Адной зборкі, праўда, можа быць недастаткова, таму настройка тэсціравання сумесна са статычным аналізам значна палепшыць якасць кода.
Карысныя спасылкі
Калі хочаце падзяліцца гэтым артыкулам з англамоўнай аўдыторыяй, то прашу выкарыстаць спасылку на пераклад: Maxim Zvyagintsev.
Крыніца: habr.com