Come configurare PVS-Studio in Travis CI utilizzando l'esempio di un emulatore di console di gioco PSP

Come configurare PVS-Studio in Travis CI utilizzando l'esempio di un emulatore di console di gioco PSP
Travis CI è un servizio Web distribuito per la creazione e il test di software che utilizza GitHub come hosting del codice sorgente. Oltre agli scenari operativi sopra indicati, puoi aggiungerne di nuovi grazie alle ampie opzioni di configurazione. In questo articolo configureremo Travis CI per funzionare con PVS-Studio utilizzando l'esempio di codice PPSSPP.

Introduzione

Travis CI è un servizio web per la creazione e il test del software. Di solito viene utilizzato insieme alle pratiche di integrazione continua.

PPSSPP - Emulatore di console di gioco PSP. Il programma è in grado di emulare il lancio di qualsiasi gioco da immagini disco destinate a Sony PSP. Il programma è stato rilasciato il 1 novembre 2012. PPSSPP è concesso in licenza con GPL v2. Chiunque può apportare miglioramenti codice sorgente del progetto.

Studio PVS — un analizzatore di codice statico per la ricerca di errori e potenziali vulnerabilità nel codice del programma. In questo articolo, tanto per cambiare, avvieremo PVS-Studio non localmente sul computer dello sviluppatore, ma nel cloud, e cercheremo gli errori in PPSSPP.

Configurazione di Travis CI

Avremo bisogno di un repository su GitHub, dove si trova il progetto di cui abbiamo bisogno, nonché di una chiave per PVS-Studio (puoi ottenerla chiave di prova o gratuito per progetti Open Source).

Andiamo al sito Travis CI. Dopo l'autorizzazione utilizzando il tuo account GitHub, vedremo un elenco di repository:

Come configurare PVS-Studio in Travis CI utilizzando l'esempio di un emulatore di console di gioco PSP
Per il test ho effettuato un fork di PPSSPP.

Attiviamo il repository che vogliamo raccogliere:

Come configurare PVS-Studio in Travis CI utilizzando l'esempio di un emulatore di console di gioco PSP
Al momento, Travis CI non può realizzare il nostro progetto perché non ci sono istruzioni per la costruzione. Quindi è il momento della configurazione.

Durante l'analisi ci saranno utili alcune variabili, ad esempio la chiave per PVS-Studio, che sarebbe indesiderabile specificare nel file di configurazione. Quindi aggiungiamo variabili di ambiente utilizzando le impostazioni di build in Travis CI:

Come configurare PVS-Studio in Travis CI utilizzando l'esempio di un emulatore di console di gioco PSP
Abbiamo bisogno di:

  • PVS_USERNAME - nome utente
  • PVS_KEY - chiave
  • MAIL_USER - email che verrà utilizzata per inviare il report
  • MAIL_PASSWORD: password e-mail

Gli ultimi due sono facoltativi. Questi verranno utilizzati per inviare i risultati tramite posta. Se vuoi distribuire il report in altro modo non è necessario indicarli.

Quindi, abbiamo aggiunto le variabili d'ambiente di cui abbiamo bisogno:

Come configurare PVS-Studio in Travis CI utilizzando l'esempio di un emulatore di console di gioco PSP
Ora creiamo un file .travis.yml e posizionarlo nella radice del progetto. PPSSPP aveva già un file di configurazione per Travis CI, tuttavia era troppo grande e del tutto inadatto all'esempio, quindi abbiamo dovuto semplificarlo notevolmente e lasciare solo gli elementi di base.

Per prima cosa indichiamo la lingua, la versione di Ubuntu Linux che vogliamo utilizzare nella macchina virtuale e i pacchetti necessari per la build:

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'

Tutti i pacchetti elencati sono necessari esclusivamente per PPSSPP.

Ora indichiamo la matrice di assemblaggio:

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

Qualcosa in più sulla sezione matrice. In Travis CI, ci sono due modi per creare opzioni di build: il primo è specificare un elenco di compilatori, tipi di sistema operativo, variabili di ambiente, ecc., dopodiché viene generata una matrice di tutte le possibili combinazioni; la seconda è un'indicazione esplicita della matrice. Naturalmente puoi combinare questi due approcci e aggiungere un caso unico o, al contrario, escluderlo utilizzando la sezione escludere. Puoi leggere di più a riguardo in Documentazione Travis CI.

Non resta che fornire le istruzioni di montaggio specifiche del progetto:

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 ti consente di aggiungere i tuoi comandi per le varie fasi della vita di una macchina virtuale. Sezione prima_installazione eseguito prima di installare i pacchetti. Poi install, che segue l'installazione dei pacchetti dall'elenco componenti aggiuntivi.aptche abbiamo sopra indicato. L'assemblea vera e propria si svolge in copione. Se tutto è andato bene allora ci ritroviamo dentro dopo_successo (è in questa sezione che eseguiremo l'analisi statica). Questi non sono tutti i passaggi che possono essere modificati, se ne hai bisogno di più, dovresti dare un'occhiata Documentazione Travis CI.

Per facilità di lettura, i comandi sono stati inseriti in uno script separato .travis.sh, che viene posizionato nella radice del progetto.

Quindi abbiamo il seguente file .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

Prima di installare i pacchetti, aggiorneremo i sottomoduli. Ciò è necessario per creare PPSSPP. Aggiungiamo la prima funzione a .travis.sh (notare l'estensione):

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

Ora arriviamo direttamente alla configurazione dell'avvio automatico di PVS-Studio in Travis CI. Per prima cosa dobbiamo installare il pacchetto PVS-Studio sul sistema:

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
}

All'inizio della funzione travis_install installiamo i compilatori di cui abbiamo bisogno utilizzando variabili d'ambiente. Quindi se la variabile $PVS_ANALISI memorizza valore (lo abbiamo indicato nella sezione ENV durante la configurazione della matrice di build), installiamo il pacchetto pvs-studio. Oltre a questo vengono indicati anche i pacchetti libio-socket-ssl-perl и libnet-ssleay-perl, tuttavia, sono necessari per l'invio tramite posta dei risultati, quindi non sono necessari se hai scelto un altro metodo per consegnare il rapporto.

Funzione download_estrai scarica e decomprime l'archivio specificato:

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

È il momento di mettere insieme il progetto. Questo accade nella sezione copione:

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
}

In realtà, questa è una configurazione originale semplificata, ad eccezione di queste righe:

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

In questa sezione di codice impostiamo for cMake flag per esportare i comandi di compilazione. Ciò è necessario per un analizzatore di codice statico. Puoi leggere di più a riguardo nell’articolo “Come eseguire PVS-Studio su Linux e macOS«.

Se l'assemblea ha avuto successo, allora arriviamo a dopo_successo, dove eseguiamo l'analisi statica:

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
}

Diamo un'occhiata più da vicino alle seguenti righe:

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

La prima riga genera un file di licenza dal nome utente e dalla chiave che abbiamo specificato all'inizio durante la configurazione delle variabili di ambiente Travis CI.

La seconda riga avvia direttamente l'analisi. Bandiera -J imposta il numero di thread per l'analisi, flag -l indica licenza, bandiera -o definisce il file per l'output dei log e il flag -disableLicenseExpirationCheck richiesto per le versioni di prova, poiché per impostazione predefinita pvs-studio-analizzatore avviserà l'utente che la licenza sta per scadere. Per evitare che ciò accada, è possibile specificare questo flag.

Il file di registro contiene output non elaborato che non può essere letto senza conversione, quindi è necessario prima rendere leggibile il file. Passiamo i log convertitore di ploge l'output è un file html.

In questo esempio, ho deciso di inviare report tramite posta utilizzando il comando invia una email.

Di conseguenza, abbiamo ottenuto il seguente file .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;

Ora è il momento di inviare le modifiche al repository git, dopodiché Travis CI eseguirà automaticamente la build. Fare clic su "ppsspp" per accedere ai rapporti di compilazione:

Come configurare PVS-Studio in Travis CI utilizzando l'esempio di un emulatore di console di gioco PSP
Vedremo una panoramica della build attuale:

Come configurare PVS-Studio in Travis CI utilizzando l'esempio di un emulatore di console di gioco PSP
Se la build viene completata con successo, riceveremo un'e-mail con i risultati dell'analisi statica. Naturalmente, l'invio tramite posta non è l'unico modo per ricevere un rapporto. Puoi scegliere qualsiasi metodo di implementazione. Ma è importante ricordare che una volta completata la compilazione non sarà possibile accedere ai file della macchina virtuale.

Riepilogo degli errori

Abbiamo completato con successo la parte più difficile. Ora assicuriamoci che tutti i nostri sforzi siano valsi la pena. Vediamo alcuni punti interessanti del rapporto di analisi statica che mi è arrivato via mail (non per niente lo avevo indicato).

Ottimizzazione pericolosa

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

Avviso PVS-Studio: V597 Il compilatore potrebbe eliminare la chiamata alla funzione "memset", che viene utilizzata per svuotare il buffer "sum". La funzione RtlSecureZeroMemory() dovrebbe essere utilizzata per cancellare i dati privati. sha1.cpp 325

Questo pezzo di codice si trova nel modulo di hashing sicuro, tuttavia contiene una grave falla di sicurezza (CWE-14). Diamo un'occhiata all'elenco degli assembly che viene generato durante la compilazione della versione Debug:

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

Tutto è in ordine e funzionante set di meme viene eseguito, sovrascrivendo così i dati importanti nella RAM, tuttavia, non rallegrarci ancora. Diamo un'occhiata all'elenco degli assembly della versione Release con ottimizzazione:

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

Come si può vedere dal listato, il compilatore ha ignorato la chiamata set di meme. Ciò è dovuto al fatto che nella funzione sha1 dopo la chiamata set di meme niente più riferimento alla struttura ctx. Pertanto, il compilatore non vede alcun motivo di sprecare tempo del processore sovrascrivendo la memoria che non verrà utilizzata in futuro. Puoi risolvere questo problema utilizzando la funzione RtlSecureZeroMemory o un simile a lei.

correggere:

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

Confronto inutile

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

Avviso PVS-Studio: V547 L'espressione 'leftvol >= 0' è sempre vera. sceAudio.cpp 120

Presta attenzione al ramo else per il primo if. Il codice verrà eseguito solo se tutte le condizioni volsinistra > 0xFFFF || voldestra > 0xFFFF || volsinistra < 0 || voldestra < 0 risulterà falso. Pertanto, otteniamo le seguenti affermazioni, che saranno vere per il ramo else: volsinistra <= 0xFFFF, voldestra <= 0xFFFF, volsinistra >= 0 и voldestra >= 0. Notare le ultime due affermazioni. Ha senso verificare quale sia una condizione necessaria per l'esecuzione di questo pezzo di codice?

Quindi possiamo rimuovere in sicurezza queste istruzioni condizionali:

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

Un altro scenario. Dietro queste condizioni ridondanti si nasconde qualche tipo di errore. Forse non hanno controllato cosa fosse richiesto.

Ctrl+C Ctrl+V colpisce ancora

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 Sono presenti sottoespressioni identiche "!Memory::IsValidAddress(psmfData)" a sinistra e a destra di "||" operatore. scePsmf.cpp 703

Presta attenzione al controllo interno if. Non trovi strano che controlliamo se l'indirizzo è valido? psmfData, due volte tanto? Quindi mi sembra strano... In realtà si tratta, ovviamente, di un errore di battitura e l'idea era di controllare entrambi i parametri di input.

Opzione corretta:

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

Variabile dimenticata

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

Avviso PVS-Studio: V547 L'espressione 'dimensione == 8' è sempre falsa. syn-att.c 195

Questo errore si trova nella cartella ext, quindi non molto rilevante per il progetto, ma il bug è stato trovato prima che me ne accorgessi, quindi ho deciso di lasciarlo. Dopotutto, questo articolo non riguarda la revisione degli errori, ma l'integrazione con Travis CI e non è stata effettuata alcuna configurazione dell'analizzatore.

Переменная Taglia viene inizializzato da una costante, tuttavia non viene utilizzato affatto nel codice, nemmeno dall'operatore if, che, ovviamente, dà falso verificando le condizioni, perché, come ricordiamo, Taglia uguale a zero. Anche i controlli successivi non hanno senso.

Apparentemente l'autore del frammento di codice ha dimenticato di sovrascrivere la variabile Taglia prima di ciò.

Fermare

Qui è dove probabilmente finiremo con gli errori. Lo scopo di questo articolo è dimostrare il lavoro di PVS-Studio insieme a Travis CI e non analizzare il progetto nel modo più approfondito possibile. Se vuoi errori più grandi e più belli, puoi sempre ammirarli qui :).

conclusione

L'utilizzo dei servizi Web per creare progetti insieme alla pratica dell'analisi incrementale consente di individuare molti problemi immediatamente dopo l'unione del codice. Tuttavia, una build potrebbe non essere sufficiente, quindi l'impostazione dei test insieme all'analisi statica migliorerà significativamente la qualità del codice.

Link utili

Come configurare PVS-Studio in Travis CI utilizzando l'esempio di un emulatore di console di gioco PSP

Se desideri condividere questo articolo con un pubblico di lingua inglese, utilizza il link di traduzione: Maxim Zvyagintsev. Come configurare PVS-Studio in Travis CI utilizzando l'esempio dell'emulatore di console di gioco PSP.

Fonte: habr.com

Aggiungi un commento