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 вказує ліцензію, прапор -о визначає файл для виведення логів, а прапор -disableLicenseExpirationCheck необхідний для тріальних версій, оскільки за умовчанням pvs-studio-analyzer попередить користувача про швидке закінчення ліцензії. Щоб цього не було, можна вказати цей прапор.
Файл ліг містить необроблений висновок, який не вийде прочитати без конвертування, тому спочатку необхідно зробити файл читабельним. Пропустимо логи через plog-converterі на виході отримуємо html файл.
У цьому прикладі я вирішив надіслати звіти поштою, використавши команду відправити лист.
У результаті вийшов наступний файл .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:
Ця помилка знаходиться в папці extтому не зовсім відноситься до проекту, але помилка була знайдена до того, як я звернув увагу на це, так що вирішив залишити. Все-таки ця стаття не про огляд помилок, а про інтеграцію з Travis CI, і ніякого налаштування аналізатора не проводилося.
Мінлива розмір ініціалізується константою, однак, абсолютно не використовується в коді, аж до оператора ifякий, само собою, видає false під час перевірки умови, адже, як ми пам'ятаємо, розмір дорівнює нулю. Подальші перевірки сенсу теж немає.
Зважаючи на все, автор фрагмента коду забув про те, щоб перезаписати змінну розмір перед цим.
Стоп
На цьому, мабуть, скінчимо з помилками. Мета цієї статті продемонструвати роботу PVS-Studio спільно з Travis CI, а не якнайретельніше провести аналіз проекту. Якщо хочеться побільше і покрасив помилок, то на них завжди можна помилуватися
Висновок
Використання веб-сервісів для складання проектів спільно з практикою інкрементального аналізу дозволяє виявити багато проблем відразу після злиття коду. Одного складання, щоправда, може бути недостатньо, тому налаштування тестування спільно зі статичним аналізом значно покращить якість коду.
Корисні посилання
Якщо хочете поділитися цією статтею з англомовною аудиторією, прошу використати посилання на переклад: Maxim Zvyagintsev.
Джерело: habr.com