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"
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.
Autorizamos o aplicativo (botão verde "Autorizar circleci") e redirecionamos para a página de boas-vindas "Bem-vindo ao CircleCI!"
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á.
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.
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.
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:
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:
Carregamos o arquivo no repositório e o CircleCI iniciará automaticamente a montagem do projeto.
Após o término da tarefa, os arquivos com os resultados do analisador podem ser baixados na guia "Artefatos".
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;
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),
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);
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",
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;
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 .