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
Sí (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;
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;
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