Jak nakonfigurovat PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP

Jak nakonfigurovat PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP
Travis CI je distribuovaná webová služba pro vytváření a testování softwaru, která využívá GitHub jako hosting zdrojového kódu. Kromě výše uvedených provozních scénářů můžete díky rozsáhlým konfiguračním možnostem přidat vlastní. V tomto článku nakonfigurujeme Travis CI pro práci s PVS-Studio pomocí příkladu kódu PPSSPP.

úvod

Travis CI je webová služba pro vytváření a testování softwaru. Obvykle se používá ve spojení s postupy kontinuální integrace.

PPSSPP - Emulátor herní konzole PSP. Program je schopen emulovat spouštění jakýchkoli her z diskových obrazů určených pro Sony PSP. Program byl vydán 1. listopadu 2012. PPSSPP je licencován pod GPL v2. Každý může provést vylepšení zdrojový kód projektu.

Studio PVS — analyzátor statického kódu pro vyhledávání chyb a potenciálních zranitelností v kódu programu. V tomto článku pro změnu spustíme PVS-Studio nikoli lokálně na vývojářském počítači, ale v cloudu a budeme hledat chyby v PPSSPP.

Nastavení Travis CI

Budeme potřebovat úložiště na GitHubu, kde se nachází projekt, který potřebujeme, a také klíč pro PVS-Studio (můžete získat zkušební klíč nebo zdarma pro Open Source projekty).

Pojďme na web Travis CI. Po autorizaci pomocí vašeho účtu GitHub uvidíme seznam úložišť:

Jak nakonfigurovat PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP
Pro test jsem forknul PPSSPP.

Aktivujeme úložiště, které chceme shromáždit:

Jak nakonfigurovat PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP
V tuto chvíli Travis CI nemůže postavit náš projekt, protože neexistují žádné pokyny pro stavbu. Je tedy čas na konfiguraci.

Při analýze se nám budou hodit některé proměnné, například klíč pro PVS-Studio, který by bylo nežádoucí uvádět v konfiguračním souboru. Pojďme tedy přidat proměnné prostředí pomocí nastavení sestavení v Travis CI:

Jak nakonfigurovat PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP
Potřebujeme:

  • PVS_USERNAME – uživatelské jméno
  • PVS_KEY - klíč
  • MAIL_USER - e-mail, který bude použit k odeslání zprávy
  • MAIL_PASSWORD - heslo k e-mailu

Poslední dva jsou volitelné. Ty budou použity k odeslání výsledků poštou. Pokud chcete zprávu distribuovat jiným způsobem, nemusíte je uvádět.

Takže jsme přidali proměnné prostředí, které potřebujeme:

Jak nakonfigurovat PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP
Nyní vytvoříme soubor .travis.yml a umístěte jej do kořenového adresáře projektu. PPSSPP již měl konfigurační soubor pro Travis CI, nicméně byl příliš velký a pro příklad zcela nevhodný, takže jsme jej museli značně zjednodušit a ponechat pouze základní prvky.

Nejprve označme jazyk, verzi Ubuntu Linux, kterou chceme ve virtuálním počítači používat, a potřebné balíčky pro sestavení:

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'

Všechny balíčky, které jsou uvedeny, jsou potřeba výhradně pro PPSSPP.

Nyní naznačíme montážní matici:

matrix:
  include:
    - os: linux
      compiler: "gcc"
      env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes
    - os: linux
      compiler: "clang"
      env: PPSSPP_BUILD_TYPE=Linux

Trochu více o sekci matice. V Travis CI existují dva způsoby, jak vytvořit možnosti sestavení: první je specifikovat seznam kompilátorů, typů operačního systému, proměnných prostředí atd., načež se vygeneruje matice všech možných kombinací; druhá je explicitní označení matice. Tyto dva přístupy můžete samozřejmě kombinovat a přidat jedinečný případ, nebo jej naopak pomocí sekce vyloučit vyloučit. Více si o tom můžete přečíst v Dokumentace Travis CI.

Zbývá pouze poskytnout návod k montáži pro konkrétní projekt:

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 vám umožňuje přidávat vlastní příkazy pro různé fáze života virtuálního stroje. Sekce před_instalací provedeny před instalací balíčků. Pak instalovat, který následuje po instalaci balíčků ze seznamu addons.aptkteré jsme naznačili výše. Samotná montáž probíhá v skript. Pokud vše půjde dobře, ocitneme se uvnitř po_úspěchu (v této části spustíme statickou analýzu). Toto nejsou všechny kroky, které lze upravit, pokud potřebujete více, měli byste se podívat Dokumentace Travis CI.

Pro usnadnění čtení byly příkazy umístěny v samostatném skriptu .travis.sh, který je umístěn v kořenovém adresáři projektu.

Máme tedy následující soubor .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

Před instalací balíčků provedeme aktualizaci submodulů. To je potřeba k vybudování PPSSPP. Přidejme první funkci do .travis.sh (všimněte si rozšíření):

travis_before_install() {
  git submodule update --init --recursive
}

Nyní přejdeme přímo k nastavení automatického spuštění PVS-Studio v Travis CI. Nejprve musíme do systému nainstalovat balíček 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
}

Na začátku funkce travis_install nainstalujeme kompilátory, které potřebujeme, pomocí proměnných prostředí. Pak pokud proměnná $PVS_ANALYZE ukládá hodnotu Ano (uvedli jsme to v sekci env během konfigurace matice sestavení), nainstalujeme balíček pvs-studio. Kromě toho jsou uvedeny také balíčky libio-socket-ssl-perl и libnet-ssleay-perljsou však vyžadovány pro zasílání výsledků poštou, takže nejsou nutné, pokud jste zvolili jiný způsob doručení zprávy.

Funkce download_extract stáhne a rozbalí zadaný archiv:

download_extract() {
  aria2c -x 16 $1 -o $2
  tar -xf $2
}

Je čas dát projekt dohromady. To se děje v sekci skript:

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
}

Ve skutečnosti se jedná o zjednodušenou původní konfiguraci, s výjimkou těchto řádků:

if [ "$PVS_ANALYZE" = "Yes" ]; then
  CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi

V této části kódu jsme stanovili pro cmake příznak pro export příkazů kompilace. To je nezbytné pro analyzátor statického kódu. Více si o tom můžete přečíst v článku „Jak spustit PVS-Studio na Linuxu a macOS".

Pokud byla montáž úspěšná, tak se dostáváme k po_úspěchu, kde provádíme statickou analýzu:

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
}

Pojďme se blíže podívat na následující řádky:

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

První řádek vygeneruje licenční soubor z uživatelského jména a klíče, které jsme zadali úplně na začátku při nastavování proměnných prostředí Travis CI.

Druhý řádek přímo spustí analýzu. Vlajka -j nastavuje počet vláken pro analýzu, příznak -l označuje licenci, příznak definuje soubor pro výstup protokolů a příznak -disableLicenseExpirationCheck vyžadováno pro zkušební verze, protože ve výchozím nastavení pvs-studio-analyzer upozorní uživatele, že licence brzy vyprší. Chcete-li tomu zabránit, můžete zadat tento příznak.

Soubor protokolu obsahuje nezpracovaný výstup, který nelze číst bez konverze, takže nejprve musíte soubor nastavit jako čitelný. Projdeme protokoly plog-konvertora výstupem je soubor html.

V tomto příkladu jsem se rozhodl odesílat zprávy poštou pomocí příkazu poslat e-mailem.

V důsledku toho jsme dostali následující soubor .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;

Nyní je čas přesunout změny do úložiště git, poté Travis CI automaticky spustí sestavení. Kliknutím na „ppsspp“ přejdete na sestavy sestav:

Jak nakonfigurovat PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP
Uvidíme přehled aktuálního sestavení:

Jak nakonfigurovat PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP
Pokud je sestavení úspěšně dokončeno, obdržíme e-mail s výsledky statické analýzy. Mailing samozřejmě není jediný způsob, jak zprávu obdržet. Můžete si vybrat jakýkoli způsob implementace. Je však důležité si uvědomit, že po dokončení sestavení nebude možné přistupovat k souborům virtuálního počítače.

Shrnutí chyb

Nejtěžší část máme úspěšně za sebou. Nyní se přesvědčme, že veškeré naše úsilí stojí za to. Podívejme se na některé zajímavé body ze zprávy statické analýzy, která mi přišla poštou (ne nadarmo jsem to naznačil).

Nebezpečná optimalizace

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 ) );
}

Upozornění PVS-Studio: V597 Kompilátor by mohl odstranit volání funkce 'memset', která se používá k vyprázdnění vyrovnávací paměti 'součet'. K vymazání soukromých dat by měla být použita funkce RtlSecureZeroMemory(). sha1.cpp 325

Tento kus kódu se nachází v zabezpečeném hashovacím modulu, obsahuje však závažnou bezpečnostní chybu (CWE-14). Podívejme se na výpis sestavení, který se vygeneruje při kompilaci ladicí verze:

; Line 355
  mov r8d, 20
  xor edx, edx
  lea rcx, QWORD PTR sum$[rsp]
  call memset
; Line 356

Vše v pořádku a funkce pamětová sada se provede, čímž dojde k přepsání důležitých dat v RAM, ale zatím se neradujte. Podívejme se na výpis sestavy verze Release s optimalizací:

; 354  :
; 355  :  memset( sum, 0, sizeof( sum ) );
; 356  :}

Jak je vidět z výpisu, kompilátor volání ignoroval pamětová sada. To je způsobeno tím, že ve funkci sha1 po hovoru pamětová sada žádné další odkazy na strukturu CTX. Kompilátor proto nevidí žádný smysl v plýtvání časem procesoru přepisováním paměti, která se v budoucnu nevyužije. Můžete to opravit pomocí funkce RtlSecureZeroMemory nebo podobné jí.

Správně:

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 ) );
} 

Zbytečné srovnání

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);
  }
}

Upozornění PVS-Studio: V547 Výraz 'leftvol >= 0' je vždy pravdivý. sceAudio.cpp 120

Nejprve věnujte pozornost větvi else if. Kód bude proveden pouze při splnění všech podmínek leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0 se ukáže jako nepravdivé. Dostaneme tedy následující tvrzení, která budou platit pro větev else: leftvol <= 0xFFFF, rightvol <= 0xFFFF, leftvol >= 0 и rightvol >= 0. Všimněte si posledních dvou tvrzení. Má smysl kontrolovat, co je nezbytnou podmínkou pro provedení tohoto kusu kódu?

Takže můžeme bezpečně odstranit tyto podmíněné příkazy:

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);
  }
}

Jiný scénář. Za těmito nadbytečnými podmínkami se skrývá nějaký druh chyby. Možná nezkontrolovali, co bylo požadováno.

Ctrl+C Ctrl+V vrací úder

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 Nalevo a napravo od '||' jsou identické podvýrazy '!Memory::IsValidAddress(psmfData)' operátor. scePsmf.cpp 703

Věnujte pozornost kontrole uvnitř if. Nepřipadá vám divné, že kontrolujeme, zda je adresa platná? psmfData, dvakrát tolik? Tak tohle mi připadá divné... Ve skutečnosti jde samozřejmě o překlep a záměrem bylo zkontrolovat oba vstupní parametry.

Správná možnost:

static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
  if (!Memory::IsValidAddress(psmfStruct) ||
      !Memory::IsValidAddress(psmfData)) {
    return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
  }
  ....
}

Zapomenutá proměnná

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");
  }
  ....
}

Upozornění PVS-Studio: V547 Výraz 'velikost == 8' je vždy nepravdivý. syn-att.c 195

Tato chyba se nachází ve složce ext, takže to není pro projekt opravdu relevantní, ale chyba byla nalezena dříve, než jsem si jí všiml, takže jsem se rozhodl ji opustit. Koneckonců, tento článek není o kontrole chyb, ale o integraci s Travis CI a nebyla provedena žádná konfigurace analyzátoru.

Proměnná velikost je inicializováno konstantou, ta se však v kódu vůbec nepoužívá, až na operátora if, což samozřejmě dává nepravdivý při kontrole podmínek, protože, jak si pamatujeme, velikost rovna nule. Následné kontroly také nemají smysl.

Autor fragmentu kódu zřejmě zapomněl přepsat proměnnou velikost před tím.

Stop

Tady asi s chybami skončíme. Účelem tohoto článku je demonstrovat práci PVS-Studio společně s Travisem CI, nikoli co nejdůkladněji analyzovat projekt. Pokud chcete větší a krásnější chyby, můžete je vždy obdivovat zde :).

Závěr

Používání webových služeb k vytváření projektů spolu s praxí inkrementální analýzy vám umožňuje najít mnoho problémů ihned po sloučení kódu. Jedno sestavení však nemusí stačit, takže nastavení testování spolu se statickou analýzou výrazně zlepší kvalitu kódu.

Užitečné odkazy

Jak nakonfigurovat PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP

Pokud chcete tento článek sdílet s anglicky mluvícím publikem, použijte prosím odkaz na překlad: Maxim Zvjagincev. Jak nastavit PVS-Studio v Travis CI na příkladu emulátoru herní konzole PSP.

Zdroj: www.habr.com

Přidat komentář