PVS-Studio en las nubes - Ejecución del análisis en Travis CI

Por el momento, los sistemas de CI en la nube son un servicio muy demandado. En este artículo, le diremos cómo integrar el análisis del código fuente en una plataforma de nube CI con las herramientas que ya están disponibles en PVS-Studio. Como ejemplo, utilizaremos el servicio Travis CI.

Imagen 1


¿Por qué consideramos nubes de terceros y no hacemos las nuestras? Hay varias razones, la principal es que la implementación de SaaS es un procedimiento bastante costoso y difícil. De hecho, es una tarea simple y trivial integrar directamente el análisis PVS-Studio en una plataforma de nube de terceros, ya sea plataformas abiertas como CircleCI, Travis CI, GitLab o una solución empresarial específica utilizada solo en una determinada empresa. Por lo tanto, podemos decir que PVS-Studio ya está disponible "en las nubes". Otro problema es implementar y garantizar el acceso a la infraestructura 24/7. Esta es una tarea más complicada. PVS-Studio no proporcionará su propia plataforma en la nube directamente para ejecutar análisis en ella.

Alguna 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 de programación para usar el servicio. Todos los ajustes se realizan en el archivo .travis.yml ubicado en la raíz del repositorio.

Tomaremos LXC (Contenedores de Linux) como un proyecto de prueba para PVS-Studio. Es un sistema de virtualización a nivel del sistema operativo para lanzar varias instancias del sistema operativo Linux en un nodo.

El proyecto es pequeño, pero más que suficiente para la demostración. 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.

Configuracion


Para comenzar a trabajar con Travis CI, seguimos el enlace e iniciamos sesión con una cuenta GitHub.

Cuadro 17

En la ventana abierta necesitamos iniciar sesión en Travis CI.

Cuadro 16

Después de la autorización, redirige a la página de bienvenida "¿Primera vez aquí? ¡Vamos a empezar! , donde encontramos una breve descripción de lo que se debe hacer después para comenzar:

  • habilitar los repositorios;
  • agregue el archivo .travis.yml en el repositorio;
  • comienza la primera compilación.

Cuadro 18

Comencemos a hacer estas acciones.

Para agregar nuestro repositorio en Travis CI, vamos a la configuración del perfil mediante el enlace y presionamos "Activar".

Cuadro 19

Una vez que se hace clic, se abrirá una ventana para seleccionar los repositorios a los que la aplicación Travis CI tendrá acceso.

Nota: para proporcionar acceso al repositorio, su cuenta debe tener derechos de administrador para hacerlo.

Cuadro 38

Después de eso, elegimos el repositorio correcto, confirmamos la opción con el botón "Aprobar e instalar", y se nos redirigirá a la página de configuración del perfil.

Agreguemos algunas variables que usaremos para crear el archivo de licencia del analizador y enviar sus informes. Para hacer esto, iremos a la página de configuración: el botón "Configuración" a la derecha del repositorio necesario.

Cuadro 39

Se abrirá la ventana de configuración.

Cuadro 41

Breve descripción de la configuración;

  • Sección "General": configuración de activadores de tareas de inicio automático;
  • La sección "Cancelación automática" permite configurar la cancelación automática de compilación;
  • La sección "Variables de entorno" permite definir variables de entorno que contienen información abierta y confidencial, como información de inicio de sesión, teclas ssh;
  • La sección "Trabajos de Cron" es una configuración del cronograma de ejecución de tareas.

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

Cuadro 5

Aquí crearemos las variables MAIL_USER y MAIL_PASSWORD , que contienen un nombre de usuario y una contraseña de correo electrónico, que usaremos para enviar informes.

Cuadro 4

Al ejecutar tareas, Travis CI toma instrucciones del archivo .travis.yml, ubicado en la raíz del repositorio.

Al usar Travis CI, podemos ejecutar análisis estáticos directamente en la máquina virtual y utilizar un contenedor preconfigurado para hacerlo. Los resultados de estos enfoques no son diferentes entre sí. Sin embargo, 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 un producto de software y no queremos restaurar este entorno en Travis CI.

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

Para compilar y probar utilizaremos una máquina virtual en Ubuntu Trusty, su descripción está disponible en el enlace .

En primer lugar, especificamos que el proyecto está escrito en C y enumeramos los compiladores que usaremos para la compilación:

language: c compiler: - gcc - clang 

Nota: si especifica más de un compilador, las tareas se ejecutarán simultáneamente para cada uno de ellos. Lee más aquí .

Antes de la compilación, necesitamos agregar el repositorio del analizador, establecer 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 un proyecto, necesitamos preparar su 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 de licencia y comenzar a analizar el proyecto.

Luego creamos un archivo de licencia para el analizador con el primer comando. 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 

Con el siguiente comando, comenzamos a rastrear la construcción del proyecto.

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

Después de eso, ejecutamos análisis estáticos.
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 

El archivo con los resultados del análisis se convierte en el informe html por el último comando.

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

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

 - 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 

Aquí está el texto completo del archivo de configuración para ejecutar el analizador en la 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 PVS-Studio en un contenedor, vamos a crearlo previamente 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 hacemos nada dentro de la máquina virtual, y todas las acciones para construir y probar el proyecto tienen lugar dentro del contenedor.

Nota : cuando inicia el contenedor, debe especificar el parámetro --cap-add SYS_PTRACE o --security-opt seccomp: inconfined , ya que se utiliza una llamada del sistema ptrace para el rastreo del compilador.

A continuación, cargamos el archivo de configuración en la raíz del repositorio y vemos que Travis CI ha sido notificado de los cambios en el proyecto y ha iniciado automáticamente la compilación.

Los detalles del progreso de la compilación y la verificación del analizador se pueden ver en la consola.

Imagen 2

Una vez finalizadas las pruebas, recibiremos dos correos electrónicos: el primero, con resultados de análisis estáticos para construir un proyecto usando gcc, y el segundo, para clang, respectivamente.

Brevemente sobre los resultados de la verificación


En general, el proyecto es bastante limpio, el analizador emitió solo 24 advertencias de alta certeza y 46 de certeza media. Veamos 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). Comprobación redundante, ret! = EOF se puede eliminar.

Se emitieron dos advertencias similares:

  • 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. El uso de la marca mo-> como máscara de bits provocará la pérdida de 32 bits altos. Una máscara de bits se convierte implícitamente en una variable entera de 64 bits después de la inversión bit a bit. Se pueden perder partes altas de esta máscara.

Lo mostraré usando un ejemplo:

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

Cuadro 3


Aquí está la versión correcta del código:

 *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

Lazo 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 se inicia e interrumpe en la primera iteración. Esto podría haberse hecho intencionalmente, pero en este caso el bucle podría haberse omitido.

Índice de matriz fuera de límites


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 en el búfer de la tubería. En caso de error, la función lxc_read_nointr devolverá un valor negativo. Si todo funciona correctamente, el último elemento escribe un terminal nulo. Sin embargo, si se leen 0 bytes, el índice estará fuera de los límites del búfer, lo que conducirá 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; } .... } .... } 

En este caso, el uso de sscanf puede ser peligroso, porque si el búfer oparq es más grande que el búfer de nombre , el índice estará fuera de los límites al formar el búfer de nombre .

Conclusión


Como vemos, es una tarea bastante simple configurar una verificación del analizador de código estático en una nube. Para esto, solo necesitamos agregar un archivo en un repositorio y pasar poco tiempo configurando el sistema CI. Como resultado, obtendremos una herramienta para detectar problemas en la etapa de escritura de código. La herramienta nos permite evitar que los errores pasen a las siguientes etapas de prueba, donde su reparación requerirá mucho tiempo y esfuerzos.

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

Enlaces utiles


  • Para obtener información adicional sobre cómo ejecutar PVS-Studio en Linux y MacOS, siga el enlace .
  • También puede leer sobre la creación, configuración y uso de contenedores con el analizador de código estático PVS-Studio instalado mediante el enlace .
  • TravisCI documentación .

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


All Articles