PVS-Studio nas nuvens - Executando a análise no Travis CI

No momento, os sistemas de IC na nuvem são um serviço altamente exigido. Neste artigo, mostraremos como integrar a análise do código-fonte em uma plataforma de nuvem de CI com as ferramentas que já estão disponíveis no PVS-Studio. Como exemplo, usaremos o serviço Travis CI.

Quadro 1


Por que consideramos nuvens de terceiros e não criamos as nossas? Há várias razões, a principal delas é que a implementação do SaaS é um procedimento bastante caro e difícil. Na verdade, é uma tarefa simples e trivial integrar diretamente a análise do PVS-Studio em uma plataforma em nuvem de terceiros - sejam plataformas abertas como CircleCI, Travis CI, GitLab ou uma solução corporativa específica usada apenas em uma determinada empresa. Portanto, podemos dizer que o PVS-Studio já está disponível "nas nuvens". Outra questão é implementar e garantir o acesso à infraestrutura 24/7. Esta é uma tarefa mais complicada. O PVS-Studio não fornecerá sua própria plataforma de nuvem diretamente para executar análises nela.

Algumas informações sobre o software usado


O Travis CI é um serviço para criação e teste de software que usa o GitHub como armazenamento. O Travis CI não requer alteração do código de programação para usar o serviço. Todas as configurações são feitas no arquivo .travis.yml localizado na raiz do repositório.

Tomaremos o LXC (Linux Containers) como um projeto de teste para o PVS-Studio. É um sistema de virtualização no nível do sistema operacional para ativar várias instâncias do sistema operacional Linux em um nó.

O projeto é pequeno, mas é mais do que suficiente para demonstração. Saída do comando cloc:
Linguagem
arquivos
em branco
comentário
código
C
124
11937
6758
50836
Cabeçalho C / C ++
65
1117
3676
3774
Nota: Os desenvolvedores de LXC já usam o Travis CI, portanto, tomaremos o arquivo de configuração como base e editaremos para nossos propósitos.

Configuração


Para começar a trabalhar com o Travis CI, seguimos o link e efetue o login usando uma conta GitHub.

Quadro 17

Na janela aberta, precisamos fazer login no Travis CI.

Quadro 16

Após a autorização, ele é redirecionado para a página de boas-vindas "Primeira vez aqui? Vamos começar! , onde encontramos uma breve descrição do que deve ser feito posteriormente para começar:

  • habilitar os repositórios;
  • adicione o arquivo .travis.yml no repositório;
  • inicie a primeira compilação.

Quadro 18

Vamos começar a fazer essas ações.

Para adicionar nosso repositório no Travis CI, acessamos as configurações do perfil no link e pressione "Ativar".

Quadro 19

Uma vez clicada, uma janela será aberta para selecionar repositórios aos quais o aplicativo Travis CI terá acesso.

Nota: para fornecer acesso ao repositório, sua conta deve ter direitos de administrador para isso.

Quadro 38

Depois disso, escolhemos o repositório certo, confirme a opção com o botão "Aprovar e instalar" e seremos redirecionados de volta à página de configurações do perfil.

Vamos adicionar algumas variáveis ​​que usaremos para criar o arquivo de licença do analisador e enviar seus relatórios. Para fazer isso, vamos para a página de configurações - o botão "Configurações" à direita do repositório necessário.

Quadro 39

A janela de configurações será aberta.

Quadro 41

Breve descrição das configurações;

  • Seção "Geral" - configurando disparadores de tarefas de inicialização automática;
  • A seção "Cancelamento automático" permite configurar o cancelamento automático da compilação;
  • A seção “Variáveis ​​de ambiente” permite definir variáveis ​​de ambiente que contêm informações abertas e confidenciais, como informações de login, chaves ssh;
  • A seção "Trabalhos Cron" é uma configuração do agendamento de tarefas em execução.

Na seção "Variáveis ​​de ambiente", criaremos as variáveis PVS_USERNAME e PVS_KEY contendo um nome de usuário e uma chave de licença para o analisador estático, respectivamente. Se você não possui uma licença permanente do PVS-Studio, pode solicitar uma licença de avaliação .

Quadro 5

Aqui, criaremos as variáveis MAIL_USER e MAIL_PASSWORD , contendo um nome de usuário e uma senha de email, que usaremos para enviar relatórios.

Quadro 4

Ao executar tarefas, o Travis CI recebe instruções do arquivo .travis.yml, localizado na raiz do repositório.

Usando o Travis CI, podemos executar a análise estática diretamente na máquina virtual e usar um contêiner pré-configurado para fazer isso. Os resultados dessas abordagens não são diferentes entre si. No entanto, o uso de um contêiner pré-configurado pode ser útil. Por exemplo, se já temos um contêiner com algum ambiente específico, dentro do qual um produto de software é construído e testado e não queremos restaurar esse ambiente no Travis CI.

Vamos criar uma configuração para executar o analisador em uma máquina virtual.

Para construir e testar, usaremos uma máquina virtual no Ubuntu Trusty, sua descrição está disponível no link .

Primeiro, especificamos que o projeto seja escrito em C e listamos os compiladores que usaremos para a compilação:

language: c compiler: - gcc - clang 

Nota: se você especificar mais de um compilador, as tarefas serão executadas simultaneamente para cada um deles. Leia mais aqui .

Antes da compilação, precisamos adicionar o repositório do analisador, definir dependências e pacotes adicionais:

 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 criarmos um projeto, precisamos preparar seu ambiente:

 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 

Em seguida, precisamos criar um arquivo de licença e começar a analisar o projeto.

Em seguida, criamos um arquivo de licença para o analisador pelo primeiro comando. Os dados para as variáveis ​​$ PVS_USERNAME e $ PVS_KEY são obtidos das configurações do projeto.

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

Pelo próximo comando, começamos a rastrear a construção do projeto.

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

Depois disso, executamos a análise estática.
Nota: ao usar uma licença de avaliação, você precisa especificar o parâmetro --disableLicenseExpirationCheck .
  - pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic -o PVS-Studio-${CC}.log --disableLicenseExpirationCheck 

O arquivo com os resultados da análise é convertido no relatório html pelo último comando.

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

Como o TravisCI não permite alterar o formato das notificações por email, na última etapa, usaremos o pacote sendemail para enviar relatórios:

 - 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 

Aqui está o texto completo do arquivo de configuração para executar o analisador na 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 executar o PVS-Studio em um contêiner, vamos pré-criá-lo usando o seguinte 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/* 

Nesse caso, o arquivo de configuração pode ficar assim:

 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 você pode ver, nesse caso, não fazemos nada dentro da máquina virtual e todas as ações de criação e teste do projeto ocorrem dentro do contêiner.

Nota : ao iniciar o contêiner, é necessário especificar o parâmetro --cap-add SYS_PTRACE ou --security-opt seccomp: unconfined , pois uma chamada do sistema ptrace é usada para rastreamento do compilador.

Em seguida, carregamos o arquivo de configuração na raiz do repositório e vemos que o Travis CI foi notificado sobre alterações no projeto e iniciou automaticamente a compilação.

Detalhes do progresso da construção e verificação do analisador podem ser vistos no console.

Quadro 2

Após a conclusão dos testes, receberemos dois emails: o primeiro - com resultados de análise estática para a construção de um projeto usando gcc, e o segundo - para clang, respectivamente.

Brevemente sobre os resultados da verificação


Em geral, o projeto é bastante limpo, o analisador emitiu apenas 24 avisos de alta certeza e 46 de média certeza. Vejamos algumas notificações interessantes:

Condições redundantes em


V590 Considere inspecionar a expressão 'ret! = (- 1) && ret == 1'. A expressão é excessiva ou contém uma impressão incorreta. 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; } } .... } 

Se ret == 1 , definitivamente não é igual a -1 (EOF). Verificação redundante, ret! = EOF pode ser removido.

Dois avisos semelhantes foram emitidos:

  • V590 Considere inspecionar a expressão 'ret! = (- 1) && ret == 1'. A expressão é excessiva ou contém uma impressão incorreta. attach.c 579
  • V590 Considere inspecionar a expressão 'ret! = (- 1) && ret == 1'. A expressão é excessiva ou contém uma impressão incorreta. attach.c 583

Perda de bits altos


V784 O tamanho da máscara de bit é menor que o tamanho do primeiro operando. Isso causará a perda de bits mais 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; } } .... } 

No Linux, long é uma variável inteira de 64 bits, o sinalizador mo-> é uma variável inteira de 32 bits. O uso do sinalizador mo-> como uma máscara de bit levará à perda de 32 bits altos. Uma máscara de bits é convertida implicitamente em uma variável inteira de 64 bits após a inversão bit a bit. Bits altos dessa máscara podem ser perdidos.

Vou mostrar usando um exemplo:

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

Quadro 3


Aqui está a versão correta do código:

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

O analisador emitiu outro aviso semelhante:

  • V784 O tamanho da máscara de bit é menor que o tamanho do primeiro operando. Isso causará a perda de bits mais altos. conf.c 1933

Loop suspeito


V612 Um 'retorno' incondicional dentro de um loop. 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; } 

O loop é iniciado e interrompido na primeira iteração. Isso pode ter sido feito intencionalmente, mas nesse caso o loop poderia ter sido omitido.

Índice de matriz fora dos limites


V557 Array underrun é possível. O valor do índice 'bytes - 1' pode chegar a -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'; } .... } 

Os bytes são lidos no buffer do canal. Em caso de erro, a função lxc_read_nointr retornará um valor negativo. Se tudo der certo, um nulo terminal será gravado pelo último elemento. No entanto, se 0 bytes for lido, o índice ficará fora dos limites do buffer, levando a um comportamento indefinido.

O analisador emitiu outro aviso semelhante:

  • V557 Array underrun é possível. O valor do índice 'bytes - 1' pode chegar a -1. network.c 2725

Estouro de buffer


V576 Formato incorreto. Considere verificar o terceiro argumento real da função 'sscanf'. É perigoso usar um especificador de string sem especificação de largura. O estouro de buffer é possível. 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; } .... } .... } 

Nesse caso, o uso do sscanf pode ser perigoso, porque se o buffer oparq for maior que o buffer de nome , o índice ficará fora dos limites ao formar o buffer de nome .

Conclusão


Como vemos, é uma tarefa bastante simples configurar uma verificação de analisador de código estático em uma nuvem. Para isso, basta adicionar um arquivo em um repositório e gastar pouco tempo configurando o sistema de IC. Como resultado, obteremos uma ferramenta para detectar problemas no estágio de escrita do código. A ferramenta nos permite impedir que os bugs cheguem aos próximos estágios dos testes, nos quais sua correção exigirá muito tempo e esforços.

Obviamente, o uso do PVS-Studio com plataformas em nuvem não se limita apenas ao Travis CI. Semelhante ao método descrito no artigo, com pequenas diferenças, a análise do PVS-Studio pode ser integrada a outras soluções populares de IC na nuvem, como CircleCI, GitLab, etc.

Links úteis


  • Para informações adicionais sobre a execução do PVS-Studio no Linux e MacOS, siga o link .
  • Você também pode ler sobre a criação, configuração e uso de contêineres com o analisador de código estático instalado do PVS-Studio pelo link .
  • Documentação do TravisCI .

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


All Articles