PVS-Studio va a las nubes: análisis de lanzamiento en Travis CI

Por el momento, los sistemas de CI en la nube son un servicio muy popular. En este artículo, le diremos cómo, utilizando las herramientas existentes disponibles en PVS-Studio, puede integrar el análisis de código fuente con la plataforma de CI en la nube, utilizando el servicio Travis CI como ejemplo.

Imagen 1


¿Por qué miramos nubes de terceros y no hacemos las nuestras? Hay varias razones, y la principal es que SaaS es un procedimiento bastante costoso y difícil. De hecho, integrar directamente el análisis PVS-Studio con una plataforma en la nube de terceros (ya sea plataformas abiertas como CircleCI, Travis CI, GitLab o alguna solución empresarial especializada utilizada en una sola empresa específica) es una tarea bastante simple y trivial. Es decir, podemos decir que PVS-Studio ya está disponible "en las nubes" . Un tema completamente diferente es la organización y provisión de infraestructura para dicho trabajo 24/7. Esta es una tarea completamente diferente, y PVS-Studio no tiene planes de proporcionar su propia plataforma en la nube directamente para ejecutar análisis en ella.

Información sobre el software utilizado


Travis CI es un servicio para crear y probar software que utiliza GitHub como almacenamiento. Travis CI no requiere cambiar el código del programa para usar el servicio, todas las configuraciones ocurren en el archivo .travis.yml ubicado en la raíz del repositorio.

Tomaremos LXC (Contenedores Linux) como un proyecto de prueba para probar con PVS-Studio. Es un sistema de virtualización a nivel de sistema operativo para ejecutar múltiples instancias del sistema operativo Linux en un solo nodo.

El proyecto es pequeño, pero más que suficiente para demostrarlo. La salida del comando cloc:
Idioma
archivos
en blanco
comentar
codigo
C
124
11937
6758
50836
Encabezado C / C ++
65
1117
3676
3774
Nota: Los desarrolladores de LXC ya usan Travis CI, por lo que tomaremos su archivo de configuración como base y lo editaremos para nuestros propósitos.

Personalización


Para comenzar con Travis CI, siga el enlace y autentíquese con una cuenta de GitHub.

Cuadro 17

En la ventana que se abre, debe autorizar Travis CI.

Cuadro 16

Después de la autorización, una redirección a la página de bienvenida “¿Primera vez aquí? ¡Vamos a empezar! , que describe brevemente lo que se debe hacer a continuación para comenzar:

  • activar repositorios;
  • agregue el archivo .travis.yml al repositorio;
  • ejecuta la primera compilación.

Cuadro 18

Comenzaremos a llevar a cabo estos puntos.

Para agregar nuestro repositorio a Travis CI, vaya a la configuración del perfil a través del enlace y haga clic en el botón "Activar".

Cuadro 19

Después de hacer clic, se abre una ventana con una selección de repositorios a los que se otorgará acceso a la aplicación Travis CI.
Nota: para proporcionar acceso al repositorio, la cuenta debe tener derechos de administrador.

Cuadro 38

Seleccionamos el repositorio deseado, confirmamos la selección con el botón "Aprobar e instalar" y se nos redirigirá a la página de configuración del perfil.

Inmediatamente cree las variables que usaremos para crear el archivo de licencia del analizador y enviar sus informes. Para hacer esto, vaya a la página de configuración: el botón "Configuración" a la derecha del repositorio deseado.

Cuadro 39

Se abrirá la ventana de configuración.

Cuadro 41

Breve descripción de la configuración:

  • Sección "General": configuración de disparadores para tareas de ejecución automática;
  • Sección "Cancelación automática": le permite configurar el ensamblaje de cancelación automática;
  • Sección "Variables de entorno": le permite definir variables de entorno que contienen información pública y confidencial, como credenciales, claves ssh;
  • Sección "Cron Jobs": configuración del cronograma de inicio de tareas.

En la sección "Variables de entorno" creamos las variables PVS_USERNAME y PVS_KEY que contienen, respectivamente, el nombre de usuario y la clave de licencia para el analizador estático. Si no tiene una licencia permanente de PVS-Studio, puede solicitar una licencia de prueba .

Cuadro 5

Inmediatamente cree las variables MAIL_USER y MAIL_PASSWORD que contienen el nombre de usuario y la contraseña del buzón, que usaremos para enviar informes.

Cuadro 4

Cuando se inicia la tarea, Travis CI toma instrucciones del archivo .travis.yml ubicado en la raíz del repositorio.

Usando Travis CI, podemos ejecutar análisis estáticos directamente en la máquina virtual, o usar un contenedor preconfigurado para esto. Los resultados de estos enfoques no son diferentes entre sí, pero el uso de un contenedor preconfigurado puede ser útil, por ejemplo, si ya tenemos un contenedor con un entorno específico dentro del cual se construye y prueba el producto de software, y no hay ningún deseo de restaurar este entorno en Travis CI .

Creemos una configuración para ejecutar el analizador en una máquina virtual.

Para el montaje y las pruebas, utilizaremos una máquina virtual basada en Ubuntu Trusty, su descripción se puede encontrar aquí .

En primer lugar, indicamos que el proyecto está escrito en C y enumeramos los compiladores que usaremos para el ensamblaje:

language: c compiler: - gcc - clang 

Nota : si especifica más de un compilador, las tareas se iniciarán en paralelo para cada uno de ellos. Lea más en la documentación .

Antes de comenzar la compilación, necesitamos agregar el repositorio del analizador, instalar las dependencias y paquetes adicionales:

 before_install: - sudo add-apt-repository ppa:ubuntu-lxc/daily -y - 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 coccinelle parallel libapparmor-dev libcap-dev libseccomp-dev python3-dev python3-setuptools docbook2x libgnutls-dev libselinux1-dev linux-libc-dev pvs-studio libio-socket-ssl-perl libnet-ssleay-perl sendemail ca-certificates 

Antes de construir el proyecto, debe preparar el entorno:

 script: - ./coccinelle/run-coccinelle.sh -i - git diff --exit-code - export CFLAGS="-Wall -Werror" - export LDFLAGS="-pthread -lpthread" - ./autogen.sh - rm -Rf build - mkdir build - cd build - ../configure --enable-tests --with-distro=unknown 

Luego, necesitamos crear un archivo con una licencia y ejecutar el análisis del proyecto.

El primer comando crea un archivo de licencia para el analizador. Los datos para las variables $ PVS_USERNAME y $ PVS_KEY se toman de la configuración del proyecto.

 - pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic 

El siguiente comando inicia la traza del ensamblaje del proyecto:

 - pvs-studio-analyzer trace -- make -j4 

Después comenzamos el análisis estático.
Nota: cuando use una licencia de prueba, debe especificar el parámetro --disableLicenseExpirationCheck .

  - pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic -o PVS-Studio-${CC}.log –-disableLicenseExpirationCheck 

Con el último comando, el archivo de resultados del analizador se convierte en un informe html.

 - plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html 

Como TravisCI no permite cambiar el formato de las notificaciones por correo electrónico, utilizaremos el paquete sendemail para enviar informes en el último paso:

 - 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 

Texto completo del archivo de configuración para ejecutar el analizador en una máquina virtual:

 language: c compiler: - gcc - clang before_install: - sudo add-apt-repository ppa:ubuntu-lxc/daily -y - 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 coccinelle parallel libapparmor-dev libcap-dev libseccomp-dev python3-dev python3-setuptools docbook2x libgnutls-dev libselinux1-dev linux-libc-dev pvs-studio libio-socket-ssl-perl libnet-ssleay-perl sendemail ca-certificates script: - ./coccinelle/run-coccinelle.sh -i - git diff --exit-code - export CFLAGS="-Wall -Werror" - export LDFLAGS="-pthread -lpthread" - ./autogen.sh - rm -Rf build - mkdir build - cd build - ../configure --enable-tests --with-distro=unknown - pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic - pvs-studio-analyzer trace -- make -j4 - 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 

Para ejecutar un analizador estático en un contenedor, primero créelo usando el siguiente Dockerfile:

 FROM docker.io/ubuntu:trusty ENV CFLAGS="-Wall -Werror" ENV LDFLAGS="-pthread -lpthread" RUN apt-get update && apt-get install -y software-properties-common wget \ && wget -q -O - https://files.viva64.com/etc/pubkey.txt | sudo apt-key add - \ && wget -O /etc/apt/sources.list.d/viva64.list https://files.viva64.com/etc/viva64.list \ && apt-get update \ && apt-get install -yqq coccinelle parallel libapparmor-dev libcap-dev libseccomp-dev python3-dev python3-setuptools docbook2x libgnutls-dev libselinux1-dev linux-libc-dev pvs-studio git libtool autotools-dev automake pkg-config clang make libio-socket-ssl-perl libnet-ssleay-perl sendemail ca-certificates \ && rm -rf /var/lib/apt/lists/* 

En este caso, el archivo de configuración puede verse así:

 before_install: - docker pull docker.io/oandreev/lxc env: - CC=gcc - CC=clang script: - docker run --rm --cap-add SYS_PTRACE -v $(pwd):/pvs -w /pvs docker.io/oandreev/lxc /bin/bash -c " ./coccinelle/run-coccinelle.sh -i && git diff --exit-code && ./autogen.sh && mkdir build && cd build && ../configure CC=$CC && pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic && pvs-studio-analyzer trace -- make -j4 && pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic -o PVS-Studio-$CC.log --disableLicenseExpirationCheck && plog-converter -t html -o PVS-Studio-$CC.html PVS-Studio-$CC.log && 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" 

Como puede ver, en este caso no estamos haciendo nada dentro de la máquina virtual, y absolutamente todas las acciones para ensamblar y probar el proyecto tienen lugar dentro del contenedor.

Nota : al iniciar el contenedor, debe especificar --cap-add SYS_PTRACE o --security-opt seccomp: parámetro no confinado , ya que la llamada al sistema ptrace se utiliza para compilar la traza.

Cargamos el archivo de configuración en la raíz del repositorio y vemos que Travis CI recibió una notificación sobre la presencia de cambios en el proyecto e inició automáticamente el ensamblaje.

Se puede ver información detallada sobre el progreso del ensamblaje y la verificación por parte del analizador en la consola.

Imagen 2

Una vez que se completen las pruebas, recibiremos 2 cartas por correo: una con los resultados del análisis estático para construir el proyecto usando gcc y la segunda con clang, respectivamente.

Brevemente sobre los resultados de la prueba.


En general, el proyecto es bastante limpio, el analizador emitió solo 24 advertencias críticas y 46 advertencias promedio. Para demostrar el trabajo, considere un par de notificaciones interesantes:

Condiciones redundantes en if


V590 Considere inspeccionar la expresión 'ret! = (- 1) && ret == 1'. La expresión es excesiva o contiene un error de imprenta. attach.c 107

 #define EOF -1 static struct lxc_proc_context_info *lxc_proc_get_context_info(pid_t pid) { .... while (getline(&line, &line_bufsz, proc_file) != -1) { ret = sscanf(line, "CapBnd: %llx", &info->capability_mask); if (ret != EOF && ret == 1) // <= { found = true; break; } } .... } 

Si ret == 1 , definitivamente no es igual a -1 (EOF). Validación excesiva, ret! = EOF se puede eliminar.

Se emitieron dos advertencias más:

  • V590 Considere inspeccionar la expresión 'ret! = (- 1) && ret == 1'. La expresión es excesiva o contiene un error de imprenta. attach.c 579
  • V590 Considere inspeccionar la expresión 'ret! = (- 1) && ret == 1'. La expresión es excesiva o contiene un error de imprenta. attach.c 583

Pérdida de bits altos.


V784 El tamaño de la máscara de bits es menor que el tamaño del primer operando. Esto causará la pérdida de bits más altos. conf.c 1879

 struct mount_opt { char *name; int clear; int flag; }; static void parse_mntopt(char *opt, unsigned long *flags, char **data, size_t size) { struct mount_opt *mo; /* If opt is found in mount_opt, set or clear flags. * Otherwise append it to data. */ for (mo = &mount_opt[0]; mo->name != NULL; mo++) { if (strncmp(opt, mo->name, strlen(mo->name)) == 0) { if (mo->clear) { *flags &= ~mo->flag; // <= } else { *flags |= mo->flag; } return; } } .... } 

En Linux, long es una variable entera de 64 bits, mo-> flag es una variable entera de 32 bits. Usar mo-> flag como máscara de bits dará como resultado la pérdida de los 32 bits más significativos. Conversión implícita de la máscara de bits a una variable entera de 64 bits después de realizar la inversión bit a bit. Los bits altos de esta máscara serán cero.

Demuestre con un ejemplo:

 unsigned long long x; unsigned y; .... x &= ~y; 

Cuadro 3


El código correcto es:

 *flags &= ~(unsigned long)(mo->flag); 

El analizador emitió otra advertencia similar:

  • V784 El tamaño de la máscara de bits es menor que el tamaño del primer operando. Esto causará la pérdida de bits más altos. conf.c 1933

Ciclo sospechoso


V612 Un 'retorno' incondicional dentro de un bucle. conf.c 3477

 #define lxc_list_for_each(__iterator, __list) \ for (__iterator = (__list)->next; __iterator != __list; \ __iterator = __iterator->next) static bool verify_start_hooks(struct lxc_conf *conf) { char path[PATH_MAX]; struct lxc_list *it; lxc_list_for_each (it, &conf->hooks[LXCHOOK_START]) { int ret; char *hookname = it->elem; ret = snprintf(path, PATH_MAX, "%s%s", conf->rootfs.path ? conf->rootfs.mount : "", hookname); if (ret < 0 || ret >= PATH_MAX) return false; ret = access(path, X_OK); if (ret < 0) { SYSERROR("Start hook \"%s\" not found in container", hookname); return false; } return true; // <= } return true; } 

El ciclo comienza y en la primera iteración se interrumpe. Tal vez esto fue intencionado, pero luego se puede omitir el ciclo.

Ir más allá de los límites de una matriz.


V557 Array underrun es posible. El valor del índice 'bytes - 1' podría alcanzar -1. network.c 2570

 static int lxc_create_network_unpriv_exec(const char *lxcpath, const char *lxcname, struct lxc_netdev *netdev, pid_t pid, unsigned int hooks_version) { int bytes; char buffer[PATH_MAX] = {0}; .... bytes = lxc_read_nointr(pipefd[0], &buffer, PATH_MAX); if (bytes < 0) { SYSERROR("Failed to read from pipe file descriptor"); close(pipefd[0]); } else { buffer[bytes - 1] = '\0'; } .... } 

Los bytes se leen desde la tubería al búfer. En caso de error, la función lxc_read_nointr devolverá un valor negativo. Si todo salió bien, entonces la terminal nula se escribe como el último elemento. Sin embargo, si se leen 0 bytes, entonces el búfer se saldrá de los límites, lo que conduce a un comportamiento indefinido.

El analizador emitió otra advertencia similar:

  • V557 Array underrun es posible. El valor del índice 'bytes - 1' podría alcanzar -1. network.c 2725

Desbordamiento de búfer


V576 Formato incorrecto. Considere verificar el tercer argumento real de la función 'sscanf'. Es peligroso usar un especificador de cadena sin especificación de ancho. El desbordamiento del búfer es posible. lxc_unshare.c 205

 static bool lookup_user(const char *oparg, uid_t *uid) { char name[PATH_MAX]; .... if (sscanf(oparg, "%u", uid) < 1) { /* not a uid -- perhaps a username */ if (sscanf(oparg, "%s", name) < 1) // <= { free(buf); return false; } .... } .... } 

Usar sscanf en este caso puede ser peligroso, porque si la longitud del búfer oparq es mayor que la longitud del búfer de nombre , se irá al extranjero cuando se forme el búfer de nombre .

Conclusión


Como vimos, configurar una comprobación del analizador de código estático de nuestro proyecto en la nube es una tarea bastante simple. Para hacer esto, solo necesita agregar un archivo al repositorio y pasar el tiempo mínimo configurando el sistema CI. Como resultado, obtenemos una herramienta que le permite identificar código problemático en la etapa de escritura, y no permite que los errores pasen a las siguientes etapas de prueba, donde su corrección tomará más tiempo y recursos.

Por supuesto, el uso de PVS-Studio junto con plataformas en la nube no se limita a Travis CI. Por analogía con el método descrito en el artículo, con diferencias mínimas, el análisis PVS-Studio se puede integrar con otras soluciones de CI basadas en la nube populares, como CircleCI, GitLab, etc.

Enlaces utiles


  • Puede encontrar información adicional sobre el lanzamiento de PVS-Studio en Linux y MacOS aquí .
  • Puede leer sobre la creación, configuración y uso de contenedores con el analizador estático PVS-Studio instalado aquí .
  • TravisCI Documentation .



Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Oleg Andreev. PVS-Studio en las nubes: ejecución del análisis en Travis CI

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


All Articles