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

PPSSPP

Travis CI es un servicio web distribuido para crear y probar software que utiliza GitHub como servicio de alojamiento de código fuente. Además de los scripts anteriores, puede agregar los suyos, gracias a las amplias opciones de configuración. En este artículo configuraremos Travis CI para trabajar con PVS-Studio con el ejemplo del código PPSSPP.

Introduccion


Travis CI es un servicio web para crear y probar software. Por lo general, se usa en combinación con la práctica de la integración continua.

PPSSPP es un emulador de la consola de juegos PSP. El programa puede emular el lanzamiento de cualquier juego con imágenes de discos diseñados para Sony PSP. El programa se lanzó el 1 de noviembre de 2012. PPSSPP se distribuye bajo licencia GPL v2. Cualquiera puede hacer mejoras al código fuente del proyecto.

PVS-Studio : analizador de código estático para buscar errores y vulnerabilidades potenciales en el código del programa. En este artículo, lanzaremos PVS-Studio en la nube en lugar de hacerlo localmente en la computadora del desarrollador para una variedad de propósitos y buscaremos errores en PPSSPP.

Travis ci configurado


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

Vayamos al sitio de Travis CI . Después de la autorización con la ayuda de la cuenta de GitHub, tendremos una lista de repositorios:



Para la prueba, hice un tenedor PPSSPP.

Activamos el repositorio que queremos construir:



Por el momento, Travis CI no puede construir nuestro proyecto porque no hay instrucciones para construirlo. Por eso es hora de la configuración.

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



Necesitaremos:

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

Los dos últimos son opcionales. Se utilizarán para enviar los resultados por correo. Si desea enviar el informe de otra manera, no necesita especificarlos.

Entonces, hemos agregado las variables de entorno que necesitamos:


Ahora creemos un archivo .travis.yml y lo colocamos en la raíz del proyecto. PPSSPP ya tenía un archivo de configuración para Travis CI, sin embargo, era demasiado grande y no adecuado para el ejemplo, por lo que tuvimos que simplificarlo y dejar solo los elementos básicos.

Primero, especifiquemos el lenguaje de programación, la versión de Ubuntu Linux que queremos usar en la máquina virtual y los paquetes necesarios para construir:

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 agregados solo son necesarios para PPSSPP.

Ahora especifique la matriz de construcción:

 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 de la matriz . En Travis CI hay dos formas de crear opciones de compilación: la primera es especificar compiladores, tipos de sistemas operativos, variables de entorno, etc. con la lista, después de lo cual se generará la matriz de todas las combinaciones posibles; el segundo es una indicación explícita de la matriz. Por supuesto, puede combinar estos dos enfoques y agregar un caso único o, por el contrario, excluirlo utilizando la sección de exclusión . Puede leer más sobre esto en la documentación de Travis CI .

Lo único que queda por hacer es especificar las instrucciones de compilación 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 diferentes etapas de la vida de la máquina virtual. La sección before_install se ejecuta antes de instalar los paquetes. Luego instale , que sigue a la instalación de los paquetes de la lista addons.apt que hemos especificado anteriormente. La construcción en sí tiene lugar en el script . Si todo ha sido exitoso, entramos en after_success (aquí es donde comenzaremos el análisis estático). Estos no son todos los pasos que puede modificar, si necesita más, debe consultar la documentación de Travis CI .

Para la conveniencia de leer, los comandos se colocaron en un script 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, vamos a actualizar los submódulos. Esto es necesario para construir PPSSPP. Agregue la primera función a .travis.sh (tenga en cuenta la extensión):

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

Ahora hemos llegado directamente a configurar el lanzamiento 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 travis_install instalamos los compiladores que necesitamos usando variables de entorno. Luego, si la variable $ PVS_ANALYZE almacena el valor de (lo especificamos en la sección env al configurar la matriz de compilación), instalamos el paquete pvs-studio . Además, también hay paquetes libio-socket-ssl-perl y libnet-ssleay-perl , pero son necesarios para enviar los resultados por correo, por lo que no son necesarios si ha elegido otra forma de entrega de informes.

La función download_extract descarga y desempaqueta el archivo especificado:

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

Es hora de construir un proyecto. Esto sucede en la sección del 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 hecho, esta es una configuración original simplificada, a excepción de estas líneas:

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

En esta sección del código, establecemos el indicador de exportación del comando de compilación para cmake . Esto es necesario para un analizador de código estático. Puede leer más al respecto en el artículo " Cómo iniciar PVS-Studio en Linux y macOS ".

Si la compilación fue exitosa, llegaremos a after_success donde ejecutaremos el 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 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 } 

Consideremos las siguientes líneas en detalle:

 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 el archivo de licencia a partir del nombre de usuario y la clave que especificamos al comienzo de la configuración de las variables de entorno de Travis CI.

La segunda línea comienza el análisis directamente. El indicador -j <N> establece el número de subprocesos de análisis, el indicador -l <archivo> establece la licencia, el indicador -o <archivo> establece el archivo para generar los registros y el indicador - disableLicenseExpirationCheck es necesario para las versiones de prueba , porque de forma predeterminada pvs-studio-analyzer advertirá al usuario sobre el vencimiento inminente de la licencia. Para evitar que esto suceda, puede especificar este indicador.

El archivo de registro contiene una salida sin procesar que no se puede leer sin conversión, por lo que primero debe hacer que el archivo sea legible. Ejecutemos los registros a través de plog-converter y obtengamos un archivo html en la salida.

En este ejemplo, decidí enviar informes por correo usando el comando sendemail .

El resultado fue 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 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; 

Es hora de agregar los cambios al repositorio de git, y luego Travis CI iniciará automáticamente la compilación. Haga clic en "ppsspp" para ir a generar informes:


Veremos una descripción general de la compilación actual:



Si la compilación se completa con éxito, recibiremos un correo electrónico con los resultados del análisis estático. Por supuesto, enviar por correo no es la única forma de obtener el informe. Puede elegir cualquier método de implementación. Pero es importante recordar que será imposible obtener acceso a los archivos de la máquina virtual una vez finalizada la compilación.

Breve resumen de errores


Hemos completado con éxito la parte más difícil. Asegurémonos ahora de que todos nuestros esfuerzos se hayan justificado. Consideremos algunos puntos interesantes del informe de análisis estático que me llegó por correo (no es por nada que lo especifiqué).

Optimizaciones peligrosas


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

La advertencia PVS-Studio: V597 El compilador podría eliminar la llamada a la función 'memset', que se utiliza para vaciar el búfer 'sum'. 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 hash seguro, pero contiene un defecto de seguridad grave ( CWE-14 ). Consideremos la lista de ensambladores que se genera cuando se compila 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á bien y la función memset se ejecuta, borrando así los datos importantes en la RAM, pero aún no debería estar contento. Consideremos la lista de ensambladores de la versión Release con optimización:
 ; 354 : ; 355 : memset( sum, 0, sizeof( sum ) ); ; 356 :} 

Como puede ver en la lista, el compilador ignoró la llamada de memset . Está relacionado con el hecho de que la función sha1 ya no llama a la estructura ctx después de llamar a memset . Es por eso que el compilador no tiene sentido perder el tiempo del procesador en sobrescribir la memoria que no se utilizará en el futuro. Puede solucionarlo utilizando la función RtlSecureZeroMemory o una función similar .

Derecha:

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

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

Presta atención a la rama else para el primer if . El código se ejecutará solo si todas las condiciones quedan vol> 0xFFFFF || rightvol> 0xFFFF || leftvol <0 || rightvol <0 son falsos. Por lo tanto, obtenemos las siguientes afirmaciones que serán verdaderas para la rama else: leftvol <= 0xFFFFF, rightvol <= 0xFFFFF, leftvol> = 0 y rightvol> = 0 . Presta atención a las dos últimas declaraciones. ¿Es razonable verificar cuál es la condición necesaria para la ejecución de este fragmento de código?

Entonces podemos eliminar con calma estos operadores 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 Detrás de estas condiciones redundantes hay algún error. Tal vez hemos comprobado lo que no es lo que necesitamos ...

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

Tenga en cuenta el cheque dentro si . ¿No te parece extraño que estemos verificando si la dirección psmfData es válida el doble? Entonces me resulta extraño ... En realidad, tenemos un error tipográfico ante nosotros, por supuesto, y la idea era verificar ambos parámetros de entrada.

La variante correcta es:

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

La advertencia PVS-Studio: la expresión V547 'size == 8' siempre es falsa. syn-att.c 195

Este error se encuentra en la carpeta ext , por lo que en realidad no se aplica al proyecto, pero el error se encontró antes de notarlo, por lo que decidí mantenerlo. Aún así, 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.

La variable de tamaño se inicializa con una constante, pero no se utiliza en absoluto en el código hasta el operador if que, por supuesto, genera información falsa mientras se verifica la condición porque, como recordamos, el tamaño es igual a cero. Las comprobaciones posteriores tampoco tienen sentido.

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

Para


Ahí es donde vamos a parar con los errores. El propósito de este artículo es demostrar cómo funciona PVS-Studio con Travis CI y no analizar el proyecto tan a fondo como sea posible. Si desea errores más grandes y hermosos, siempre puede verlos aquí :).

Conclusión


El uso de servicios web para crear proyectos junto con la práctica de análisis incremental le permite detectar muchos problemas justo después de la fusión del 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 utiles


Source: https://habr.com/ru/post/464641/


All Articles