PVS-Studio vai para as nuvens: CircleCI

Quadro 2

Continuamos a série de artigos sobre o uso do analisador estático PVS-Studio em sistemas de CI na nuvem. Hoje estamos considerando outro serviço - CircleCI. Desta vez, o media player Kodi atuará como um projeto para análise, no código fonte do qual tentaremos encontrar lugares interessantes.

Nota Outros artigos sobre a integração do PVS-Studio em sistemas de CI na nuvem podem ser encontrados aqui:


Antes de prosseguirmos diretamente para configurar e analisar os avisos do analisador, digamos algumas palavras sobre o software usado e analisado.

O CircleCI é um serviço de IC baseado em nuvem para automatizar a montagem, teste e publicação de software. Ele suporta a montagem de projetos em contêineres e em máquinas virtuais executando Windows, Linux e macOS.

O Kodi é um reprodutor de mídia multiplataforma gratuito e de código aberto. Permite reproduzir arquivos de áudio e vídeo localizados no computador doméstico e em uma rede local ou na Internet. Ele suporta temas e funcionalidades instalando plugins. Disponível para Windows, Linux, macOS e Android.

O PVS-Studio é um analisador de código estático para procurar erros e possíveis vulnerabilidades no código escrito em C, C ++, C # e Java. Funciona com Windows, Linux e macOS.

Personalização


Vá para a página inicial do CircleCI e clique no botão "Inscrever-se"

Quadro 1

Na próxima página, seremos solicitados a autenticar com uma conta GitHub ou Bitbucket. Selecione GitHub e acesse a página de autorização do aplicativo CircleCI.

Quadro 3

Autorizamos o aplicativo (botão verde "Autorizar circleci") e redirecionamos para a página de boas-vindas "Bem-vindo ao CircleCI!"

Quadro 4

Nesta página, podemos configurar imediatamente quais projetos serão montados no CircleCI. Marcamos nosso repositório e clicamos em "Seguir".

Após adicionar o repositório, o CircleCI iniciará automaticamente a compilação, mas desde ainda não existe um arquivo de configuração no repositório - a tarefa de construção falhará.

Quadro 5

Antes de adicionar o arquivo de configuração, adicionaremos às variáveis ​​do projeto que contêm dados de licença para o analisador. Para fazer isso, clique em "Configurações" no painel esquerdo e, no grupo "ORGANIZAÇÃO", selecione o item "Projetos" e clique na engrenagem à direita do projeto que precisamos. Uma janela de configurações será aberta.

Quadro 6

Estamos interessados ​​na seção "Variáveis ​​de ambiente". Entramos nele e criamos as 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 uma construção do projeto, o CircleCI lê a configuração da tarefa a partir de um arquivo no repositório no caminho .circleci / config.yml. Adicione.

Primeiro, indicamos a imagem da máquina virtual onde o analisador será iniciado. Uma lista completa de imagens está disponível aqui .

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

Em seguida, adicione os repositórios necessários para o apt e instale 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 

Adicione o repositório PVS-Studio e instale 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" 

Vamos coletar as dependências do projeto:

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

Gere Makefiles no diretório de montagem:

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

O próximo passo é configurar e executar uma análise estática do nosso projeto.

Primeiro, crie um arquivo com a licença do analisador. O segundo comando inicia a compilação do rastreio de montagem do projeto.

Após o rastreamento, iniciamos diretamente a análise estática. Ao usar uma licença de avaliação, o analisador deve ser iniciado com o parâmetro:

--disableLicenseExpirationCheck .

O último comando converte o arquivo com os resultados 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 concluir os testes, salve os relatórios do analisador:

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

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 

Carregamos o arquivo no repositório e o CircleCI iniciará automaticamente a montagem do projeto.

Quadro 12

Após o término da tarefa, os arquivos com os resultados do analisador podem ser baixados na guia "Artefatos".

Quadro 11

Resultados da análise


Bem, agora vamos dar uma olhada em alguns avisos emitidos pelo analisador durante o trabalho.

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 julgar pela formatação do código, foi assumida a seguinte lógica de execução:

  • se arttypes for um ponteiro nulo, conclua a execução do método;
  • se arttypes for um ponteiro diferente de zero, limpe o vetor artworkMap e execute outras ações.

No entanto, o caractere ausente ';' fez ajustes; como resultado, a lógica de execução não corresponde à formatação. Como resultado, torna-se o seguinte:

  • se arttypes for um ponteiro nulo, o vetor artworkMap é limpo e o método sai;
  • se arttypes for um ponteiro diferente de zero, outras ações serão executadas, mas o vetor artworkMap não será limpo.

Em geral, é muito improvável que não haja erro. E quase ninguém escreveria expressões no espírito de retorno 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; } } .... } 

Preste atenção nos locais marcados com // <= . O valor 0 é gravado na variável lastsector e, em seguida, é usado duas vezes como expressão condicional da instrução if . Como o valor da variável não muda no loop ou entre essas atribuições, as ramificações de ambas as instruções if não serão executadas.

Pode ser verdade que a funcionalidade necessária ainda não foi implementada (preste atenção em todo ).

A propósito, como você pode ver, o analisador emitiu imediatamente 3 avisos para esse código. Às vezes, no entanto, apenas alguns avisos não são suficientes e os usuários continuam acreditando que o analisador está errado ... Um colega escreveu mais sobre isso no artigo " Um dia do 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(); } 

Nesse caso, a verificação de values.size ()! = 2 é redundante, pois o resultado da expressão condicional será sempre falso . De fato, se a execução entrar em um dos ramos do caso switch , 2 elementos serão adicionados ao vetor e, como estava vazio, seu tamanho se tornará igual a dois; caso contrário (ao executar a ramificação padrão ), o método será encerrado.

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 , após o qual também é usada no operador ternário na comparação prio == INT_MAX . No entanto, entre o local de inicialização e uso, seu valor não muda, portanto, o valor da expressão prio == INT_MAX é verdadeiro 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 - o ponteiro obtido como resultado da chamada da função malloc é usado ainda mais na função memcpy sem primeiro verificar NULL .

Alguém pode se opor a esses avisos na seguinte chave: malloc nunca retornará um ponteiro nulo e, se o fizer, deixe o aplicativo cair melhor. Este é um tópico para uma longa discussão, mas de uma forma ou de outra proponho ler a nota do meu colega - " Por que é importante verificar se a função malloc retornou "?

Se desejar, você pode configurar o analisador para se comportar de tal forma que não considere que o malloc possa retornar um ponteiro nulo - então não haverá tais avisos. Leia mais sobre isso aqui .

PVS-Studio Warning : V522 Pode haver desreferenciamento de uma possível entrada nula 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); .... } 

A situação é semelhante à descrita acima. O ponteiro obtido como resultado da chamada ao malloc é gravado na variável de entrada , após o qual é usado sem verificar NULL ( entry-> d_name ).

PVS-Studio Warning : 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 ponteiro progressHandler contém o valor obtido chamando o operador new . No entanto, a exclusão do operador não é chamada para esse ponteiro, o que causa um vazamento de memória.

PVS-Studio Warning : V557 Array overrun é possível. 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 impõe restrições ao tamanho do vetor m_vecPlayerConfigs devido à expressão condicional e sai do método, se for verdadeiro. Como resultado, se a execução do código atingir a última instrução de retorno , o tamanho do vetor m_vecPlayerConfigs estará no intervalo especificado: [1; idx]. No entanto, algumas linhas abaixo são a chamada idx : m_vecPlayerConfigs [idx] -> m_bPlaysVideo . Como resultado, se idx for igual ao tamanho do vetor, ele ultrapassará a borda do intervalo permitido.

E, finalmente, dê uma olhada em alguns avisos no código da biblioteca Platinum .

PVS-Studio Warning : 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); .... } 

Nesse caso, a prioridade das operações é confusa. Const char * não é o resultado da computação do operador ternário ( assinatura? "S": "Uns" ), mas a assinatura variável. Pelo menos parece estranho.

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 espaço é 0x20, o código da guia é 0x09. Portanto, a subexpressão c == '\ t' sempre será falsa , pois esse caso já está coberto pela expressão c <'' (se verdadeira, a função será encerrada).

Conclusão


Como você pode ver neste artigo, no próximo sistema de CI (CircleCI), conseguimos configurar a verificação do projeto usando o PVS-Studio. Sugiro que você baixe e experimente o analisador em seu projeto. Se você tiver alguma dúvida sobre como configurar ou usar o analisador, sinta-se à vontade para nos escrever , teremos prazer em ajudar.

E, claro, o código de descuido para vocês, amigos. :)



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Sergey Vasiliev, Ilya Gainulin. PVS-Studio nas nuvens: CircleCI .

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


All Articles