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

PPSSPP

O Travis CI é um serviço da Web distribuído para criação e teste de software que usa o GitHub como hospedagem de código-fonte. Além dos cenários acima, você pode adicionar seus próprios, graças às extensas opções de configuração. Neste artigo, configuraremos o Travis CI para funcionar com o PVS-Studio usando o código PPSSPP de exemplo.

1. Introdução


O Travis CI é um serviço da Web para criação e teste de software. Geralmente é usado em conjunto com a prática 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 projetadas para a Sony PSP. O programa foi lançado em 1 de novembro de 2012. O PPSSPP é licenciado sob a GPL v2. Qualquer pessoa pode fazer melhorias no código fonte do projeto .

O PVS-Studio é um analisador de código estático para procurar erros e possíveis vulnerabilidades no código do programa. Neste artigo, para variar, iniciaremos o PVS-Studio não localmente na máquina do desenvolvedor, mas na nuvem e procuraremos erros no PPSSPP.

Configurar o Travis CI


Vamos precisar de um repositório no GitHub, onde reside o projeto, bem como uma chave para o PVS-Studio (você pode obter uma chave de avaliação ou gratuitamente para projetos de código aberto ).

Vamos ao site do Travis CI . Após a autorização usando a conta do GitHub, teremos uma lista de repositórios:


Para o teste, peguei o PPSSPP.

Ativamos o repositório que queremos coletar:


No momento, o Travis CI não pode montar nosso projeto, pois não há instruções para montagem. Portanto, é 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. Portanto, adicione variáveis ​​de ambiente usando configurações de construção no Travis CI:


Vamos precisar de:

  • PVS_USERNAME - nome de usuário
  • PVS_KEY - chave
  • MAIL_USER - e-mail que será usado para enviar o relatório
  • MAIL_PASSWORD - senha de e-mail

Os dois últimos são opcionais. Eles serão usados ​​para enviar os resultados por correio. Se você deseja enviar o relatório de outra maneira, não é necessário especificá-los.

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


Agora crie um arquivo .travis.yml e coloque-o na raiz do projeto. O arquivo de configuração para o Travis CI já existia no PPSSPP, no entanto, era muito grande e era completamente inapropriado para o exemplo, então tive que simplificá-lo significativamente e deixar apenas os elementos básicos.

Primeiro, indicamos o idioma, a versão do Ubuntu Linux que queremos usar na máquina virtual e os pacotes necessários para a montagem:

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 apenas para PPSSPP.

Agora especifique 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 da matriz . No Travis CI, existem duas maneiras de criar opções de criação: a primeira é listar os compiladores, tipos de sistemas operacionais, variáveis ​​de ambiente etc., após o qual é gerada uma matriz de todas as combinações possíveis; o segundo é uma indicação explícita da matriz. Obviamente, você pode combinar essas duas abordagens e adicionar um caso exclusivo ou, alternativamente, excluir usando a seção excluir . Leia mais sobre isso na documentação do Travis CI .

Resta especificar 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 suas próprias equipes para vários estágios da vida útil da máquina virtual. A seção before_install é executada antes da instalação dos pacotes. Em seguida, instale , que segue a instalação dos pacotes da lista addons.apt , que indicamos acima. A montagem em si ocorre em um script . Se tudo correu bem, entramos no after_success (é nesta seção que executaremos a análise estática). Essas não são todas as etapas que podem ser modificadas; se você precisar de mais, consulte a documentação do Travis CI .

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

Portanto, 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, atualize os submódulos. Isso é necessário para criar o PPSSPP. Adicione a primeira função ao .travis.sh (preste atenção à extensão):

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

Agora chegamos diretamente à configuração do PVS-Studio para iniciar automaticamente 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 o valor Yes (nós o especificamos na seção env durante a configuração da matriz de montagem), instalamos o pacote pvs-studio . Além disso, os pacotes libio-socket-ssl-perl e libnet-ssleay-perl também são indicados; no entanto, eles são necessários para enviar os resultados por email, portanto, não são necessários se você escolher um método diferente de entrega de relatórios.

A função download_extract baixa e descompacta o arquivo especificado:

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

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

 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 } 

De fato, esta é uma configuração original simplificada, com exceção das seguintes linhas:

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

Nesta seção do código, definimos o sinalizador de exportação dos comandos de compilação para o cmake . 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, nos encontramos em after_success , onde realizamos uma 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 mail@domain.com \ -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 considerar as seguintes linhas com mais detalhes:

 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. O sinalizador -j <N> define o número de fluxos para análise, o sinalizador -l <file> indica a licença, o sinalizador -o <file> define o arquivo para a saída do log e o sinalizador -disableLicenseExpirationCheck é necessário para versões de avaliação, pois, por padrão, pvs- O analisador de estúdio avisará o usuário sobre o vencimento da licença. Para evitar isso, você pode especificar esse sinalizador.

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

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

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 mail@domain.com \ -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; 

É hora de adicionar as alterações ao repositório git, após as quais o Travis CI iniciará automaticamente a compilação. Clique em "ppsspp" para acessar os relatórios de montagem:


Veremos uma visão geral da montagem atual:


Se a montagem for concluída com êxito, receberemos um email com os resultados da análise estática. Obviamente, a correspondência não é a única maneira de obter um relatório. Você pode escolher qualquer método de implementação. Mas é importante lembrar que, após a conclusão da montagem, será impossível acessar os arquivos da máquina virtual.

Resumo dos erros


Concluímos com sucesso a parte mais difícil. Agora vamos garantir que todos os nossos esforços sejam justificados. Considere alguns pontos interessantes do relatório sobre análise estática, que me foi enviado por correio (não por acaso).

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

PVS-Studio Warning: 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 particulares. sha1.cpp 325

Esse fragmento de código está localizado no módulo de hash seguro, no entanto, contém uma falha de segurança grave ( CWE-14 ). Considere a lista do assembler que é 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 perfeita ordem, e a função memset é executada, sobrescrevendo dados importantes na RAM, no entanto, até agora não se regozijam. Considere a lista do assembler da versão Release com otimização:

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

Como pode ser visto na lista, o compilador ignorou a chamada memset . Isso ocorre porque a função sha1 não chama mais a estrutura ctx após chamar o memset . Portanto, o compilador não vê o ponto de perder tempo do processador substituindo a memória não utilizada no futuro. Você pode corrigir isso usando a função RtlSecureZeroMemory ou similar .

Corretamente:

 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: A expressão V547 'leftvol> = 0' sempre é verdadeira. sceAudio.cpp 120

Preste atenção no ramo else para o primeiro if . O código será executado apenas se todas as condições forem deixadas vol> 0xFFFF || rightvol> 0xFFFF || leftvol <0 || rightvol <0 será falso. Portanto, obtemos as seguintes instruções que serão verdadeiras para o ramo else: leftvol <= 0xFFFF , rightvol <= 0xFFFF , leftvol> = 0 e rightvol> = 0 . Preste atenção nas duas últimas afirmações. Faz sentido verificar o que é um pré-requisito para executar este pedaço de código?

Portanto, podemos remover calmamente essas 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. Por trás dessas condições redundantes há algum tipo de erro. Talvez eles não tenham verificado o que é 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 sub-expressões idênticas '! Memory :: IsValidAddress (psmfData)' à esquerda e à direita da '||' operador. scePsmf.cpp 703

Preste atenção na verificação interna, se . Não lhe parece estranho verificarmos se o endereço psmfData é válido duas vezes mais? Então, isso me parece estranho ... De fato, temos, é claro, um erro de digitação, e a ideia era verificar os dois parâmetros de entrada.

A 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: A expressão V547 'size == 8' é sempre falsa. syn-att.c 195

Este erro está localizado na pasta ext , portanto, não se aplica ao projeto, mas o erro foi encontrado antes que eu percebesse isso, então decidi deixá-lo. Ainda assim, este artigo não trata de uma revisão de erros, mas de integração com o Travis CI, e nenhuma configuração do analisador foi realizada.

O tamanho da variável é inicializado por uma constante, no entanto, não é usado de todo no código, até a instrução if , que, é claro, retorna false ao verificar a condição, porque, como lembramos, o tamanho é zero. As verificações subsequentes também não fazem sentido.

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

Parar


Nisto, talvez, acabemos com erros. O objetivo deste artigo é demonstrar o trabalho do PVS-Studio em conjunto com o Travis CI, e não analisar o projeto da maneira mais completa possível. Se você quer erros cada vez mais bonitos, sempre pode admirá-los aqui :).

Conclusão


O uso de serviços da Web para criar projetos em conjunto com a prática da análise incremental pode detectar muitos problemas imediatamente após a mesclagem de código. É verdade que um único assembly pode não ser suficiente, portanto, configurar o teste em conjunto com a análise estática melhorará significativamente a qualidade do código.

Links úteis





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

Source: https://habr.com/ru/post/pt464645/


All Articles