Cómo configurar PVS-Studio en Travis CI usando el ejemplo de un emulador de consola de juegos PSP

Cómo configurar PVS-Studio en Travis CI usando el ejemplo de un emulador de consola de juegos PSP
Travis CI es un servicio web distribuido para crear y probar software que utiliza GitHub como alojamiento de código fuente. Además de los escenarios operativos anteriores, puede agregar los suyos propios gracias a las amplias opciones de configuración. En este artículo configuraremos Travis CI para que funcione con PVS-Studio usando el ejemplo de código PPSSPP.

introducción

Travis CI es un servicio web para construir y probar software. Generalmente se utiliza junto con prácticas de integración continua.

PPSSPP - Emulador de consola de juegos PSP. El programa puede emular el lanzamiento de cualquier juego a partir de imágenes de disco destinadas a Sony PSP. El programa fue lanzado el 1 de noviembre de 2012. PPSSPP tiene licencia GPL v2. Cualquiera puede realizar mejoras en código fuente del proyecto.

PVS-Estudio — un analizador de código estático para buscar errores y posibles vulnerabilidades en el código del programa. En este artículo, para variar, iniciaremos PVS-Studio no localmente en la máquina del desarrollador, sino en la nube, y buscaremos errores en PPSSPP.

Configurando Travis CI

Necesitaremos un repositorio en GitHub, donde se encuentra el proyecto que necesitamos, así como una clave para PVS-Studio (puede obtener clave de prueba o gratis para proyectos de código abierto).

vamos al sitio Travis CI. Después de la autorización usando su cuenta de GitHub, veremos una lista de repositorios:

Cómo configurar PVS-Studio en Travis CI usando el ejemplo de un emulador de consola de juegos PSP
Para la prueba, bifurqué PPSSPP.

Activamos el repositorio que queremos recopilar:

Cómo configurar PVS-Studio en Travis CI usando el ejemplo de un emulador de consola de juegos PSP
Por el momento, Travis CI no puede construir nuestro proyecto porque no hay instrucciones para construirlo. Entonces es hora de la configuración.

Durante el análisis, algunas variables nos serán útiles, por ejemplo, la clave para PVS-Studio, que no sería deseable especificar en el archivo de configuración. Entonces, agreguemos variables de entorno usando la configuración de compilación en Travis CI:

Cómo configurar PVS-Studio en Travis CI usando el ejemplo de un emulador de consola de juegos PSP
Necesitamos:

  • PVS_USERNAME - nombre de usuario
  • PVS_KEY - clave
  • MAIL_USER: correo electrónico que se utilizará para enviar el informe
  • MAIL_PASSWORD - contraseña de correo electrónico

Los dos últimos son opcionales. Estos se utilizarán para enviar los resultados por correo. Si desea distribuir el informe de otra forma no es necesario que las indique.

Entonces, hemos agregado las variables de entorno que necesitamos:

Cómo configurar PVS-Studio en Travis CI usando el ejemplo de un emulador de consola de juegos PSP
Ahora creemos un archivo. .travis.yml y colóquelo en la raíz del proyecto. PPSSPP ya tenía un archivo de configuración para Travis CI, sin embargo, era demasiado grande y completamente inadecuado para el ejemplo, por lo que tuvimos que simplificarlo mucho y dejar solo los elementos básicos.

Primero indiquemos el idioma, la versión de Ubuntu Linux que queremos usar en la máquina virtual y los paquetes necesarios para la 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 los paquetes enumerados son necesarios exclusivamente para PPSSPP.

Ahora indicamos la matriz de montaje:

matrix:
  include:
    - os: linux
      compiler: "gcc"
      env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes
    - os: linux
      compiler: "clang"
      env: PPSSPP_BUILD_TYPE=Linux

Un poco más sobre la sección. matriz. En Travis CI, hay dos formas de crear opciones de compilación: la primera es especificar una lista de compiladores, tipos de sistemas operativos, variables de entorno, etc., después de lo cual se genera una matriz de todas las combinaciones posibles; el segundo es una indicación explícita de la matriz. Por supuesto, puedes combinar estos dos enfoques y agregar un caso único o, por el contrario, excluirlo usando la sección excluir. Puedes leer más sobre esto en Documentación de Travis CI.

Sólo queda proporcionar instrucciones de montaje específicas del proyecto:

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 le permite agregar sus propios comandos para varias etapas de la vida de una máquina virtual. Sección antes_instalar ejecutado antes de instalar los paquetes. Entonces instalar, que sigue a la instalación de paquetes de la lista complementos.aptque indicamos anteriormente. La asamblea en sí se lleva a cabo en guión. Si todo ha ido bien, entonces nos encontraremos en después_éxito (Es en esta sección donde ejecutaremos el análisis estático). Estos no son todos los pasos que se pueden modificar, si necesita más, entonces debe buscar en Documentación de Travis CI.

Para facilitar la lectura, los comandos se colocaron en un guión separado. .travis.sh, que se coloca en la raíz del proyecto.

Entonces tenemos el siguiente archivo .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 los paquetes, actualizaremos los submódulos. Esto es necesario para construir PPSSPP. Agreguemos la primera función a .travis.sh (tenga en cuenta la extensión):

travis_before_install() {
  git submodule update --init --recursive
}

Ahora pasamos directamente a configurar el inicio automático de PVS-Studio en Travis CI. Primero necesitamos instalar el paquete PVS-Studio en el 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
}

Al comienzo de la función. instalación_travis Instalamos los compiladores que necesitamos usando variables de entorno. Entonces si la variable $PVS_ANALYZE almacena valor (lo indicamos en el apartado env durante la configuración de la matriz de compilación), instalamos el paquete pvs-estudio. Además de esto, también se indican los paquetes. libio-socket-ssl-perl и libnet-ssleay-perl, sin embargo, son necesarios para enviar los resultados por correo, por lo que no son necesarios si ha elegido otro método para entregar su informe.

Función descargar_extraer descarga y descomprime el archivo especificado:

download_extract() {
  aria2c -x 16 $1 -o $2
  tar -xf $2
}

Es hora de armar el proyecto. Esto sucede en la sección guión:

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 hecho, esta es una configuración original simplificada, excepto por estas líneas:

if [ "$PVS_ANALYZE" = "Yes" ]; then
  CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi

En esta sección de código configuramos para cmake bandera para exportar comandos de compilación. Esto es necesario para un analizador de código estático. Puedes leer más sobre esto en el artículo “Cómo ejecutar PVS-Studio en Linux y macOS«.

Si la asamblea fue exitosa, entonces llegamos a después_éxito, donde realizamos análisis estático:

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
}

Echemos un vistazo más de cerca a las siguientes líneas:

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

La primera línea genera un archivo de licencia a partir del nombre de usuario y la clave que especificamos al principio al configurar las variables de entorno de Travis CI.

La segunda línea inicia el análisis directamente. Bandera -j establece el número de subprocesos para el análisis, marca -l indica licencia, bandera -o define el archivo para generar registros y la bandera -disableLicenseExpirationCheck requerido para las versiones de prueba, ya que por defecto analizador-pvs-studio Advertirá al usuario que la licencia está a punto de caducar. Para evitar que esto suceda, puede especificar esta bandera.

El archivo de registro contiene resultados sin procesar que no se pueden leer sin conversión, por lo que primero debe hacer que el archivo sea legible. Pasemos los registros. convertidor-plog, y el resultado es un archivo html.

En este ejemplo, decidí enviar informes por correo usando el comando enviar correo electrónico.

Como resultado, obtuvimos el siguiente archivo .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;

Ahora es el momento de enviar los cambios al repositorio de git, después de lo cual Travis CI ejecutará automáticamente la compilación. Haga clic en "ppsspp" para ir a los informes de compilación:

Cómo configurar PVS-Studio en Travis CI usando el ejemplo de un emulador de consola de juegos PSP
Veremos una descripción general de la compilación actual:

Cómo configurar PVS-Studio en Travis CI usando el ejemplo de un emulador de consola de juegos PSP
Si la compilación se completa con éxito, recibiremos un correo electrónico con los resultados del análisis estático. Por supuesto, el envío por correo no es la única forma de recibir un informe. Puede elegir cualquier método de implementación. Pero es importante recordar que una vez completada la compilación, no será posible acceder a los archivos de la máquina virtual.

Resumen de errores

Hemos completado con éxito la parte más difícil. Ahora asegurémonos de que todos nuestros esfuerzos valgan la pena. Veamos algunos puntos interesantes del informe de análisis estático que me llegó por correo (no en vano lo indiqué).

Optimización peligrosa

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

Advertencia de PVS-Studio: V597 El compilador podría eliminar la llamada a la función 'memset', que se utiliza para vaciar el búfer de 'suma'. La función RtlSecureZeroMemory() debe usarse para borrar los datos privados. sha1.cpp 325

Este fragmento de código se encuentra en el módulo de hash seguro; sin embargo, contiene una falla de seguridad grave (CWE-14). Veamos el listado del ensamblado que se genera al compilar la versión de depuración:

; Line 355
  mov r8d, 20
  xor edx, edx
  lea rcx, QWORD PTR sum$[rsp]
  call memset
; Line 356

Todo está en orden y en funcionamiento. conjunto de miembros se ejecuta, sobrescribiendo así datos importantes en la RAM, pero no se regocije todavía. Veamos el listado de ensamblaje de la versión de lanzamiento con optimización:

; 354  :
; 355  :  memset( sum, 0, sizeof( sum ) );
; 356  :}

Como puede verse en el listado, el compilador ignoró la llamada. conjunto de miembros. Esto se debe a que en la función sha1 después de la llamada conjunto de miembros no más referencia a la estructura ctx. Por lo tanto, el compilador no ve ningún sentido en perder tiempo del procesador sobrescribiendo memoria que no se utilizará en el futuro. Puedes solucionar este problema usando la función RtlSecureZeroMemoria o similar a ella

Correcto:

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

Advertencia de PVS-Studio: V547 La expresión 'leftvol >= 0' siempre es verdadera. sceAudio.cpp 120

Preste atención a la rama else por primera vez. if. El código se ejecutará solo si se cumplen todas las condiciones. volizquierdo > 0xFFFF || volderecho > 0xFFFF || volizquierdo < 0 || volderecho < 0 resultará falso. Por lo tanto, obtenemos las siguientes afirmaciones, que serán válidas para la rama else: vol izquierdo <= 0xFFFF, volderecho <= 0xFFFF, volizquierdo >= 0 и volderecho >= 0. Observe las dos últimas declaraciones. ¿Tiene sentido comprobar cuál es la condición necesaria para la ejecución de este fragmento de código?

Entonces podemos eliminar de forma segura estas declaraciones condicionales:

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

Otro escenario. Hay algún tipo de error escondido detrás de estas condiciones redundantes. Quizás no comprobaron lo que se requerí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 Hay subexpresiones idénticas '!Memory::IsValidAddress(psmfData)' a la izquierda y a la derecha de '||' operador. scePsmf.cpp 703

Presta atención al cheque en el interior. if. ¿No te parece extraño que comprobemos si la dirección es válida? psmfDatos, ¿el doble? Entonces esto me parece extraño... De hecho, esto es, por supuesto, un error tipográfico, y la idea era verificar ambos 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 olvidada

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

Advertencia de PVS-Studio: V547 La expresión 'tamaño == 8' siempre es falsa. syn-att.c 195

Este error se encuentra en la carpeta. ext, por lo que no es realmente relevante para el proyecto, pero el error se encontró antes de que me diera cuenta, así que decidí dejarlo. Después de todo, este artículo no trata sobre la revisión de errores, sino sobre la integración con Travis CI, y no se realizó ninguna configuración del analizador.

Variable tamaño se inicializa mediante una constante, sin embargo, no se utiliza en absoluto en el código, hasta el operador if, lo que, por supuesto, da false mientras comprobamos las condiciones, porque, como recordamos, tamaño igual a cero. Los controles posteriores tampoco tienen sentido.

Aparentemente, el autor del fragmento de código olvidó sobrescribir la variable. tamaño antes de que.

Detener

Aquí es donde probablemente terminaremos con los errores. El propósito de este artículo es demostrar el trabajo de PVS-Studio junto con Travis CI y no analizar el proyecto lo más a fondo posible. Si quieres errores más grandes y bellos, siempre podrás admirarlos. aquí :).

Conclusión

El uso de servicios web para crear proyectos junto con la práctica del análisis incremental le permite encontrar muchos problemas inmediatamente después de fusionar el código. Sin embargo, una compilación puede no ser suficiente, por lo que configurar las pruebas junto con el análisis estático mejorará significativamente la calidad del código.

Enlaces de interés

Cómo configurar PVS-Studio en Travis CI usando el ejemplo de un emulador de consola de juegos PSP

Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace de traducción: Maxim Zvyagintsev. Cómo configurar PVS-Studio en Travis CI usando el ejemplo del emulador de consola de juegos PSP.

Fuente: habr.com

Añadir un comentario