PVS-Studio nas nuvens: CircleCI

Quadro 2

Esta é uma nova parte de nossa série de artigos sobre o uso do analisador estático PVS-Studio com sistemas de IC na nuvem. Hoje vamos olhar para outro serviço, o CircleCI. Tomaremos o aplicativo Kodi media player como um projeto de teste e veremos se encontramos algum bug interessante em seu código-fonte.
Nota Os artigos anteriores sobre a integração do PVS-Studio com sistemas de CI na nuvem:


Antes de configurar o ambiente de trabalho e examinar o relatório de análise, gostaria de dizer algumas palavras sobre o software que vamos usar e verificar.

O CircleCI é um serviço de IC na nuvem para criação, teste e implantação automatizados de software. Ele suporta a criação de projetos em contêineres e em máquinas virtuais no Windows, Linux e macOS.

O Kodi é um aplicativo de reprodutor de mídia multiplataforma gratuito e de código aberto. Ele permite que os usuários reproduzam e visualizem a maioria das mídias de streaming, como vídeos, músicas, podcasts e vídeos da Internet, bem como todos os arquivos de mídia digital comuns da mídia de armazenamento local e de rede. Ele suporta o uso de temas e skins e extensões de funcionalidade por meio de plugins. O Kodi está disponível para Windows, Linux, macOS e Android.

O PVS-Studio é um analisador estático para detectar erros e possíveis vulnerabilidades no código-fonte dos aplicativos escritos em C, C ++, C # e Java. O analisador é executado no Windows, Linux e macOS.

Configurando


Primeiro, precisamos ir para a página principal do CircleCI e clicar em "Inscreva-se"

Quadro 1

Na próxima página, somos autorizados a autorizar com uma conta GitHub ou Bitbucket. Escolhemos o GitHub e chegamos à página de autorização do CircleCI.

Quadro 3

Após autorizar o aplicativo (clicando no botão verde "Autorizar circleci"), somos redirecionados para o "Bem-vindo ao CircleCI!" página:

Quadro 4

Aqui podemos especificar imediatamente quais projetos queremos que o CircleCI construa. Marque nosso repositório e clique em "Seguir".

Após adicionar o repositório, o CircleCI iniciará automaticamente o processo de compilação, mas como ainda não temos um arquivo de configuração em nosso repositório, o trabalho de compilação será interrompido com uma mensagem de erro.

Quadro 5

Antes de adicionar um arquivo de configuração, precisamos adicionar algumas variáveis ​​que contêm dados de licença do analisador. Para fazer isso, clique em "Configurações" na barra lateral esquerda, selecione "Projetos" na seção "ORGANIZAÇÃO" e clique no botão de roda dentada à direita do nome do nosso projeto. Uma janela de configurações será exibida.

Quadro 6

Vamos para a página "Variáveis ​​de ambiente". Aqui, criamos duas variáveis, PVS_USERNAME e PVS_KEY , que contêm o nome de usuário e a chave de licença do analisador.

Quadro 7

Ao iniciar a construção, o CircleCI lê a configuração da tarefa no arquivo armazenado no repositório em .circleci / config.yml. Vamos adicionar.

Primeiro, precisamos especificar a imagem da máquina virtual na qual o analisador estará executando. A lista completa de imagens está disponível aqui .

version: 2 jobs: build: machine: image: ubuntu-1604:201903-01 

Em seguida, adicionamos os repositórios necessários para o apt e instalar as dependências do projeto:

 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget 

Incluindo o repositório PVS-Studio e instalando o analisador:

 - run: 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 - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" 

Então estamos construindo as dependências:

 - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local 

Depois disso, geramos Makefiles no diretório build:

 - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. 

O próximo passo é configurar e iniciar uma análise do projeto.

Primeiro, criamos um arquivo de licença do analisador. Outro comando começará a rastrear a compilação do projeto pelo compilador.

O próximo comando após o rastreamento executa a análise como tal. Se você usar uma versão demo do PVS-Studio, inicie-a com o parâmetro:

--disableLicenseExpirationCheck .

O comando final converte o arquivo de relatório do analisador em um relatório html:

 - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log 

Após a conclusão dos testes, salvamos os relatórios:

 - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

Aqui está o texto completo do arquivo .circleci / config.yml:

 version: 2.1 jobs: build: machine: image: ubuntu-1604:201903-01 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget - run: 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 - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

Depois que esse arquivo for carregado no repositório, o CircleCI iniciará automaticamente a compilação.

Quadro 12

Depois que o trabalho é concluído, os arquivos com os resultados da análise podem ser baixados na guia "Artefatos".

Quadro 11

Resultados da análise


OK, agora vamos dar uma olhada em alguns dos avisos emitidos pelo analisador.

Aviso do PVS-Studio: V504 É altamente provável que o ponto e vírgula ';' está ausente após a palavra-chave 'return'. AdvancedSettings.cpp: 1476

 void CAdvancedSettings::SetExtraArtwork(const TiXmlElement* arttypes, std::vector<std::string>& artworkMap) { if (!arttypes) return artworkMap.clear(); const TiXmlNode* arttype = arttypes->FirstChild("arttype"); .... } 

A formatação do código sugere a seguinte lógica de execução:

  • se arttypes for um ponteiro nulo, o método retornará;
  • se arttypes for um ponteiro não nulo, o vetor artworkMap será limpo e algumas ações serão executadas.

Mas a falta ';' O caractere quebra tudo e a lógica de execução real é a seguinte:

  • se arttypes for um ponteiro nulo, o vetor artworkMap será limpo e o método retornará;
  • se arttypes for um ponteiro não nulo, o programa executará as ações que virem a seguir, mas o vetor artworkMap não será limpo.

Para resumir uma longa história, essa situação parece um bug. Afinal, você dificilmente espera que alguém escreva expressões como return artworkMap.clear (); :).

Avisos do PVS-Studio:

  • A expressão V547 'lastsector' é sempre falsa. udf25.cpp: 636
  • A expressão V547 'lastsector' é sempre falsa. udf25.cpp: 644
  • V571 Verificação recorrente. A condição 'if (lastsector)' já foi verificada na linha 636. udf25.cpp: 644

 int udf25::UDFGetAVDP( struct avdp_t *avdp) { .... uint32_t lastsector; .... lastsector = 0; // <= .... for(;;) { .... if( lastsector ) { // <= V547 lbnum = lastsector; terminate = 1; } else { //! @todo Find last sector of the disc (this is optional). if( lastsector ) // <= V547 lbnum = lastsector - 256; else return 0; } } .... } 

Observe os pontos marcados com // <= . A variável do setor final é atribuída ao valor 0 e, em seguida, é usada como expressão condicional em duas instruções if . Como o valor não muda no loop ou entre as atribuições, o controle nunca entrará nos ramos else das instruções if .

No entanto, isso também pode significar que os desenvolvedores simplesmente ainda não implementaram a funcionalidade pretendida (observe a observação do todo ).

A propósito, como você provavelmente notou, esse trecho acionou três avisos ao mesmo tempo. Mas mesmo que muitos avisos para um pedaço de código não pareçam convincentes o suficiente para alguns usuários, e eles continuarão acreditando que o analisador está errado ... Esse aspecto é discutido em detalhes em uma postagem de um dos meus colegas de equipe: " Um dia de Suporte ao Usuário do PVS-Studio ":).

Aviso do PVS-Studio: A expressão V547 'values.size ()! = 2' é sempre falsa. GUIControlSettings.cpp: 1174

 bool CGUIControlRangeSetting::OnClick() { .... std::vector<CVariant> values; SettingConstPtr listDefintion = settingList->GetDefinition(); switch (listDefintion->GetType()) { case SettingType::Integer: values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorUpper)); break; case SettingType::Number: values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorUpper)); break; default: return false; } if (values.size() != 2) return false; SetValid(CSettingUtils::SetList(settingList, values)); return IsValid(); } 

A verificação values.size ()! = 2 é redundante aqui, pois essa expressão condicional sempre será avaliada como falsa . De fato, se a execução inserir um dos ramos do caso da instrução switch , dois elementos serão adicionados ao vetor e, como inicialmente vazio, seu tamanho naturalmente se tornará igual a 2; caso contrário (ou seja, se a ramificação padrão for executada), o método retornará.

Aviso do PVS-Studio: A expressão V547 'prio == 0x7fffffff' sempre é verdadeira. DBusReserve.cpp: 57

 bool CDBusReserve::AcquireDevice(const std::string& device) { .... int prio = INT_MAX; .... res = dbus_bus_request_name( m_conn, service.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE | (prio == INT_MAX ? 0 : DBUS_NAME_FLAG_ALLOW_REPLACEMENT), // <= error); .... } 

A variável prio é inicializada com o valor INT_MAX e, em seguida, usada como um operando do operador ternário na comparação prio == INT_MAX , embora seu valor não seja alterado após a inicialização. Isso significa que a expressão prio == INT_MAX é verdadeira e o operador ternário sempre retornará 0.

Avisos do PVS-Studio:

  • V575 O potencial ponteiro nulo é passado para a função 'memcpy'. Inspecione o primeiro argumento. Verifique as linhas: 39, 38. DVDOverlayImage.h: 39
  • V575 O potencial ponteiro nulo é passado para a função 'memcpy'. Inspecione o primeiro argumento. Verifique as linhas: 44, 43. DVDOverlayImage.h: 44

 CDVDOverlayImage(const CDVDOverlayImage& src) : CDVDOverlay(src) { Data = (uint8_t*)malloc(src.linesize * src.height); memcpy(data, src.data, src.linesize * src.height); // <= if(src.palette) { palette = (uint32_t*)malloc(src.palette_colors * 4); memcpy(palette, src.palette, src.palette_colors * 4); // <= } .... } 

Ambos os avisos têm o mesmo padrão: um ponteiro retornado pela função malloc é usado ainda mais na função memcpy sem ser verificado primeiro como NULL .

Alguns podem argumentar que o malloc nunca retornará um ponteiro nulo e, se o fizer, seria melhor o aplicativo travar. É um assunto de uma discussão separada, mas seja qual for a sua opinião, recomendo a leitura deste post pelo meu companheiro de equipe: " Por que é importante verificar o que a função malloc retornou ".

Se desejar, você pode personalizar o analisador para que ele não assuma que o malloc possa retornar um ponteiro nulo - isso impedirá a emissão desse tipo de aviso. Mais detalhes podem ser encontrados aqui .

Aviso do PVS-Studio: V522 Pode haver desreferenciamento de uma potencial 'entrada' de ponteiro nulo. Verifique as linhas: 985, 981. emu_msvcrt.cpp: 985

 struct dirent *dll_readdir(DIR *dirp) { .... struct dirent *entry = NULL; entry = (dirent*) malloc(sizeof(*entry)); if (dirData->curr_index < dirData->items.Size() + 2) { if (dirData->curr_index == 0) strncpy(entry->d_name, ".\0", 2); .... } 

Este exemplo é semelhante ao anterior. O ponteiro retornado pela função malloc é armazenado na variável de entrada , e essa variável é usada sem uma verificação nula anterior ( entry-> d_name ).

Aviso do PVS-Studio: O escopo de visibilidade V773 do ponteiro 'progressHandler' foi encerrado sem liberar a memória. É possível um vazamento de memória. PVRGUIChannelIconUpdater.cpp: 94

 void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const { .... CPVRGUIProgressHandler* progressHandler = new CPVRGUIProgressHandler(g_localizeStrings.Get(19286)); for (const auto& group : m_groups) { const std::vector<PVRChannelGroupMember> members = group->GetMembers(); int channelIndex = 0; for (const auto& member : members) { progressHandler->UpdateProgress(member.channel->ChannelName(), channelIndex++, members.size()); .... } progressHandler->DestroyProgress(); } 

O valor do ponteiro progressHandler foi retornado pelo novo operador. Mas não há operador de exclusão para esse ponteiro. Isso significa um vazamento de memória.

Aviso do PVS-Studio: É possível saturação da matriz V557 . O índice 'idx' está apontando além do limite da matriz. PlayerCoreFactory.cpp: 240

 std::vector<CPlayerCoreConfig *> m_vecPlayerConfigs; bool CPlayerCoreFactory::PlaysVideo(const std::string& player) const { CSingleLock lock(m_section); size_t idx = GetPlayerIndex(player); if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size()) return false; return m_vecPlayerConfigs[idx]->m_bPlaysVideo; } 

A instrução if restringe o tamanho do vetor m_vecPlayerConfigs a um determinado intervalo, retornando o método se a condição de verificação de tamanho for verdadeira. Como resultado, quando a execução atingir a última instrução de retorno , o tamanho do vetor m_vecPlayerConfigs estará dentro do intervalo especificado, [1; idx]. Mas algumas linhas depois, o programa está indexando o vetor em idx : m_vecPlayerConfigs [idx] -> m_bPlaysVideo . Isso significa que, se idx for igual ao tamanho do vetor, indexaremos além do intervalo válido.

Vamos encerrar este artigo com alguns exemplos do código da biblioteca Platinum .

Aviso do PVS-Studio: V542 Considere inspecionar uma conversão de tipo ímpar: 'bool' para 'char *'. PltCtrlPoint.cpp: 1617

 NPT_Result PLT_CtrlPoint::ProcessSubscribeResponse(...) { .... bool subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE"); .... NPT_String prefix = NPT_String::Format(" PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for service \"%s\" (result = %d, status code = %d)", (const char*)subscription?"S":"Uns", // <= (const char*)service->GetServiceID(), res, response?response->GetStatusCode():0); .... } 

Os desenvolvedores tiveram suposições erradas sobre a precedência das operações. O que é convertido para const char * não é o resultado retornado pelo operador ternário ( assinatura? "S": "Uns" ), mas a variável de assinatura . Isso parece estranho, no mínimo.

Aviso do PVS-Studio: V560 Uma parte da expressão condicional é sempre falsa: c == '\ t'. NptUtils.cpp: 863

 NPT_Result NPT_ParseMimeParameters(....) { .... case NPT_MIME_PARAMETER_PARSER_STATE_NEED_EQUALS: if (c < ' ') return NPT_ERROR_INVALID_SYNTAX; // END or CTLs are invalid if (c == ' ' || c == '\t') continue; // ignore leading whitespace .... } 

O código do caractere de espaço é 0x20 e o código do caractere de tabulação é 0x09. Portanto, a subexpressão c == '\ t' sempre será avaliada como falsa, pois esse caso já está coberto pela verificação c <'' (que, se verdadeira, fará com que a função retorne).

Conclusão


Como este artigo demonstra, configuramos com sucesso uma análise do PVS-Studio em outro sistema de CI (CircleCI). Convido você a baixar e experimentar o analisador em seu próprio projeto. Se você tiver alguma dúvida sobre a configuração ou o uso do PVS-Studio, não hesite em nos contactar - teremos prazer em ajudar.

E, é claro, desejamos um código sem erros. :)

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


All Articles