PVS-Studio e integração contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2



Um dos cenários mais relevantes para o uso do analisador PVS-Studio é sua integração com sistemas CI. E embora a análise de um projeto PVS-Studio de quase qualquer sistema de integração contínua possa ser construída em apenas alguns comandos, continuamos a tornar esse processo ainda mais conveniente. PVS-Studio agora suporta a conversão da saída do analisador para o formato TeamCity - TeamCity Inspections Type. Vamos ver como isso funciona.



Informações sobre o software usado



PVS-Studio é um analisador estático de código , ++, C # e Java projetado para facilitar a tarefa de encontrar e corrigir vários tipos de erros. O analisador pode ser usado em Windows, Linux e macOS. Neste artigo, usaremos ativamente não apenas o próprio analisador, mas também alguns utilitários de seu kit de distribuição.



CLMonitor é um servidor de monitoramento que monitora os lançamentos do compilador. Ele deve ser executado imediatamente antes de começar a construir seu projeto. No modo monitor, o servidor interceptará as execuções de todos os compiladores suportados. Deve-se notar que este utilitário só pode ser usado para analisar projetos C / C ++.



PlogConverter é um utilitário para converter o relatório do analisador em diferentes formatos.



Informações sobre o projeto investigado



Vamos tentar essa funcionalidade com um exemplo prático - vamos analisar o projeto OpenRCT2.



OpenRCT2 é uma implementação de código aberto do RollerCoaster Tycoon 2 (RCT2), estendendo-o com novos recursos e correções de bugs. A jogabilidade gira em torno da construção e manutenção de um parque de diversões, que abriga atrações, lojas e instalações. O jogador deve tentar obter lucro e manter uma boa reputação para o parque enquanto mantém os visitantes felizes. OpenRCT2 permite que você jogue tanto no cenário quanto no sandbox. Os cenários exigem que o jogador conclua uma tarefa específica em um determinado tempo, enquanto a caixa de areia permite que o jogador construa um parque mais flexível sem quaisquer restrições ou finanças.



Configurando



Para economizar tempo, provavelmente irei pular o processo de instalação e começar no momento em que o servidor TeamCity estiver em execução no meu computador. Precisamos ir para: localhost: {a porta especificada durante a instalação} (no meu caso, localhost: 9090) e inserir os dados de autorização. Depois de entrar, seremos recebidos por:



image3.png


Clique no botão Criar Projeto. Em seguida, selecione Manualmente, preencha os campos.



image5.png


Após clicar no botão Criar , somos recebidos por uma janela com configurações.



image7.png


Clique em Criar configuração de compilação .



image9.png


Preencha os campos e clique em Criar . Vemos uma janela oferecendo para escolher um sistema de controle de versão. Como as fontes já estão localizadas localmente, clique em Ignorar .



image11.png


Finalmente, passamos para as configurações do projeto.



image13.png


Inclua etapas de construção clicando em: Etapas de construção -> Incluir etapa de construção .



image15.png


Aqui nós selecionamos:



  • Tipo de corredor -> Linha de Comando
  • Executar -> Script Personalizado


Como analisaremos durante a compilação do projeto, a montagem e a análise devem ser uma única etapa, portanto, preencha o campo Script personalizado :



image17.png


Falaremos sobre etapas individuais mais tarde. É importante que carregar o analisador, construir o projeto, analisá-lo, gerar o relatório e formatá-lo, tudo leva apenas onze linhas de código.



A última coisa que precisamos fazer é definir as variáveis ​​de ambiente, para as quais indiquei algumas maneiras de melhorar sua legibilidade. Para fazer isso, vá para: Parâmetros -> Adicionar novo parâmetro e adicionar três variáveis:



image19.png


Resta clicar no botão Executar no canto superior direito. Enquanto o projeto está sendo montado e analisado, vou falar sobre o roteiro.



Script direto



Primeiro, precisamos baixar a distribuição mais recente do PVS-Studio. Para isso, usamos o gerenciador de pacotes hocolatey. Para quem quiser saber mais sobre isso, existe um artigo relacionado :



choco install pvs-studio -y


A seguir, vamos lançar o utilitário de rastreamento de montagem do projeto CLMonitor.



%CLmon% monitor –-attach


Em seguida, construiremos o projeto, o caminho para a versão do MSBuild que preciso construir é usado como a variável de ambiente MSB



%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable


Vamos inserir o login do PVS-Studio e a chave de licença:



%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%


Depois que a compilação for concluída, execute CLMonitor novamente para gerar arquivos pré-processados ​​e análise estática:



%CLmon% analyze -l "c:\ptest.plog"


Em seguida, usaremos mais um utilitário de nosso kit de distribuição. PlogConverter converte o relatório do formato padrão para o formato específico do TeamCity. Graças a isso, poderemos vê-lo direto na janela de montagem.



%PlogConverter% "c:\ptest.plog" --renderTypes=TeamCity -o "C:\temp"


A última etapa é a saída do relatório formatado para stdout , onde será selecionado pelo analisador TeamCity.



type "C:\temp\ptest.plog_TeamCity.txt"


Código de script completo:



choco install pvs-studio -y
%CLmon% monitor --attach
set platform=x64
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%
%CLmon% analyze -l "c:\ptest.plog"
%PlogConverter% "c:\ptest.plog" --renderTypes=TeamCity -o "C:\temp"
type "C:\temp\ptest.plog_TeamCity.txt"


Nesse ínterim, a montagem e análise do projeto foram concluídas com sucesso, podemos ir para a guia Projetos e verificar isso.



image21.png


Agora clique em Inspeções totais para ver o relatório do analisador:



image23.png


Os avisos são agrupados por números de regra de diagnóstico. Para navegar pelo código, você precisa clicar no número da linha com o aviso. Clicar no ponto de interrogação no canto superior direito abrirá uma nova guia de documentação para você. Você também pode navegar pelo código clicando no número da linha de aviso do analisador. A navegação de um computador remoto é possível usando o marcador SourceTreeRoot . Aqueles que estão interessados ​​neste modo de operação do analisador podem se familiarizar com a seção correspondente da documentação .



Ver os resultados do analisador



Depois que terminarmos de implantar e configurar o build, sugiro olhar alguns avisos interessantes encontrados no projeto em estudo.



Aviso N1



V773 [CWE-401] A exceção foi lançada sem liberar o ponteiro 'resultado'. Um vazamento de memória é possível. libopenrct2 ObjectFactory.cpp 443



Object* CreateObjectFromJson(....)
{
  Object* result = nullptr;
  ....
  result = CreateObject(entry);
  ....
  if (readContext.WasError())
  {
    throw std::runtime_error("Object has errors");
  }
  ....
}

Object* CreateObject(const rct_object_entry& entry)
{
  Object* result;
  switch (entry.GetType())
  {
    case OBJECT_TYPE_RIDE:
      result = new RideObject(entry);
      break;
    case OBJECT_TYPE_SMALL_SCENERY:
      result = new SmallSceneryObject(entry);
      break;
    case OBJECT_TYPE_LARGE_SCENERY:
      result = new LargeSceneryObject(entry);
      break;
    ....
    default:
      throw std::runtime_error("Invalid object type");
  }
  return result;
}


O analisador notou um erro que, após a alocação dinâmica de memória em CreateObject , quando uma exceção é lançada, a memória não é apagada; portanto, ocorre um vazamento de memória.



Aviso N2



V501 Existem subexpressões idênticas '(1ULL << WIDX_MONTH_BOX)' à esquerda e à direita de '|' operador. libopenrct2ui Cheats.cpp 487



static uint64_t window_cheats_page_enabled_widgets[] = 
{
  MAIN_CHEAT_ENABLED_WIDGETS |
  (1ULL << WIDX_NO_MONEY) |
  (1ULL << WIDX_ADD_SET_MONEY_GROUP) |
  (1ULL << WIDX_MONEY_SPINNER) |
  (1ULL << WIDX_MONEY_SPINNER_INCREMENT) |
  (1ULL << WIDX_MONEY_SPINNER_DECREMENT) |
  (1ULL << WIDX_ADD_MONEY) |
  (1ULL << WIDX_SET_MONEY) |
  (1ULL << WIDX_CLEAR_LOAN) |
  (1ULL << WIDX_DATE_SET) |
  (1ULL << WIDX_MONTH_BOX) |  // <=
  (1ULL << WIDX_MONTH_UP) |
  (1ULL << WIDX_MONTH_DOWN) |
  (1ULL << WIDX_YEAR_BOX) |
  (1ULL << WIDX_YEAR_UP) |
  (1ULL << WIDX_YEAR_DOWN) |
  (1ULL << WIDX_DAY_BOX) |
  (1ULL << WIDX_DAY_UP) |
  (1ULL << WIDX_DAY_DOWN) |
  (1ULL << WIDX_MONTH_BOX) |  // <=
  (1ULL << WIDX_DATE_GROUP) |
  (1ULL << WIDX_DATE_RESET),
  ....
};


Poucas pessoas, exceto um analisador estático, poderiam passar neste teste de atenção. Este exemplo de copiar e colar é bom exatamente para isso.



N3



V703 É estranho que o campo 'sinalizadores' na classe derivada 'RCT12BannerElement' substitua o campo na classe base 'RCT12TileElementBase'. Verifique as linhas: RCT12.h: 570, RCT12.h: 259. libopenrct2 RCT12.h 570



struct RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};
struct rct1_peep : RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};


É claro que usar uma variável com o mesmo nome na classe base e no herdeiro nem sempre é um erro. No entanto, a própria tecnologia de herança assume a presença de todos os campos da classe pai no filho. Ao declarar um campo com o mesmo nome no herdeiro, introduzimos confusão.



Aviso N4



V793 É estranho que o resultado da instrução 'imageDirection / 8' faça parte da condição. Talvez essa afirmação devesse ser comparada com outra. libopenrct2 ObservationTower.cpp 38



void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
  if ((imageDirection / 8) && (imageDirection / 8) != 3)
  {
    ....
  }
  ....
}


Vamos olhar mais de perto. A expressão imageDirection / 8 será falsa se imageDirection estiver no intervalo de -7 a 7. A segunda parte: (imageDirection / 8)! = 3 verifica se imageDirection está fora do intervalo: -31 a -24 e 24 a 31 respectivamente. Parece-me bastante estranho verificar números para inserir um determinado intervalo dessa maneira e, mesmo que não haja nenhum erro neste fragmento de código, recomendo reescrever essas condições para ficar mais explícito. Isso tornaria a vida muito mais fácil para as pessoas que leriam e manteriam este código.



Aviso N5



V587Uma sequência ímpar de atribuições desse tipo: A = B; B = A; Verifique as linhas: 1115, 1118.libopenrct2ui MouseInput.cpp 1118



void process_mouse_over(....)
{
  ....
  switch (window->widgets[widgetId].type)
  {
    case WWT_VIEWPORT:
      ebx = 0;
      edi = cursorId;                                 // <=
      // Window event WE_UNKNOWN_0E was called here,
      // but no windows actually implemented a handler and
      // it's not known what it was for
      cursorId = edi;                                 // <=
      if ((ebx & 0xFF) != 0)
      {
        set_cursor(cursorId);
        return;
      }
      break;
      ....
  }
  ....
}


Este pedaço de código foi provavelmente obtido por descompilação. Então, a julgar pelo comentário deixado, parte do código que não funcionava foi removido. No entanto, algumas operações em cursorId permanecem , o que também não faz muito sentido.



N6 Warning



V1004 [CWE-476] O ponteiro 'player' foi usado de maneira insegura após ter sido verificado em relação a nullptr. Verifique as linhas: 2085, 2094.libopenrct2 Network.cpp 2094



void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Add new player.
    player = AddPlayer("", "");
    if (player)                                          // <=
    {
      *player = pendingPlayer;
       if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
       {
         _serverConnection->Player = player;
       }
    }
    newPlayers.push_back(player->Id);                    // <=
  }
  ....
}


Este código é bastante simples de corrigir, você precisa verificar o player para um ponteiro nulo uma terceira vez ou adicioná-lo ao corpo do operador condicional. Eu sugeriria a segunda opção:



void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Add new player.
    player = AddPlayer("", "");
    if (player)
    {
      *player = pendingPlayer;
      if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
      {
        _serverConnection->Player = player;
      }
      newPlayers.push_back(player->Id);
    }
  }
  ....
}


Aviso N7



V547 [CWE-570] A expressão 'name == nullptr' é sempre falsa. libopenrct2 ServerList.cpp 102



std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    ....
  }
  else
  {
    ....
    entry.name = (name == nullptr ? "" : json_string_value(name));
    ....
  }
  ....
}


Você pode se livrar da linha de código difícil de ler de uma só vez e resolver o problema com a verificação de nullptr . Eu sugiro alterar o código da seguinte maneira:



std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    name = ""
    ....
  }
  else
  {
    ....
    entry.name = json_string_value(name);
    ....
  }
  ....
}


Aviso N8



V1048 [CWE-1164] A variável 'ColumnHeaderPressedCurrentState' foi atribuída ao mesmo valor. libopenrct2ui CustomListView.cpp 510



void CustomListView::MouseUp(....)
{
  ....
  if (!ColumnHeaderPressedCurrentState)
  {
    ColumnHeaderPressed = std::nullopt;
    ColumnHeaderPressedCurrentState = false;
    Invalidate();
  }
}


O código parece muito estranho. Acho que houve um erro de digitação ou estar sujeito a, ou ao reatribuir o valor da variável ColumnHeaderPressedCurrentState para false .



Resultado



Como podemos ver, integrar o analisador estático PVS-Studio em seu projeto TeamCity é bastante simples. Para isso, basta escrever um pequeno arquivo de configuração. A revisão do código permitirá que você identifique problemas imediatamente após a construção, o que ajudará a eliminá-los quando a complexidade e o custo das edições ainda são pequenos.





Se você quiser compartilhar este artigo com um público de língua inglesa, use o link de tradução: Vladislav Stolyarov. PVS-Studio e integração contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2 .



All Articles