Como configurar o PVS-Studio no Travis CI usando o exemplo de um emulador de console de jogos PSP

Como configurar o PVS-Studio no Travis CI usando o exemplo de um emulador de console de jogos PSP
Travis CI é um serviço web distribuído para construção e teste de software que usa GitHub como hospedagem de código-fonte. Além dos cenários operacionais acima, você pode adicionar o seu próprio graças às extensas opções de configuração. Neste artigo iremos configurar o Travis CI para funcionar com PVS-Studio usando o exemplo de código PPSSPP.

Introdução

Travis C.I. é um serviço web para construção e teste de software. Geralmente é usado em conjunto com práticas de integração contínua.

PPSSPP — Emulador de console de jogos PSP. O programa é capaz de emular o lançamento de qualquer jogo a partir de imagens de disco destinadas ao Sony PSP. O programa foi lançado em 1º de novembro de 2012. PPSSPP é licenciado sob GPL v2. Qualquer um pode fazer melhorias código fonte do projeto.

Estúdio PVS — um analisador de código estático para procurar erros e vulnerabilidades potenciais no código do programa. Neste artigo, para variar, lançaremos o PVS-Studio não localmente na máquina do desenvolvedor, mas na nuvem, e procuraremos erros no PPSSPP.

Configurando o Travis CI

Precisaremos de um repositório no GitHub, onde está localizado o projeto que precisamos, bem como uma chave para o PVS-Studio (você pode obter chave de teste ou gratuito para projetos de código aberto).

Vamos ao site Travis C.I.. Após autorização usando sua conta GitHub, veremos uma lista de repositórios:

Como configurar o PVS-Studio no Travis CI usando o exemplo de um emulador de console de jogos PSP
Para o teste, bifurquei o PPSSPP.

Ativamos o repositório que queremos coletar:

Como configurar o PVS-Studio no Travis CI usando o exemplo de um emulador de console de jogos PSP
No momento, Travis CI não pode construir nosso projeto porque não há instruções para construção. Então é hora de configuração.

Durante a análise, algumas variáveis ​​​​serão úteis para nós, por exemplo, a chave do PVS-Studio, que seria indesejável especificar no arquivo de configuração. Então, vamos adicionar variáveis ​​de ambiente usando as configurações de compilação no Travis CI:

Como configurar o PVS-Studio no Travis CI usando o exemplo de um emulador de console de jogos PSP
Precisamos de:

  • PVS_USERNAME - nome de usuário
  • PVS_KEY - chave
  • MAIL_USER – e-mail que será utilizado para envio do relatório
  • MAIL_PASSWORD - senha do e-mail

Os dois últimos são opcionais. Eles serão usados ​​para enviar resultados por correio. Caso queira distribuir o relatório de outra forma, não é necessário indicá-los.

Então, adicionamos as variáveis ​​de ambiente que precisamos:

Como configurar o PVS-Studio no Travis CI usando o exemplo de um emulador de console de jogos PSP
Agora vamos criar um arquivo .travis.yml e coloque-o na raiz do projeto. O PPSSPP já possuía um arquivo de configuração para o Travis CI, porém ele era muito grande e totalmente inadequado para o exemplo, então tivemos que simplificá-lo bastante e deixar apenas os elementos básicos.

Primeiramente vamos indicar o idioma, a versão do Ubuntu Linux que queremos utilizar na máquina virtual e os pacotes necessários para a compilação:

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'

Todos os pacotes listados são necessários exclusivamente para PPSSPP.

Agora indicamos a matriz de montagem:

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

Um pouco mais sobre a seção matriz. No Travis CI, existem duas maneiras de criar opções de construção: a primeira é especificar uma lista de compiladores, tipos de sistema operacional, variáveis ​​de ambiente, etc., após a qual é gerada uma matriz de todas as combinações possíveis; a segunda é uma indicação explícita da matriz. Claro, você pode combinar essas duas abordagens e adicionar um caso único ou, pelo contrário, excluí-lo usando a seção excluir. Você pode ler mais sobre isso em Documentação do Travis CI.

Resta apenas fornecer instruções de montagem específicas do projeto:

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

O Travis CI permite adicionar seus próprios comandos para vários estágios da vida de uma máquina virtual. Seção antes_instalar executado antes de instalar os pacotes. Então instalar, que segue a instalação dos pacotes da lista complementos.aptque indicamos acima. A assembléia propriamente dita ocorre em escrita. Se tudo correu bem, então nos encontraremos em depois_sucesso (é nesta seção que executaremos a análise estática). Estas não são todas as etapas que podem ser modificadas; se precisar de mais, você deve procurar Documentação do Travis CI.

Para facilitar a leitura, os comandos foram colocados em um script separado .travis.sh, que é colocado na raiz do projeto.

Então temos o seguinte arquivo .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

Antes de instalar os pacotes, atualizaremos os submódulos. Isso é necessário para construir o PPSSPP. Vamos adicionar a primeira função a .travis.sh (observe a extensão):

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

Agora chegamos diretamente à configuração do lançamento automático do PVS-Studio no Travis CI. Primeiro precisamos instalar o pacote PVS-Studio no 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
}

No início da função travis_install instalamos os compiladores necessários usando variáveis ​​de ambiente. Então se a variável $PVS_ANALYZE armazena valor Sim (indicamos isso na seção env durante a configuração da matriz de construção), instalamos o pacote estúdio pvs. Além disso, também são indicados pacotes libio-socket-ssl-perl и libnet-ssleay-perl, no entanto, eles são obrigatórios para o envio de resultados, portanto não serão necessários se você tiver escolhido outro método para entregar seu relatório.

Função download_extrato baixa e descompacta o arquivo especificado:

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

É hora de montar o projeto. Isso acontece na seção escrita:

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
}

Na verdade, esta é uma configuração original simplificada, exceto por estas linhas:

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

Nesta seção do código definimos para cmake sinalizador para exportar comandos de compilação. Isso é necessário para um analisador de código estático. Você pode ler mais sobre isso no artigo “Como executar o PVS-Studio no Linux e macOS".

Se a montagem foi bem sucedida, então chegamos a depois_sucesso, onde realizamos análise estática:

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
}

Vamos dar uma olhada mais de perto nas seguintes linhas:

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

A primeira linha gera um arquivo de licença a partir do nome de usuário e da chave que especificamos no início ao configurar as variáveis ​​de ambiente do Travis CI.

A segunda linha inicia a análise diretamente. Bandeira -j define o número de threads para análise, sinalizador -eu indica licença, bandeira -o define o arquivo para saída de logs e o sinalizador -disableLicenseExpirationCheck obrigatório para versões de teste, já que por padrão analisador de estúdio pvs avisará o usuário que a licença está prestes a expirar. Para evitar que isso aconteça, você pode especificar este sinalizador.

O arquivo de log contém saída bruta que não pode ser lida sem conversão, portanto, primeiro você deve tornar o arquivo legível. Vamos passar os logs conversor plog, e a saída é um arquivo html.

Neste exemplo, decidi enviar relatórios por correio usando o comando enviar email.

Como resultado, obtivemos o seguinte arquivo .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;

Agora é hora de enviar as alterações para o repositório git, após o qual o Travis CI executará automaticamente a compilação. Clique em “ppsspp” para acessar os relatórios de construção:

Como configurar o PVS-Studio no Travis CI usando o exemplo de um emulador de console de jogos PSP
Veremos uma visão geral da compilação atual:

Como configurar o PVS-Studio no Travis CI usando o exemplo de um emulador de console de jogos PSP
Se a construção for concluída com sucesso, receberemos um email com os resultados da análise estática. É claro que o envio pelo correio não é a única forma de receber um relatório. Você pode escolher qualquer método de implementação. Mas é importante lembrar que após a conclusão da compilação não será possível acessar os arquivos da máquina virtual.

Resumo de erros

Concluímos com sucesso a parte mais difícil. Agora vamos ter certeza de que todos os nossos esforços valem a pena. Vejamos alguns pontos interessantes do relatório de análise estática que me chegou pelo correio (não foi à toa que o indiquei).

Otimização perigosa

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

Aviso do PVS-Studio: V597 O compilador pode excluir a chamada de função 'memset', que é usada para liberar o buffer 'sum'. A função RtlSecureZeroMemory() deve ser usada para apagar os dados privados. sha1.cpp 325

Este trecho de código está localizado no módulo de hashing seguro, porém contém uma falha grave de segurança (CWE-14). Vejamos a listagem do assembly gerada ao compilar a versão Debug:

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

Tudo está em ordem e a função conjunto memset é executado, substituindo assim dados importantes na RAM, no entanto, não se alegre ainda. Vejamos a listagem de montagem da versão Release com otimização:

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

Como pode ser visto na listagem, o compilador ignorou a chamada conjunto memset. Isto se deve ao fato de que na função sha1 depois da chamada conjunto memset não há mais referência à estrutura ctx. Portanto, o compilador não vê sentido em desperdiçar tempo do processador sobrescrevendo memória que não será usada no futuro. Você pode corrigir isso usando a função RtlSecureZeroMemória ou similar dela.

Correto:

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

Comparação desnecessária

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

Aviso do PVS-Studio: V547 A expressão 'leftvol >= 0' é sempre verdadeira. sceAudio.cpp 120

Preste atenção ao ramo else pela primeira vez if. O código será executado somente se todas as condições vol esquerdo > 0xFFFF || vol direito> 0xFFFF || vol esquerdo < 0 || vol direito < 0 acabará sendo falso. Portanto, obtemos as seguintes afirmações, que serão verdadeiras para o ramo else: vol esquerdo <= 0xFFFF, vol direito <= 0xFFFF, vol esquerdo >= 0 и vol direito >= 0. Observe as duas últimas afirmações. Faz sentido verificar qual é a condição necessária para a execução deste trecho de código?

Portanto, podemos remover com segurança estas instruções condicionais:

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

Outro cenário. Há algum tipo de erro oculto por trás dessas condições redundantes. Talvez eles não tenham verificado o que era necessário.

Ctrl+C Ctrl+V Contra-ataca

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 Existem subexpressões idênticas '!Memory::IsValidAddress(psmfData)' à esquerda e à direita de '||' operador. scePsmf.cpp 703

Preste atenção ao cheque dentro if. Você não acha estranho verificarmos se o endereço é válido? psmfDados, o dobro? Então isso me parece estranho... Na verdade, isso é, claro, um erro de digitação, e a ideia era verificar os dois parâmetros de entrada.

Opção correta:

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

Variável esquecida

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

Aviso do PVS-Studio: V547 A expressão 'tamanho == 8' é sempre falsa. syn-att.c 195

Este erro está localizado na pasta ext, então não é realmente relevante para o projeto, mas o bug foi encontrado antes que eu percebesse, então decidi deixá-lo. Afinal, este artigo não se trata de revisão de erros, mas sim de integração com Travis CI, e nenhuma configuração do analisador foi realizada.

Variável tamanho é inicializado por uma constante, no entanto, não é usado no código, até o operador if, o que, claro, dá falso enquanto verificamos as condições, porque, como lembramos, tamanho igual a zero. As verificações subsequentes também não fazem sentido.

Aparentemente, o autor do fragmento de código esqueceu de sobrescrever a variável tamanho antes disso.

Dê um basta

É aqui que provavelmente terminaremos com os erros. O objetivo deste artigo é demonstrar o trabalho do PVS-Studio em conjunto com o Travis CI, e não analisar o projeto da forma mais aprofundada possível. Se você quer erros maiores e mais bonitos, você sempre pode admirá-los aqui :).

Conclusão

Usar serviços da web para construir projetos junto com a prática de análise incremental permite encontrar muitos problemas imediatamente após a fusão do código. No entanto, uma construção pode não ser suficiente, portanto, configurar os testes junto com a análise estática melhorará significativamente a qualidade do código.

Links úteis

Como configurar o PVS-Studio no Travis CI usando o exemplo de um emulador de console de jogos PSP

Se você quiser compartilhar este artigo com um público que fala inglês, use o link de tradução: Maxim Zvyagintsev. Como configurar o PVS-Studio no Travis CI usando o exemplo do emulador de console de jogos PSP.

Fonte: habr.com

Adicionar um comentário