Travis CI é un servizo web distribuído para crear e probar software que usa GitHub como hospedaxe de código fonte. Ademais dos escenarios operativos anteriores, pode engadir os seus propios grazas ás amplas opcións de configuración. Neste artigo configuraremos Travis CI para traballar con PVS-Studio usando o exemplo de código PPSSPP.
Introdución
Configurando Travis CI
Necesitaremos un repositorio en GitHub, onde estea o proxecto que necesitamos, así como unha chave para PVS-Studio (podes obter
Imos ao sitio
Para a proba, bifurqueime PPSSPP.
Activamos o repositorio que queremos recoller:
Polo momento, Travis CI non pode construír o noso proxecto porque non hai instrucións para construír. Entón é o momento da configuración.
Durante a análise, algunhas variables serán útiles para nós, por exemplo, a clave para PVS-Studio, que non sería desexable especificar no ficheiro de configuración. Entón, imos engadir variables de ambiente usando a configuración de compilación en Travis CI:
Necesitaremos:
- PVS_USERNAME - nome de usuario
- PVS_KEY - chave
- MAIL_USER: correo electrónico que se utilizará para enviar o informe
- MAIL_PASSWORD: contrasinal de correo electrónico
Os dous últimos son opcionais. Estes serán utilizados para enviar os resultados por correo. Se quere distribuír o informe doutro xeito, non é preciso que os indique.
Entón, engadimos as variables de ambiente que necesitamos:
Agora imos crear un ficheiro .travis.yml e colócao na raíz do proxecto. PPSSPP xa tiña un ficheiro de configuración para Travis CI, non obstante, era demasiado grande e completamente inadecuado para o exemplo, polo que tivemos que simplificalo moito e deixar só os elementos básicos.
En primeiro lugar, imos indicar o idioma, a versión de Ubuntu Linux que queremos usar na máquina virtual e os paquetes necesarios para a compilación:
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 paquetes que se enumeran son necesarios exclusivamente para PPSSPP.
Agora indicamos a matriz de montaxe:
matrix:
include:
- os: linux
compiler: "gcc"
env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes
- os: linux
compiler: "clang"
env: PPSSPP_BUILD_TYPE=Linux
Un pouco máis sobre a sección matriz. En Travis CI, hai dúas formas de crear opcións de compilación: a primeira é especificar unha lista de compiladores, tipos de sistema operativo, variables de ambiente, etc., despois do cal se xera unha matriz de todas as combinacións posibles; o segundo é unha indicación explícita da matriz. Por suposto, pode combinar estes dous enfoques e engadir un caso único ou, pola contra, excluílo usando a sección eliminar. Podes ler máis sobre isto en
Só queda proporcionar instrucións de montaxe específicas do proxecto:
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
Travis CI permítelle engadir os seus propios comandos para varias etapas da vida dunha máquina virtual. Sección antes_instalar executado antes de instalar paquetes. Entón instalar, que segue a instalación dos paquetes da lista complementos.aptque indicamos anteriormente. A propia asemblea ten lugar en escrita. Se todo foi ben, entón atopámonos despois do éxito (é nesta sección onde realizaremos a análise estática). Estes non son todos os pasos que se poden modificar, se necesitas máis, deberías mirar
Para facilitar a lectura, os comandos colocáronse nun guión separado .travis.sh, que se coloca na raíz do proxecto.
Polo tanto, temos o seguinte ficheiro .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 paquetes, actualizaremos os submódulos. Isto é necesario para construír PPSSPP. Imos engadir a primeira función a .travis.sh (teña en conta a extensión):
travis_before_install() {
git submodule update --init --recursive
}
Agora chegamos directamente a configurar o lanzamento automático de PVS-Studio en Travis CI. Primeiro necesitamos instalar o paquete 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
}
Ao comezo da función travis_install instalamos os compiladores que necesitamos utilizando variables de ambiente. Entón, se a variable $PVS_ANALYZE almacena valor si (indicámolo no apartado aprox durante a configuración da matriz de compilación), instalamos o paquete pvs-studio. Ademais disto, tamén se indican paquetes libio-socket-ssl-perl и libnet-ssleay-perl, non obstante, son necesarios para enviar os resultados por correo, polo que non son necesarios se escolleches outro método para entregar o teu informe.
Función descargar_extraer descarga e desempaqueta o arquivo especificado:
download_extract() {
aria2c -x 16 $1 -o $2
tar -xf $2
}
É o momento de xuntar o proxecto. Isto ocorre na sección 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
}
De feito, esta é unha configuración orixinal simplificada, excepto nestas liñas:
if [ "$PVS_ANALYZE" = "Yes" ]; then
CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi
Nesta sección de código que establecemos facer marca para exportar comandos de compilación. Isto é necesario para un analizador de código estático. Podes ler máis sobre isto no artigo "
Se a asemblea foi exitosa, entón chegamos despois do éxito, onde realizamos análises estáticas:
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
}
Vexamos máis de cerca as seguintes liñas:
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 liña xera un ficheiro de licenza a partir do nome de usuario e da clave que especificamos ao principio ao configurar as variables de ambiente de Travis CI.
A segunda liña comeza a análise directamente. Bandeira -j establece o número de fíos para a análise, marca -l indica licenza, bandeira -o define o ficheiro para a saída dos rexistros e a marca -disableLicenseExpirationCheck necesario para as versións de proba, xa que por defecto pvs-studio-analizador avisará ao usuario de que a licenza está a piques de caducar. Para evitar que isto suceda, podes especificar esta bandeira.
O ficheiro de rexistro contén saída en bruto que non se pode ler sen conversión, polo que primeiro debes facer que o ficheiro sexa lexible. Imos pasar os rexistros Plog-conversor, e a saída é un ficheiro html.
Neste exemplo, decidín enviar informes por correo mediante o comando enviar correo electrónico.
Como resultado, obtivemos o seguinte ficheiro .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 é o momento de impulsar os cambios no repositorio git, despois do cal Travis CI executará automaticamente a compilación. Fai clic en "ppsspp" para ir aos informes de compilación:
Veremos unha visión xeral da construción actual:
Se a compilación se completa con éxito, recibiremos un correo electrónico cos resultados da análise estática. Por suposto, o envío por correo non é a única forma de recibir un informe. Podes escoller calquera método de implementación. Pero é importante lembrar que despois de completar a compilación, non será posible acceder aos ficheiros da máquina virtual.
Resumo do erro
Completamos con éxito a parte máis difícil. Agora asegurémonos de que todos os nosos esforzos valen a pena. Vexamos algúns puntos interesantes do informe de análise estática que me chegou por correo (non foi por nada que o indiquei).
Optimización 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 de PVS-Studio:
Esta peza de código está situada no módulo de hash seguro, pero contén un grave fallo de seguranza (
; Line 355
mov r8d, 20
xor edx, edx
lea rcx, QWORD PTR sum$[rsp]
call memset
; Line 356
Todo está en orde e a función memeset execútase, sobrescribindo así os datos importantes na RAM, con todo, aínda non te alegras. Vexamos a listaxe de montaxe da versión de lanzamento con optimización:
; 354 :
; 355 : memset( sum, 0, sizeof( sum ) );
; 356 :}
Como se pode ver na listaxe, o compilador ignorou a chamada memeset. Isto débese ao feito de que na función sha1 despois da chamada memeset non hai máis referencia á estrutura ctx. Polo tanto, o compilador non ve sentido en perder o tempo do procesador sobreescribindo a memoria que non se utilizará no futuro. Podes corrixir isto usando a función RtlSecureZeroMemory ou
De forma correcta:
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 ) );
}
Comparación innecesaria
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 de PVS-Studio:
Preste atención á rama máis para a primeira if. O código executarase só se todas as condicións leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || dereitovol < 0 resultará falso. Polo tanto, obtemos as seguintes afirmacións, que serán certas para a rama else: leftvol <= 0xFFFF, rightvol <= 0xFFFF, leftvol >= 0 и dereitovol >= 0. Fíxate nas dúas últimas afirmacións. Ten sentido comprobar cal é unha condición necesaria para a execución deste anaco de código?
Polo tanto, podemos eliminar con seguridade estas instrucións 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 escenario. Hai algún tipo de erro escondido detrás destas condicións redundantes. Quizais non comprobaron o que se requiría.
Ctrl+C Ctrl+V contraataca
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 atención ao cheque no interior if. Non cres estraño que comprobemos se o enderezo é válido? psmfData, o dobre? Entón, isto paréceme estraño... De feito, isto é, por suposto, un erro tipográfico, e a idea era comprobar os dous parámetros de entrada.
Opción correcta:
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfStruct) ||
!Memory::IsValidAddress(psmfData)) {
return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}
....
}
Variable 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 de PVS-Studio:
Este erro atópase no cartafol ext, polo que non é moi relevante para o proxecto, pero o erro atopouse antes de que me dese conta, así que decidín deixalo. Despois de todo, este artigo non trata de revisar erros, senón de integración con Travis CI, e non se realizou ningunha configuración do analizador.
Variable tamaño inicialízase mediante unha constante, con todo, non se usa en absoluto no código, ata o operador if, que, por suposto, dá teito ao comprobar as condicións, porque, como lembramos, tamaño igual a cero. As comprobacións posteriores tampouco teñen sentido.
Ao parecer, o autor do fragmento de código esqueceu sobrescribir a variable tamaño antes diso.
Deixe
Aquí é onde probablemente remataremos cos erros. O propósito deste artigo é demostrar o traballo de PVS-Studio xunto con Travis CI, e non analizar o proxecto o máis a fondo posible. Se queres erros máis grandes e fermosos, sempre podes admiralos
Conclusión
Usar servizos web para construír proxectos xunto coa práctica da análise incremental permítelle atopar moitos problemas inmediatamente despois de fusionar o código. Non obstante, unha compilación pode non ser suficiente, polo que configurar as probas xunto coa análise estática mellorará significativamente a calidade do código.
Ligazóns útiles
Se queres compartir este artigo cun público de fala inglesa, utiliza a ligazón de tradución: Maxim Zvyagintsev.
Fonte: www.habr.com