Como configurar PVS-Studio en Travis CI usando o exemplo dun emulador de consola de xogos PSP

Como configurar PVS-Studio en Travis CI usando o exemplo dun emulador de consola de xogos PSP
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

Travis C.I. é un servizo web para crear e probar software. Normalmente úsase en conxunto con prácticas de integración continua.

PPSSPP - Emulador de consola de xogos PSP. O programa é capaz de emular o lanzamento de calquera xogo a partir de imaxes de disco destinadas a Sony PSP. O programa foi lanzado o 1 de novembro de 2012. PPSSPP ten licenza GPL v2. Calquera pode facer melloras código fonte do proxecto.

PVS-Estudio — un analizador de código estático para buscar erros e posibles vulnerabilidades no código do programa. Neste artigo, para variar, lanzaremos PVS-Studio non localmente na máquina do programador, senón na nube, e buscaremos erros en PPSSPP.

Configurando Travis CI

Necesitaremos un repositorio en GitHub, onde estea o proxecto que necesitamos, así como unha chave para PVS-Studio (podes obter chave de proba ou gratuíto para proxectos de código aberto).

Imos ao sitio Travis C.I.. Despois da autorización usando a túa conta de GitHub, veremos unha lista de repositorios:

Como configurar PVS-Studio en Travis CI usando o exemplo dun emulador de consola de xogos PSP
Para a proba, bifurqueime PPSSPP.

Activamos o repositorio que queremos recoller:

Como configurar PVS-Studio en Travis CI usando o exemplo dun emulador de consola de xogos PSP
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:

Como configurar PVS-Studio en Travis CI usando o exemplo dun emulador de consola de xogos PSP
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:

Como configurar PVS-Studio en Travis CI usando o exemplo dun emulador de consola de xogos PSP
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 Documentación de Travis CI.

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 Documentación de Travis CI.

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 "Como executar PVS-Studio en Linux e macOS«.

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:

Como configurar PVS-Studio en Travis CI usando o exemplo dun emulador de consola de xogos PSP
Veremos unha visión xeral da construción actual:

Como configurar PVS-Studio en Travis CI usando o exemplo dun emulador de consola de xogos PSP
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: V597 O compilador podería eliminar a chamada á función "memset", que se usa para limpar o búfer "sum". A función RtlSecureZeroMemory() debería usarse para borrar os datos privados. sha1.cpp 325

Esta peza de código está situada no módulo de hash seguro, pero contén un grave fallo de seguranza (CWE-14). Vexamos a listaxe de conxuntos que se xera ao compilar a versión Debug:

; 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 semellante para ela.

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: V547 A expresión 'leftvol >= 0' sempre é verdadeira. sceAudio.cpp 120

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

V501 Hai subexpresións idénticas '!Memory::IsValidAddress(psmfData)' á esquerda e á dereita do '||' operador. scePsmf.cpp 703

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: V547 A expresión 'tamaño == 8' sempre é falsa. syn-att.c 195

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 aquí :).

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

Como configurar PVS-Studio en Travis CI usando o exemplo dun emulador de consola de xogos PSP

Se queres compartir este artigo cun público de fala inglesa, utiliza a ligazón de tradución: Maxim Zvyagintsev. Como configurar PVS-Studio en Travis CI usando o exemplo do emulador de consola de xogos PSP.

Fonte: www.habr.com

Engadir un comentario