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:
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:
É 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;
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;
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 .