Як наладзіць PVS-Studio у Travis CI на прыкладзе эмулятара гульнявой прыстаўкі PSP

Як наладзіць PVS-Studio у Travis CI на прыкладзе эмулятара гульнявой прыстаўкі PSP
Travis CI — размеркаваны вэб-сэрвіс для зборкі і тэсціравання праграмнага забеспячэння, які выкарыстоўвае GitHub у якасці хостынгу зыходнага кода. Апроч паказаных вышэй сцэнараў працы, можна дадаць уласныя, дзякуючы шырокім магчымасцям для канфігурацыі. У дадзеным артыкуле мы наладзім Travis CI для працы з PVS-Studio на прыкладзе кода PPSSPP.

Увядзенне

Travis CI — гэта вэб-сэрвіс для зборкі і тэсціравання праграмнага забеспячэння. Звычайна яго выкарыстоўваюць сумесна з практыкай бесперапыннай інтэграцыі.

PPSSPP - эмулятар гульнявой прыстаўкі PSP. Праграма ў стане эмуляваць запуск любых гульняў з вобразаў дыскаў, прызначаных для Sony PSP. Выпуск праграмы адбыўся 1 лістапада 2012 года. PPSSPP распаўсюджваецца па ліцэнзіі GPL v2. Любы жадаючы можа ўнесці свае паляпшэнні ў зыходны код праекта.

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

Настройка Travis CI

Нам спатрэбіцца рэпазітар на GitHub, дзе ляжыць патрэбны нам праект, а таксама ключ для PVS-Studio (можаце атрымаць трыяльны ключ або бясплатны для Open Source праектаў).

Пяройдзем на сайт Travis CI. Пасля аўтарызацыі пры дапамозе акаўнта 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 паказвае ліцэнзію, сцяг -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", каб перайсці да справаздач па зборцы:

Як наладзіць 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 Сістэма можа пазбягаць 'фундатар функцыі", які выкарыстоўваецца для 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 '!Memory :: IsValidAddress(psmfData)' 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

Гэтая памылка знаходзіцца ў тэчцы доб, таму не зусім адносіцца да праекту, але памылка была знойдзена да таго, як я звярнуў увагу на гэта, так што вырашыў пакінуць. Усёткі гэты артыкул не пра агляд памылак, а пра інтэграцыю з Travis CI, і ніякай налады аналізатара не праводзілася.

пераменная памер ініцыялізуецца канстантай, аднак, зусім не выкарыстоўваецца ў кодзе, аж да аператара if, які, само сабой, выдае ілжывы падчас праверкі ўмовы, бо, як мы памятаем, памер роўна нулю. Наступныя праверкі сэнсу таксама ня маюць.

Судзячы па ўсім, аўтар фрагмента кода забыўся аб тым, каб перазапісаць зменную памер перад гэтым.

Стоп

На гэтым, мабыць, скончым з памылкамі. Мэта дадзенага артыкула прадэманстраваць працу PVS-Studio сумесна з Travis CI, а не як мага старанней правесці аналіз праекта. Калі жадаецца памылак пабольш і прыгажэй, то на іх заўсёды можна палюбавацца. тут :).

Заключэнне

Выкарыстанне вэб-сэрвісаў для зборкі праектаў сумесна з практыкай інкрыментальнага аналізу дазваляе выявіць шмат праблем адразу пасля зліцця кода. Адной зборкі, праўда, можа быць недастаткова, таму настройка тэсціравання сумесна са статычным аналізам значна палепшыць якасць кода.

Карысныя спасылкі

Як наладзіць PVS-Studio у Travis CI на прыкладзе эмулятара гульнявой прыстаўкі PSP

Калі хочаце падзяліцца гэтым артыкулам з англамоўнай аўдыторыяй, то прашу выкарыстаць спасылку на пераклад: Maxim Zvyagintsev. How to set PVS-Studio in Travis CI з дапамогай example of PSP game console emulator.

Крыніца: habr.com

Дадаць каментар