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

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. Embora a análise de projetos com PVS-Studio possa ser incorporada a praticamente qualquer sistema de integração contínua com apenas alguns comandos, continuamos a tornar esse processo ainda mais conveniente. O PVS-Studio agora suporta a conversão da saída do analisador para o formato do TeamCity — TeamCity Inspections Type. Vamos ver como isso funciona.

Informação sobre o software utilizado

PVS-Studio — um analisador estático para códigos em C, C++, C# e Java, destinado a facilitar a identificação e correção de diferentes tipos de erros. O analisador pode ser utilizado em Windows, Linux e macOS. Neste artigo, utilizaremos ativamente não apenas o analisador em si, mas também algumas utilidades de seu pacote.

CLMonitor — é um servidor de monitoramento que rastreia a execução de compiladores. Ele deve ser iniciado imediatamente antes do início da construção do seu projeto. Em modo de rastreamento, o servidor irá interceptar as execuções de todos os compiladores suportados. Vale ressaltar que esta utilidade pode ser utilizada apenas para análise de projetos em C/C++.

PlogConverter – uma utilidade para converter o relatório do analisador em diferentes formatos.

Informação sobre o projeto analisado

Vamos experimentar essa funcionalidade com um exemplo prático – vamos analisar o projeto OpenRCT2.

OpenRCT2 — uma implementação aberta do jogo RollerCoaster Tycoon 2 (RCT2), que expande suas funcionalidades e corrige erros. O jogo gira em torno da construção e manutenção de um parque de diversões, onde há atrações, lojas e instalações. O jogador deve tentar obter lucros e manter uma boa reputação do parque, garantindo ao mesmo tempo a felicidade dos visitantes. O OpenRCT2 permite jogar tanto em modo de scénarios quanto em modo sandbox. Os scénarios exigem que o jogador cumpra uma determinada tarefa em um tempo definido, enquanto o modo sandbox permite que o jogador construa um parque mais flexível, sem restrições ou limitações financeiras.

Configuração

Para economizar tempo, vou pular o processo de instalação e começar no momento em que o servidor TeamCity está rodando no meu computador. Precisamos acessar: localhost:{port especificado no processo de instalação} (no meu caso, localhost:9090) e inserir os dados de autenticação. Após o login, seremos recebidos por:

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Clique no botão Criar Projeto. Em seguida, escolha Manualmente e preencha os campos.

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Após clicar no botão Criar, somos recebidos com uma janela de configurações.

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Clique em Criar configuração de build.

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Preencha os campos, clique em Criar. Vemos uma janela oferecendo a escolha do sistema de controle de versões. Como os fontes já estão localmente, clique em Pular.

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Por fim, passamos para as configurações do projeto.

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Adicione etapas de build, para isso clique em: Etapas de build -> Adicionar etapa de build.

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Aqui escolheremos:

  • Tipo de runner -> Linha de comando
  • Executar -> Script personalizado

Como faremos a análise durante a compilação do projeto, a build e a análise devem ser um único passo, então preencha o campo Script personalizado:

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Falaremos sobre etapas separadas mais adiante. É importante que o carregamento do analisador, a compilação do projeto, a análise, a geração do relatório e sua formatação ocupem apenas onze linhas de código.

Por último, precisamos definir variáveis de ambiente, para as quais eu indiquei alguns caminhos para melhorar a legibilidade. Para isso, vamos para: Parâmetros -> Adicionar novo parâmetro e adicione três variáveis:

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Agora, basta clicar no botão Run no canto superior direito. Enquanto a build e a análise do projeto estão em andamento, vou falar sobre o script.

O script propriamente dito

Primeiro, precisamos baixar a versão mais recente do PVS-Studio. Para isso, utilizamos o gerenciador de pacotes Chocolatey. Para aqueles que desejam saber mais sobre isso, existe uma artigo:

choco install pvs-studio -y

Em seguida, executaremos o utilitário de monitoramento da build do projeto CLMonitor.

%CLmon% monitor –-attach

Depois, realizaremos a build do projeto, onde a variável de ambiente MSB representa o caminho necessário para a versão do MSBuild que quero usar.

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

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

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

Após a conclusão da build, executaremos novamente o CLMonitor para gerar os arquivos pré-processados e a análise estática:

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

Depois, utilizaremos outra ferramenta do nosso pacote. O PlogConverter transforma o relatório do formato padrão para o formato específico do TeamCity. Com isso, poderemos visualizá-lo diretamente na janela da build.

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

Como último passo, iremos gerar o relatório formatado em stdout, onde será capturado pelo parser do TeamCity.

type "C:tempptest.plog_TeamCity.txt"

Código completo do script:

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:tempptest.plog_TeamCity.txt"

Enquanto isso, a construção e análise do projeto foram concluídas com sucesso, podemos ir para a aba Projetos e confirmar isso.

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Agora, vamos clicar em Inspeções Totais, para ver o relatório do analisador:

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Os avisos estão agrupados por números de regras diagnósticas. Para navegar pelo código, basta clicar no número da linha onde está o aviso. Clicar no sinal de interrogação no canto superior direito abrirá uma nova aba com a documentação. A navegação pelo código também pode ser feita clicando no número da linha do aviso do analisador. A navegação de um computador remoto é possível utilizando SourceTreeRoot como um marcador. Aqueles que estão interessados neste modo de operação do analisador podem consultar a seção correspondente documentação.

Visualização dos Resultados do Analisador

Após termos finalizado a configuração e implantação da construção, convido você a ver alguns avisos interessantes que foram detectados no projeto em análise.

Aviso N1

V773 [CWE-401] A exceção foi lançada sem liberar o ponteiro 'result'. Uma 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 detectou um erro, que consiste no fato de que, após a alocação dinâmica de memória em CreateObject, quando uma exceção ocorre, a memória não é liberada, levando assim a um vazamento de memória.

Aviso N2

V501 Há subexpressões idênticas '(1ULL << WIDX_MONTH_BOX)' à esquerda e à direita do 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),
  ....
};

Poucos, além do analisador estático, conseguiriam passar neste teste de atenção. Este exemplo de copy-paste é bom exatamente por isso.

Avisos N3

V703 É estranho que o campo 'flags' na classe derivada 'RCT12BannerElement' sobrescreva 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, usar uma variável com o mesmo nome na classe base e no filho nem sempre é um erro. No entanto, a tecnologia de herança pressupõe a presença de todos os campos da classe pai na filha. Ao declarar campos com o mesmo nome no filho, criamos confusão.

Aviso N4

V793 É estranho que o resultado da expressão 'imageDirection / 8' seja parte da condição. Talvez essa expressão devesse ter sido comparada com algo diferente. libopenrct2 ObservationTower.cpp 38

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

Vamos explorar mais detalhadamente. A expressão imageDirection / 8 será false se imageDirection estiver no intervalo de -7 a 7. A segunda parte: (imageDirection / 8) != 3 verifica se está fora do intervalo: de -31 a -24 e de 24 a 31, respectivamente. Acho bastante estranho verificar números para ver se estão dentro de um certo intervalo dessa maneira e, mesmo que não haja um erro nesse fragmento de código, eu recomendaria reescrever essas condições de forma mais clara. Isso tornaria a vida muito mais fácil para as pessoas que irão ler e manter esse código. imageDirection Aviso N5

V587

Uma sequência estranha de atribuições desse tipo: A = B; B = A;. Verifique as linhas: 1115, 1118. libopenrct2ui MouseInput.cpp 1118 Uma sequência estranha 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;                                 // <=
      // Evento de janela WE_UNKNOWN_0E foi chamado aqui,
      // mas nenhuma janela realmente implementou um manipulador e
      // não se sabe qual era a finalidade
      cursorId = edi;                                 // <=
      if ((ebx & 0xFF) != 0)
      {
        set_cursor(cursorId);
        return;
      }
      break;
      ....
  }
  ....
}

Este fragmento de código foi provavelmente obtido por meio de decompilação. Assim, de acordo com o comentário deixado, parte do código não funcional foi removida. No entanto, algumas operações sobre cursorId, que também não têm um significado claro.

Aviso N6

V1004 [CWE-476] O ponteiro 'player' foi usado de forma insegura após ser verificado contra nullptr. Verifique as linhas: 2085, 2094. libopenrct2 Network.cpp 2094

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

Este código é relativamente fácil de corrigir; é necessário verificar uma terceira vez o player para o ponteiro nulo, ou incluí-lo no corpo do operador condicional. Eu sugeriria a segunda opção:

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Adicione um novo jogador.
    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::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    ....
  }
  else
  {
    ....
    entry.name = (name == nullptr ? "" : json_string_value(name));
    ....
  }
  ....
}

É possível se livrar de uma linha de código de difícil leitura e resolver o problema da verificação de nullptr. Proponho alterar o código da seguinte forma:

std::optional 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 bastante estranho. Acho que houve um erro de digitação, seja na condição ou na reatribuição da variável ColumnHeaderPressedCurrentState valores falso.

Saída

Como podemos ver, integrar o analisador estático PVS-Studio em seu projeto no TeamCity é bastante simples. Para isso, basta escrever um pequeno arquivo de configuração. A verificação do código permitirá identificar problemas imediatamente após a compilação, o que ajuda a corrigi-los quando a complexidade e o custo das alterações ainda são baixos.

PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2
Se você deseja compartilhar este artigo com um público de língua inglesa, por favor, utilize o link para a tradução: Vladislav Stolyarov. PVS-Studio e Integração Contínua: TeamCity. Análise do projeto Open RollerCoaster Tycoon 2.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster