PVS-Estudio en las nubes:Travis CI

PVS-Estudio en las nubes:Travis CI

Actualmente, 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 en la nube de CI con las herramientas que ya están disponibles en PVS-Studio. Como ejemplo, usaremos el servicio Travis CI.

Para obtener información actualizada al respecto, siga la página de documentación actualizada "Uso con Travis CI".

¿Por qué consideramos nubes de terceros y no creamos las nuestras? Hay una serie de 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 de PVS-Studio en una plataforma de nube de terceros, ya sean plataformas abiertas como CircleCI, Travis CI, GitLab o una solución empresarial específica utilizada solo en una determinada empresa. Por tanto, podemos decir que PVS-Studio ya está disponible "en las nubes". Otro tema 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 (Linux Containers) como proyecto de prueba para PVS-Studio. Es un sistema de virtualización a nivel de 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

comentario

código

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.

Configuración

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

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

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

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

Empecemos a hacer estas acciones.

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

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

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

Después de eso, elegimos el repositorio correcto, confirmamos la elección con el botón "Aprobar e instalar" y seremos redirigidos 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.

Se abrirá la ventana de configuración.

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 la 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, claves ssh;
  • La sección "Cron Jobs" es una configuración del cronograma de ejecución de tareas.

En la sección "Variables de entorno" crearemos variables PVS_USERNAME y PVS_KEY que contiene 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.

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

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 usar un contenedor preconfigurado para hacerlo. Los resultados de estos enfoques no son diferentes entre sí. Sin embargo, el uso de un contenedor preconfigurado puede resultar útil. Por ejemplo, si ya tenemos un contenedor con algún entorno específico, dentro del cual se construye y prueba un producto de software y no queremos restaurar este entorno en Travis CI.

Vamos a crear una configuración para ejecutar el analizador en una máquina virtual.

Para construir y probar usaremos 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. Lea más aquí.

Antes de la compilación, debemos 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.pvs-studio.com/etc/pubkey.txt |sudo apt-key add -
 - sudo wget -O /etc/apt/sources.list.d/viva64.list
  https://files.pvs-studio.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, debemos 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

A continuación, debemos crear un archivo de licencia y comenzar a analizar el proyecto.

Luego creamos un archivo de licencia para el analizador con el primer comando. Datos para $PVS_USERNAME y $PVS_KEY variables se toma 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 un análisis estático.

Nota: al usar 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 con el último comando.

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

Dado que 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 [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

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.pvs-studio.com/etc/pubkey.txt |sudo apt-key add -
 - sudo wget -O /etc/apt/sources.list.d/viva64.list
          https://files.pvs-studio.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 [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

Para ejecutar PVS-Studio en un contenedor, vamos a crearlo previamente con 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.pvs-studio.com/etc/pubkey.txt | 
        sudo apt-key add - \
    && wget -O /etc/apt/sources.list.d/viva64.list
       https://files.pvs-studio.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 [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"

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:unconfined , ya que se utiliza una llamada al sistema ptrace para el seguimiento 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.

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.

Resumen de los resultados de la comprobación

En general, el proyecto es bastante limpio, el analizador emitió solo 24 advertencias de certeza alta 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 tipográfico. adjuntar.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 han emitido dos advertencias similares:

  • V590 Considere inspeccionar la expresión 'ret !=(- 1) &&ret ==1'. La expresión es excesiva o contiene un error tipográfico. adjuntar.c 579
  • V590 Considere inspeccionar la expresión 'ret !=(- 1) &&ret ==1'. La expresión es excesiva o contiene un error tipográfico. adjuntar.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 provocará la pérdida de bits más altos. conferencia 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;
    }
  }
  ....
}

Bajo Linux, largo es una variable entera de 64 bits, mo->flag es una variable entera de 32 bits. Uso de mo->bandera como una máscara de bits conducirá a 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. Es posible que se pierdan partes altas de esta máscara.

Lo mostraré usando un ejemplo:

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

Esta es 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 provocará la pérdida de bits más altos. conferencia c 1933

Bucle 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 bucle se inicia y se 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 los límites

Es posible el agotamiento de la matriz V557. El valor del índice 'bytes - 1' podría llegar a -1. red.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 desde la tubería. En caso de error, el lxc_read_nointr La función devolverá un valor negativo. Si todo va bien, 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 generará un comportamiento indefinido.

El analizador emitió otra advertencia similar:

  • V557 Array underrun es posible. El valor del índice 'bytes - 1' podría llegar a -1. red.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. Es posible el desbordamiento del búfer. 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 oparq el búfer es más grande que el nombre búfer, el índice estará fuera de los límites al formar el nombre buffer.

Conclusión

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

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

Enlaces útiles

  • 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 en el enlace.
  • Documentación de TravisCI.