Як налаштувати PVS-Studio у Travis CI на прикладі емулятора ігрової приставки PSP

Як налаштувати PVS-Studio у Travis CI на прикладі емулятора ігрової приставки PSP
Travis CI — розподілений веб-сервіс для складання та тестування програмного забезпечення, що використовує GitHub як хостинг вихідного коду. Крім зазначених вище сценаріїв роботи, можна додати власні, завдяки широким можливостям конфігурації. У цій статті ми налаштуємо Travis CI для роботи з PVS-Studio на прикладі коду PPSSPP.

Запровадження

Тревіс К.І. — це веб-сервіс для збирання та тестування програмного забезпечення. Зазвичай його використовують разом із практикою безперервної інтеграції.

PPSSPP - емулятор ігрової приставки PSP. Програма може емулювати запуск будь-яких ігор з образів дисків, призначених для Sony PSP. Випуск програми відбувся 1 листопада 2012 року. PPSSPP розповсюджується за ліцензією GPL v2. Будь-який бажаючий може внести свої покращення в вихідний код проекту.

ПВС-Студія - Статичний аналізатор коду для пошуку помилок та потенційних уразливостей у коді програм. У цій статті ми для різноманітності запустимо PVS-Studio не локально на машині розробника, а в хмарі, і шукаємо помилки у PPSSPP.

Налаштування Travis CI

Нам знадобиться репозиторій на GitHub, де лежить потрібний нам проект, а також ключ для PVS-Studio (можете отримати тріальний ключ або безкоштовний для Open Source проектів).

Перейдемо на сайт Тревіс К.І.. Після авторизації за допомогою облікового запису GitHub перед нами буде список репозиторіїв:

Як налаштувати PVS-Studio у Travis CI на прикладі емулятора ігрової приставки PSP
Для тесту я зробив форк PPSSPP.

Активуємо репозиторій, який хочемо збирати:

Як налаштувати PVS-Studio у Travis CI на прикладі емулятора ігрової приставки PSP
На даний момент Travis CI не може зібрати наш проект, тому що немає інструкцій для збирання. Тому настав час конфігурації.

Під час аналізу нам знадобляться деякі змінні, наприклад ключ для PVS-Studio, які було б небажано вказувати у файлі конфігурації. Так що додамо змінні оточення за допомогою налаштування складання Travis CI:

Як налаштувати PVS-Studio у Travis CI на прикладі емулятора ігрової приставки PSP
Нам знадобляться:

  • PVS_USERNAME - ім'я користувача
  • PVS_KEY - ключ
  • MAIL_USER — email, який буде використаний для надсилання звіту
  • MAIL_PASSWORD - пароль від email

Останні дві необов'язкові. Вони будуть використовуватися для надсилання результатів поштою. Якщо ви хочете надіслати звіт іншим способом, то не потрібно їх вказувати.

Отже, ми додали потрібні нам змінні оточення:

Як налаштувати PVS-Studio у Travis CI на прикладі емулятора ігрової приставки PSP
Тепер створимо файл .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 існує два способи створення варіантів складання: перший — вказати списком компілятори, типи операційних систем, змінні оточення тощо, після чого згенерується матриця всіх можливих комбінацій; другий - явна вказівка ​​матриці. Зрозуміло, можна комбінувати ці два підходи і додати унікальний випадок, або ж, навпаки, виключити за допомогою секції виключати. Докладніше про це можна почитати в документації по 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 CI.

Для зручності читання команди було винесено в окремий скрипт .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 прапор експорту команд компіляції. Це потрібно для статичного аналізатора коду. Докладніше про це можна почитати у статті «Як запустити PVS-Studio у Linux та macOS".

Якщо збірка пройшла успішно, то ми потрапляємо до 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», щоб перейти до звітів зі складання:

Як налаштувати PVS-Studio у Travis CI на прикладі емулятора ігрової приставки PSP
Перед нами з'явиться огляд поточної збірки:

Як налаштувати PVS-Studio у Travis CI на прикладі емулятора ігрової приставки PSP
У разі успішного завершення складання ми отримаємо на пошту листа з результатами статичного аналізу. Зрозуміло, надсилання поштою — не єдиний спосіб отримати звіт. Ви можете вибрати будь-який спосіб реалізації. Але важливо пам'ятати, що після завершення зборки неможливо отримати доступ до файлів віртуальної машини.

Короткий огляд помилок

Найскладнішу частину ми успішно завершили. Тепер переконайтеся, що всі наші зусилля виправдалися. Розглянемо деякі цікаві моменти зі звіту зі статичного аналізу, які прийшли мені поштою (не дарма ж я вказав її).

Небезпечна оптимізація

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: V597 Комп'ютер може вимкнути 'memset' функцію call, яка використовується для flush 'sum' buffer. RtlSecureZeroMemory() функція повинна бути використана для private data. sha1.cpp 325

Цей фрагмент коду знаходиться в модулі безпечного хешування, проте в ньому криється серйозний дефект безпеки (CWE-14). Розглянемо асемблерний лістинг, що генерується при компіляції Debug-версії:

; 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: V547 Expression 'leftvol >= 0' is always true. sceAudio.cpp 120

Зверніть увагу на 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");
  }
  ....
}

V501 Там є подібні sub-expressions '! Operator. scePsmf.cpp 703

Зверніть увагу на перевірку всередині 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: V547 Expression 'size == 8' is always false. syn-att.c 195

Ця помилка знаходиться в папці extтому не зовсім відноситься до проекту, але помилка була знайдена до того, як я звернув увагу на це, так що вирішив залишити. Все-таки ця стаття не про огляд помилок, а про інтеграцію з Travis CI, і ніякого налаштування аналізатора не проводилося.

Мінлива розмір ініціалізується константою, однак, абсолютно не використовується в коді, аж до оператора ifякий, само собою, видає false під час перевірки умови, адже, як ми пам'ятаємо, розмір дорівнює нулю. Подальші перевірки сенсу теж немає.

Зважаючи на все, автор фрагмента коду забув про те, щоб перезаписати змінну розмір перед цим.

Стоп

На цьому, мабуть, скінчимо з помилками. Мета цієї статті продемонструвати роботу PVS-Studio спільно з Travis CI, а не якнайретельніше провести аналіз проекту. Якщо хочеться побільше і покрасив помилок, то на них завжди можна помилуватися тут :).

Висновок

Використання веб-сервісів для складання проектів спільно з практикою інкрементального аналізу дозволяє виявити багато проблем відразу після злиття коду. Одного складання, щоправда, може бути недостатньо, тому налаштування тестування спільно зі статичним аналізом значно покращить якість коду.

Корисні посилання

Як налаштувати PVS-Studio у Travis CI на прикладі емулятора ігрової приставки PSP

Якщо хочете поділитися цією статтею з англомовною аудиторією, прошу використати посилання на переклад: Maxim Zvyagintsev. How to set up PVS-Studio in Travis CI використовуючи приклад PSP game console emulator.

Джерело: habr.com

Додати коментар або відгук