Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP

Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP
Travis CI to rozproszona usługa internetowa służąca do tworzenia i testowania oprogramowania, która wykorzystuje GitHub jako hosting kodu źródłowego. Oprócz powyższych scenariuszy działania, dzięki rozbudowanym możliwościom konfiguracyjnym możesz dodać własne. W tym artykule skonfigurujemy Travis CI do współpracy z PVS-Studio na przykładzie kodu PPSSPP.

Wprowadzenie

Travis CI to usługa internetowa służąca do tworzenia i testowania oprogramowania. Zwykle stosuje się go w połączeniu z praktykami ciągłej integracji.

PPSSPP - Emulator konsoli do gier PSP. Program jest w stanie emulować uruchamianie dowolnych gier z obrazów dysków przeznaczonych dla Sony PSP. Program został wyemitowany 1 listopada 2012 roku. PPSSPP jest objęty licencją GPL v2. Każdy może dokonać ulepszeń kod źródłowy projektu.

Studio PVS — statyczny analizator kodu służący do wyszukiwania błędów i potencjalnych luk w kodzie programu. W tym artykule dla odmiany uruchomimy PVS-Studio nie lokalnie na komputerze dewelopera, a w chmurze i poszukamy błędów w PPSSPP.

Konfigurowanie Travis CI

Będziemy potrzebować repozytorium na GitHubie, gdzie znajduje się potrzebny nam projekt, a także klucz do PVS-Studio (można zdobyć klucz próbny lub bezpłatny dla projektów Open Source).

Przejdźmy do serwisu Travis CI. Po autoryzacji przy użyciu konta GitHub wyświetli nam się lista repozytoriów:

Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP
Na potrzeby testu rozwidliłem PPSSPP.

Aktywujemy repozytorium, które chcemy zebrać:

Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP
W tej chwili Travis CI nie może zbudować naszego projektu, ponieważ nie ma instrukcji budowania. Czas więc na konfigurację.

Podczas analizy przydadzą nam się niektóre zmienne, np. klucz do PVS-Studio, którego określenie w pliku konfiguracyjnym byłoby niepożądane. Dodajmy więc zmienne środowiskowe, korzystając z ustawień kompilacji w Travis CI:

Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP
Potrzebujemy:

  • PVS_USERNAME — nazwa użytkownika
  • PVS_KEY - klucz
  • MAIL_USER - adres e-mail, na który zostanie przesłany raport
  • MAIL_PASSWORD – hasło e-mail

Dwa ostatnie są opcjonalne. Zostaną one wykorzystane do przesłania wyników pocztą. Jeśli chcesz rozpowszechnić raport w inny sposób, nie musisz tego wskazywać.

Dodaliśmy więc zmienne środowiskowe, których potrzebujemy:

Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP
Teraz utwórzmy plik .travis.yml i umieść go w katalogu głównym projektu. PPSSPP posiadało już plik konfiguracyjny dla Travis CI, jednak był on za duży i zupełnie nie nadawał się do przykładu, więc musieliśmy go znacznie uprościć i pozostawić jedynie podstawowe elementy.

Na początek wskażmy język, wersję Ubuntu Linux, której chcemy używać na maszynie wirtualnej oraz niezbędne pakiety do kompilacji:

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'

Wszystkie wymienione pakiety są potrzebne wyłącznie dla PPSSPP.

Teraz wskazujemy macierz montażu:

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

Trochę więcej o dziale matryca. W Travis CI istnieją dwa sposoby tworzenia opcji kompilacji: pierwszy polega na określeniu listy kompilatorów, typów systemów operacyjnych, zmiennych środowiskowych itp., po czym generowana jest macierz wszystkich możliwych kombinacji; drugi to wyraźne wskazanie macierzy. Oczywiście można połączyć te dwa podejścia i dodać unikalny przypadek lub wręcz przeciwnie, wykluczyć go za pomocą sekcji wykluczać. Więcej na ten temat można przeczytać w Dokumentacja Travisa CI.

Pozostaje tylko dostarczyć instrukcję montażu dostosowaną do projektu:

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 umożliwia dodawanie własnych poleceń dla różnych etapów życia maszyny wirtualnej. Sekcja przed_instalacją wykonywane przed instalacją pakietów. Następnie zainstalować, co następuje po instalacji pakietów z listy dodatki.aptco wskazaliśmy powyżej. Sam montaż odbywa się w scenariusz. Jeśli wszystko poszło dobrze, znajdziemy się w środku po_sukcesu (w tej sekcji przeprowadzimy analizę statyczną). To nie wszystkie kroki, które można zmodyfikować, jeśli potrzebujesz więcej, powinieneś zajrzeć Dokumentacja Travisa CI.

Dla ułatwienia czytania polecenia zostały umieszczone w osobnym skrypcie .travis.sh, który jest umieszczony w katalogu głównym projektu.

Mamy więc następujący plik .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

Przed instalacją pakietów zaktualizujemy podmoduły. Jest to potrzebne do zbudowania PPSSPP. Dodajmy pierwszą funkcję do .travis.sh (zwróć uwagę na rozszerzenie):

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

Teraz przechodzimy bezpośrednio do konfiguracji automatycznego uruchamiania PVS-Studio w Travis CI. Najpierw musimy zainstalować w systemie pakiet 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 początku funkcji travis_install instalujemy potrzebne kompilatory, używając zmiennych środowiskowych. Następnie, jeśli zmienna $PVS_ANALYZE przechowuje wartość Tak (wskazaliśmy to w sekcji env podczas konfiguracji build matrix) instalujemy pakiet studio pvs. Oprócz tego wskazane są również pakiety libio-socket-ssl-perl и libnet-ssley-perl, są one jednak wymagane do wyników wysyłki, więc nie są konieczne, jeśli wybrałeś inną metodę dostarczenia raportu.

Funkcja pobierz_wyciąg pobiera i rozpakowuje określone archiwum:

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

Czas złożyć projekt w całość. Dzieje się to w sekcji scenariusz:

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
}

W rzeczywistości jest to uproszczona oryginalna konfiguracja, z wyjątkiem tych linii:

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

W tej części kodu ustawiliśmy cmake flaga do eksportowania poleceń kompilacji. Jest to konieczne w przypadku statycznego analizatora kodu. Więcej na ten temat przeczytasz w artykule „Jak uruchomić PVS-Studio na systemach Linux i macOS".

Jeśli montaż przebiegł pomyślnie, to przechodzimy do po_sukcesu, gdzie wykonujemy analizę statyczną:

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
}

Przyjrzyjmy się bliżej następującym liniom:

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

Pierwsza linia generuje plik licencji na podstawie nazwy użytkownika i klucza, które podaliśmy na samym początku podczas konfigurowania zmiennych środowiskowych Travis CI.

Druga linia bezpośrednio rozpoczyna analizę. Flaga -J ustawia liczbę wątków do analizy, flaga -l wskazuje licencję, flagę -o definiuje plik do wysyłania dzienników i flagę -wyłącz kontrolę wygaśnięcia licencji wymagane w przypadku wersji próbnych, ponieważ domyślnie analizator pvs-studio ostrzeże użytkownika, że ​​licencja wkrótce wygaśnie. Aby temu zapobiec, możesz określić tę flagę.

Plik dziennika zawiera surowe dane wyjściowe, których nie można odczytać bez konwersji, dlatego należy najpierw zapewnić możliwość odczytu pliku. Przepuśćmy logi konwerter plogów, a wynikiem jest plik HTML.

W tym przykładzie zdecydowałem się na wysyłanie raportów pocztą za pomocą polecenia wysłać email.

W rezultacie otrzymaliśmy następujący plik .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;

Teraz nadszedł czas, aby wypchnąć zmiany do repozytorium git, po czym Travis CI automatycznie uruchomi kompilację. Kliknij „ppsspp”, aby przejść do raportów kompilacji:

Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP
Zobaczymy przegląd bieżącej kompilacji:

Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP
Jeśli kompilacja zakończy się pomyślnie, otrzymamy wiadomość e-mail z wynikami analizy statycznej. Oczywiście mailing nie jest jedyną metodą otrzymania raportu. Możesz wybrać dowolną metodę realizacji. Należy jednak pamiętać, że po zakończeniu kompilacji dostęp do plików maszyny wirtualnej nie będzie możliwy.

Podsumowanie błędów

Udało nam się pomyślnie przejść najtrudniejszą część. Teraz upewnijmy się, że wszystkie nasze wysiłki są tego warte. Przyjrzyjmy się kilku ciekawym punktom z raportu analizy statycznej, który przyszedł do mnie pocztą (nie bez powodu to wskazałem).

Niebezpieczna optymalizacja

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

Ostrzeżenie PVS-Studio: V597 Kompilator mógłby usunąć wywołanie funkcji „memset”, która służy do opróżniania bufora „sumy”. Do usunięcia prywatnych danych należy użyć funkcji RtlSecureZeroMemory(). sha1.cpp 325

Ten fragment kodu znajduje się w bezpiecznym module mieszającym, jednak zawiera poważną lukę w zabezpieczeniach (CWE-14). Przyjrzyjmy się liście zestawu generowanej podczas kompilacji wersji debugującej:

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

Wszystko jest w porządku i działa zestaw memów jest wykonywany, zastępując w ten sposób ważne dane w pamięci RAM, jednak jeszcze się nie ciesz. Spójrzmy na listę zestawów wersji Release z optymalizacją:

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

Jak widać z listy, kompilator zignorował wywołanie zestaw memów. Dzieje się tak dlatego, że w funkcji sha1 po rozmowie zestaw memów nie ma już odniesień do struktury ctx. Dlatego kompilator nie widzi sensu marnowania czasu procesora na nadpisywanie pamięci, która nie będzie używana w przyszłości. Możesz to naprawić za pomocą funkcji Pamięć RtlSecureZero lub podobne do niej.

Poprawnie:

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

Niepotrzebne porównanie

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

Ostrzeżenie PVS-Studio: V547 Wyrażenie „leftvol >= 0” jest zawsze prawdziwe. sceAudio.cpp 120

Najpierw zwróć uwagę na gałąź else if. Kod zostanie wykonany tylko wtedy, gdy zostaną spełnione wszystkie warunki lewy tom > 0xFFFF || prawa część > 0xFFFF || lewy tom < 0 || prawa część < 0 okaże się fałszywe. Otrzymujemy zatem następujące stwierdzenia, które będą prawdziwe dla gałęzi else: lewy tom <= 0xFFFF, prawy tom <= 0xFFFF, lewa objętość >= 0 и prawa część >= 0. Zwróć uwagę na dwa ostatnie stwierdzenia. Czy jest sens sprawdzać jaki jest warunek konieczny wykonania tego fragmentu kodu?

Możemy więc bezpiecznie usunąć te instrukcje warunkowe:

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

Inny scenariusz. Za tymi zbędnymi warunkami kryje się jakiś błąd. Być może nie sprawdzili, co było wymagane.

Ctrl+C Ctrl+V kontratakuje

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 Istnieją identyczne podwyrażenia „!Memory::IsValidAddress(psmfData)” po lewej i prawej stronie „||” operator. scePsmf.cpp 703

Zwróć uwagę na czek w środku if. Nie sądzisz, że to dziwne, że sprawdzamy, czy adres jest ważny? dane psmf, dwa razy więcej? Więc wydaje mi się to dziwne... W rzeczywistości jest to oczywiście literówka, a pomysł był taki, aby sprawdzić oba parametry wejściowe.

Prawidłowa opcja:

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

Zapomniana zmienna

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

Ostrzeżenie PVS-Studio: V547 Wyrażenie „rozmiar == 8” jest zawsze fałszywe. syn-att.c 195

Ten błąd znajduje się w folderze ext, więc nie ma to większego związku z projektem, ale błąd został znaleziony, zanim go zauważyłem, więc zdecydowałem się go zostawić. Przecież ten artykuł nie jest o przeglądaniu błędów, ale o integracji z Travis CI, a nie była przeprowadzana żadna konfiguracja analizatora.

Zmienna rozmiar jest inicjowany przez stałą, jednakże nie jest w ogóle używany w kodzie, aż do operatora if, co oczywiście daje fałszywy sprawdzając warunki, bo jak pamiętamy, rozmiar równy zeru. Późniejsze kontrole też nie mają sensu.

Najwyraźniej autor fragmentu kodu zapomniał nadpisać zmienną rozmiar przed tym.

Stop

Na tym prawdopodobnie zakończymy na błędach. Celem tego artykułu jest pokazanie pracy PVS-Studio wspólnie z Travisem CI, a nie możliwie najdokładniejsza analiza projektu. Jeśli chcesz większych i piękniejszych błędów, zawsze możesz je podziwiać tutaj :).

wniosek

Wykorzystanie usług sieciowych do budowy projektów w połączeniu z praktyką analizy przyrostowej pozwala znaleźć wiele problemów zaraz po połączeniu kodu. Jednak jedna kompilacja może nie wystarczyć, dlatego skonfigurowanie testów wraz z analizą statyczną znacząco poprawi jakość kodu.

Przydatne linki

Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP

Jeśli chcesz udostępnić ten artykuł anglojęzycznej publiczności, skorzystaj z linku do tłumaczenia: Maxim Zwiagincew. Jak skonfigurować PVS-Studio w Travis CI na przykładzie emulatora konsoli do gier PSP.

Źródło: www.habr.com

Dodaj komentarz