O Travis CI é um serviço da Web distribuído para criação e teste de software que usa o GitHub como um serviço de hospedagem de código-fonte. Além dos scripts acima, você pode adicionar seus próprios, graças às extensas opções de configuração. Neste artigo, configuraremos o Travis CI para trabalhar com o PVS-Studio pelo exemplo do código PPSSPP.
1. Introdução
O Travis CI é um serviço da Web para criação e teste de software. Geralmente é usado em combinação com a prática de integração contínua.
PPSSPP é um emulador do console de jogos PSP. O programa é capaz de emular o lançamento de qualquer jogo com imagens de discos projetados para a Sony PSP. O programa foi lançado em 1 de novembro de 2012. O PPSSPP é distribuído sob licença GPL v2. Qualquer pessoa pode fazer melhorias no
código fonte do projeto.
PVS-Studio - analisador de código estático para pesquisar erros e possíveis vulnerabilidades no código do programa. Neste artigo, lançaremos o PVS-Studio na nuvem em vez de localmente no computador do desenvolvedor para diversos fins e procuraremos erros no PPSSPP.
Configuração do Travis ci
Nós precisaremos de um repositório no GitHub onde o projeto de que precisamos está localizado, bem como uma chave para o PVS-Studio (você pode obter uma
chave de avaliação ou
gratuita para projetos de código aberto ).
Vamos ao site do
Travis CI . Após a autorização com a ajuda da conta do GitHub, teremos uma lista de repositórios:
Para o teste, fiz um garfo PPSSPP.
Ativamos o repositório que queremos construir:
No momento, o Travis CI não pode criar nosso projeto porque não há instruções para construí-lo. É por isso que está na hora da configuração.
Durante a análise, precisaremos de algumas variáveis, 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, configurando a 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 precisa especificá-los.
Então, adicionamos as variáveis de ambiente que precisamos:
Agora vamos criar um arquivo
.travis.yml e colocá-lo na raiz do projeto. O PPSSPP já tinha um arquivo de configuração para o Travis CI, no entanto, era muito grande e não adequado para o exemplo, portanto, tivemos que simplificá-lo e deixar apenas os elementos básicos.
Primeiro, vamos especificar a linguagem de programação, a versão do Ubuntu Linux que queremos usar na máquina virtual e os pacotes necessários para a construçã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 adicionados são necessários apenas para o PPSSPP.
Agora especifique a matriz de construção:
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 construção: a primeira é especificar compiladores, tipos de sistemas operacionais, variáveis de ambiente etc. com a lista, após a qual a matriz de todas as combinações possíveis será gerada; o segundo é uma indicação explícita da matriz. Obviamente, 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 na
documentação do
Travis CI .
A única coisa a fazer é especificar instruções de construção 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 diferentes estágios da vida 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 especificamos acima. A construção em si ocorre no
script . Se tudo tiver sido bem-sucedido, entraremos em
after_success (é aqui que iniciaremos a análise estática). Essas não são todas as etapas que você pode modificar; se precisar de mais, consulte a
documentação no Travis CI .
Para facilitar a leitura, os comandos foram colocados em 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, vamos atualizar os submódulos. Isso é necessário para criar PPSSPPs. Adicione a primeira função ao
.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 o valor de
Yes (nós a especificamos na seção
env ao configurar a matriz de construção), instalamos o pacote
pvs-studio . Além disso, também existem
pacotes libio-socket-ssl-perl e
libnet-ssleay-perl , mas eles são necessários para enviar os resultados por email, portanto, não são necessários se você tiver escolhido outra forma de entrega de relatório.
A função download_extract baixa e descompacta o arquivo especificado:
download_extract() { aria2c -x 16 $1 -o $2 tar -xf $2 }
É hora de construir 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, exceto pelas 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 do comando de compilação para
cmake . Isso é necessário para um analisador de código estático. Você pode ler mais sobre isso no artigo "
Como iniciar o PVS-Studio no Linux e macOS ".
Se a construção foi bem-sucedida, chegaremos ao
after_success, onde executaremos a 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 em 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 o arquivo de licença a partir do nome do usuário e da chave que especificamos no início da configuração das variáveis de ambiente do Travis CI.
A segunda linha inicia a análise diretamente. O sinalizador
-j <N> define o número de threads de análise, o sinalizador
-l <file> define a licença, o sinalizador
-o <file> define o arquivo para
gerar os logs e o sinalizador -
disableLicenseExpirationCheck é necessário para versões de avaliação , porque, por padrão, o
pvs-studio-analyzer avisa o usuário sobre a expiração iminente da licença. Para impedir que isso aconteça, você pode especificar este sinalizador.
O arquivo de log contém uma saída não processada que não pode ser lida sem conversão; portanto, primeiro você precisa tornar o arquivo legível. Vamos executar os logs através do
plog-converter e obter um arquivo html na saída.
Neste exemplo, decidi enviar relatórios por email usando o comando
sendemail .
O resultado foi 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, e o Travis CI iniciará automaticamente a compilação. Clique em "ppsspp" para criar relatórios:
Veremos uma visão geral da versão atual:
Se a compilação for concluída com êxito, receberemos um email com os resultados da análise estática. Obviamente, enviar por correio não é a única maneira de obter o relatório. Você pode escolher qualquer método de implementação. Mas é importante lembrar que será impossível obter acesso aos arquivos da máquina virtual após a conclusão da compilação.
Breve visão geral dos erros
Concluímos com sucesso a parte mais difícil. Vamos agora garantir que todos os nossos esforços tenham sido justificados. Vamos considerar alguns pontos interessantes do relatório de análise estática que me foi enviado por correio (não foi à toa que eu o especifiquei).
Otimizações perigosas
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 ) ); }
O aviso do PVS-Studio:
V597 O compilador pode excluir a chamada de função 'memset', 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, mas contém um sério defeito de segurança (
CWE-14 ). Vamos considerar a lista do assembler que é gerada quando a versão Debug compila:
; Line 355 mov r8d, 20 xor edx, edx lea rcx, QWORD PTR sum$[rsp] call memset ; Line 356
Está tudo bem e a função
memset é executada, limpando assim dados importantes na RAM, mas você ainda não deveria estar feliz. Vamos considerar a lista de montadores da versão Release com otimização:
; 354 : ; 355 : memset( sum, 0, sizeof( sum ) ); ; 356 :}
Como você pode ver na lista, o compilador ignorou a chamada do
memset . Está relacionado ao fato de que a função
sha1 não chama mais a estrutura
ctx após chamar o
memset . É por isso que o compilador não vê sentido em perder tempo do processador, substituindo a memória que não está sendo usada no futuro. Você pode corrigi-lo usando a função
RtlSecureZeroMemory ou uma função
semelhante .
Direita:
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;
O 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
restarem vol> 0xFFFFF || rightvol> 0xFFFF || leftvol <0 || rightvol <0 é falso. Portanto, obtemos as seguintes instruções que serão verdadeiras para o ramo else:
leftvol <= 0xFFFFF, rightvol <= 0xFFFFF, leftvol> = 0 e rightvol> = 0 . Preste atenção nas duas últimas afirmações. É razoável verificar qual é a condição necessária para a execução desse fragmento de código?
Portanto, podemos excluir com calma esses operadores 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 erro. Talvez tenhamos verificado o que não é o que precisamos ...
Ctrl + C Ctrl + V ataca novamente
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
Observe a verificação interna
se . Não lhe parece estranho verificar se o endereço
psmfData é válido duas vezes mais? Então eu acho estranho ... Na verdade, temos uma impressão errada diante de nós, é claro, e a ideia era verificar os dois parâmetros de entrada.
A variante 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"); } .... }
O 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 o notasse, então decidi mantê-lo. Ainda assim, este artigo não trata de revisão de erros, mas de integração com o Travis CI e nenhuma configuração do analisador foi realizada.
A variável
size é inicializada com uma constante, mas não é usada no código até o operador
if que, é claro, gera informações
falsas ao verificar a condição, porque, como lembramos, o
tamanho é igual a zero. As verificações subsequentes também não fazem sentido.
Aparentemente, o autor do fragmento de código esqueceu de substituir a variável
size antes disso.
Parar
É aí que vamos parar com os erros. O objetivo deste artigo é demonstrar como o PVS-Studio funciona com o Travis CI e não analisar o projeto da maneira mais completa possível. Se você quiser erros maiores e mais bonitos, sempre poderá vê-los
aqui :).
Conclusão
O uso de serviços da Web para criar projetos, juntamente com a prática de análise incremental, permite detectar muitos problemas logo após a mesclagem do código. No entanto, uma compilação pode não ser suficiente, portanto, configurar o teste juntamente com a análise estática melhorará significativamente a qualidade do código.
Links úteis