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"
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.
Após autorizar o aplicativo (clicando no botão verde "Autorizar circleci"), somos redirecionados para o "Bem-vindo ao CircleCI!" página:
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.
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.
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.
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:
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:
Depois que esse arquivo for carregado no repositório, o CircleCI iniciará automaticamente a compilação.
Depois que o trabalho é concluído, os arquivos com os resultados da análise podem ser baixados na guia "Artefatos".
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;
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),
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);
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",
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;
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. :)