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
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
Vamos ao site
Para o teste, bifurquei o PPSSPP.
Ativamos o repositório que queremos coletar:
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:
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:
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
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
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 “
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:
Veremos uma visão geral da compilação atual:
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:
Este trecho de código está localizado no módulo de hashing seguro, porém contém uma falha grave de segurança (
; 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
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:
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");
}
....
}
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:
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
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
Se você quiser compartilhar este artigo com um público que fala inglês, use o link de tradução: Maxim Zvyagintsev.
Fonte: habr.com