PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Un dos escenarios máis actuais para o uso do analizador PVS-Studio é a súa integración con sistemas CI. E aínda que a análise dun proxecto de PVS-Studio desde case calquera sistema de integración continua pode integrarse en só algúns comandos, seguimos facendo este proceso aínda máis cómodo. PVS-Studio agora ten soporte para converter a saída do analizador nun formato para TeamCity - TeamCity Inspections Type. A ver como funciona.

Información sobre o software utilizado

PVS-Estudio — un analizador estático de código C, C++, C# e Java, deseñado para facilitar a tarefa de atopar e corrixir varios tipos de erros. O analizador pódese usar en Windows, Linux e macOS. Neste artigo utilizaremos activamente non só o propio analizador, senón tamén algunhas utilidades da súa distribución.

CLMonitor — é un servidor de monitorización que supervisa os lanzamentos do compilador. Debe executarse inmediatamente antes de comezar a construír o seu proxecto. No modo snooping, o servidor interceptará as execucións de todos os compiladores compatibles. Paga a pena sinalar que esta utilidade só se pode usar para analizar proxectos C/C++.

PlogConverter – unha utilidade para converter informes do analizador en diferentes formatos.

Información sobre o proxecto en estudo

Probemos esta funcionalidade nun exemplo práctico: analicemos o proxecto OpenRCT2.

OpenRCT2 - unha implementación aberta do xogo RollerCoaster Tycoon 2 (RCT2), ampliándoo con novas funcións e corrixindo erros. O xogo xira en torno á construción e mantemento dun parque de atraccións que conteña atraccións, tendas e instalacións. O xogador debe tentar obter beneficios e manter a boa reputación do parque mentres mantén contentos aos hóspedes. OpenRCT2 permítelle xogar tanto en escenario como en sandbox. Os escenarios requiren que o xogador complete unha tarefa específica nun tempo determinado, mentres que Sandbox permítelle construír un parque máis flexible sen restricións nin finanzas.

axuste

Para aforrar tempo, probablemente saltarei o proceso de instalación e comezarei desde o momento en que teña o servidor TeamCity en execución no meu ordenador. Necesitamos ir a: localhost:{porto especificado durante o proceso de instalación} (no meu caso, localhost:9090) e introducir os datos de autorización. Despois de entrar nos recibirán:

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Fai clic no botón Crear proxecto. A continuación, seleccione Manualmente e enche os campos.

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Despois de premer o botón crear, recíbenos unha fiestra con configuración.

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Prememos Crear configuración de compilación.

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Completa os campos e fai clic crear. Vemos unha fiestra na que se lle pide que seleccione un sistema de control de versións. Dado que as fontes xa están localizadas localmente, fai clic Saltar.

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Finalmente, pasamos á configuración do proxecto.

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Engademos pasos de montaxe, para facelo fai clic: Pasos de compilación -> Engadir paso de compilación.

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Aquí escollemos:

  • Tipo de corredor -> Liña de comandos
  • Executar -> Script personalizado

Xa que realizaremos análises durante a compilación do proxecto, a montaxe e a análise deberían ser un paso, así que enche o campo Script personalizado:

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Veremos os pasos individuais máis adiante. É importante que cargar o analizador, montar o proxecto, analizalo, emitir o informe e formatear só leva once liñas de código.

O último que temos que facer é establecer as variables de ambiente, que delineei algunhas formas de mellorar a súa lexibilidade. Para facelo, imos adiante: Parámetros -> Engadir un novo parámetro e engade tres variables:

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Todo o que tes que facer é premer o botón Correr na esquina superior dereita. Mentres se está a montar e analizar o proxecto, falareivos do guión.

Directamente guión

En primeiro lugar, necesitamos descargar a última distribución de PVS-Studio. Para iso usamos o xestor de paquetes Chocolatey. Para os que queiran saber máis sobre isto, hai un correspondente artigo:

choco install pvs-studio -y

A continuación, imos lanzar a utilidade de seguimento de construción do proxecto CLMonitor.

%CLmon% monitor –-attach

Despois construímos o proxecto como unha variable de ambiente MSB é o camiño para a versión de MSBuild que necesito construír

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

Introduzamos o inicio de sesión e a clave de licenza para PVS-Studio:

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

Despois de completar a compilación, executa CLMonitor de novo para xerar ficheiros preprocesados ​​e análise estática:

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

Despois usaremos outra utilidade da nosa distribución. PlogConverter converte un informe dun formato estándar a un formato específico de TeamCity. Grazas a isto, poderemos velo directamente na xanela de compilación.

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

O último paso é mostrar o informe con formato stdout, onde será recollido polo analizador de TeamCity.

type "C:tempptest.plog_TeamCity.txt"

Código de guión 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:tempptest.plog_TeamCity.txt"

Mentres tanto, a montaxe e análise do proxecto completouse con éxito, podemos ir á pestana proxectos e asegúrate diso.

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Agora imos premer Total de inspecciónspara ir a ver o informe do analizador:

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Os avisos agrúpanse por números de regras de diagnóstico. Para navegar polo código, cómpre facer clic no número de liña coa advertencia. Facendo clic no signo de interrogación da esquina superior dereita abrirase unha nova pestana coa documentación. Tamén pode navegar polo código facendo clic no número de liña coa advertencia do analizador. A navegación desde un ordenador remoto é posible cando se usa SourceTreeRoot marcador. Calquera persoa que estea interesada neste modo de funcionamento do analizador pode familiarizarse coa sección correspondente documentación.

Visualización dos resultados do analizador

Agora que rematamos de implementar e configurar a compilación, vexamos algunhas advertencias interesantes que se atopan no proxecto que estamos a ver.

Aviso N1

V773 [CWE-401] A excepción lanzouse sen soltar o punteiro de "resultado". É posible unha fuga de memoria. 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 analizador detectou un erro que despois de asignar memoria de forma dinámica CrearObxecto, cando se produce unha excepción, a memoria non se borra e prodúcese unha fuga de memoria.

Aviso N2

V501 Hai subexpresións idénticas '(1ULL << WIDX_MONTH_BOX)' á esquerda e á dereita 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),
  ....
};

Poucas persoas ademais dun analizador estático poderían pasar esta proba de atención. Este exemplo de copiar e pegar é bo precisamente por este motivo.

Avisos N3

V703 É estraño que o campo "bandeiras" da clase derivada "RCT12BannerElement" sobrescriba o campo da clase base "RCT12TileElementBase". Liñas de verificación: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

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

Por suposto, usar unha variable co mesmo nome na clase base e na descendente non sempre é un erro. Non obstante, a propia tecnoloxía de herdanza supón que todos os campos da clase pai están presentes na clase filla. Ao declarar campos co mesmo nome no herdeiro, introducimos confusión.

Aviso N4

V793 É estraño que o resultado da instrución "imageDirection / 8" sexa parte da condición. Quizais, esta afirmación debería ter sido comparada con outra cousa. libopenrct2 ObservationTower.cpp 38

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

Vexamos máis de cerca. Expresión Dirección da imaxe/8 será falso se Dirección da imaxe está no rango de -7 a 7. Segunda parte: (dirección da imaxe / 8) != 3 cheques Dirección da imaxe por estar fóra do rango: de -31 a -24 e de 24 a 31, respectivamente. Paréceme bastante estraño comprobar que se inclúen números nun determinado intervalo deste xeito e, aínda que non haxa ningún erro neste anaco de código, recomendaría reescribir estas condicións para ser máis explícito. Isto facilitaríalle moito a vida ás persoas que lerían e manterían este código.

Aviso N5

V587 Unha secuencia impar de asignacións deste tipo: A = B; B = A;. Liñas de verificación: 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;
      ....
  }
  ....
}

Probablemente este fragmento de código obtivo por descompilación. Despois, a xulgar polo comentario deixado, eliminouse parte do código que non funcionaba. Non obstante, aínda quedan un par de operacións cursorId, que tampouco teñen moito sentido.

Aviso N6

V1004 [CWE-476] O punteiro "xogador" utilizouse de forma insegura despois de que se verificou contra nullptr. Liñas de verificación: 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 sinxelo de corrixir; só tes que revisalo unha terceira vez xogador a un punteiro nulo ou engádeo ao corpo da instrución condicional. Suxeriría a segunda opción:

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 expresión 'nome == 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));
    ....
  }
  ....
}

Podes desfacerte dunha liña de código difícil de ler dun só golpe e resolver o problema coa comprobación de nullptr. Suxiro cambiar o código do seguinte xeito:

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] Asignouse o mesmo valor á variable 'ColumnHeaderPressedCurrentState'. libopenrct2ui CustomListView.cpp 510

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

O código parece bastante estraño. Paréceme que houbo unha errata ben na condición ou ao reasignar a variable ColumnHeaderPressedCurrentState valores teito.

Saída

Como podemos ver, integrar o analizador estático PVS-Studio no teu proxecto TeamCity é bastante sinxelo. Para iso, abonda con escribir só un pequeno ficheiro de configuración. A verificación do código permitirache identificar os problemas inmediatamente despois da montaxe, o que axudará a eliminalos cando a complexidade e o custo dos cambios aínda sexan baixos.

PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2
Se queres compartir este artigo cun público de fala inglesa, utiliza a ligazón de tradución: Vladislav Stolyarov. PVS-Studio e Integración Continua: TeamCity. Análise do proxecto Open RollerCoaster Tycoon 2.

Fonte: www.habr.com

Engadir un comentario