PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Un dels escenaris més actuals per utilitzar l'analitzador PVS-Studio és la seva integració amb sistemes CI. I encara que l'anàlisi d'un projecte PVS-Studio des de gairebé qualsevol sistema d'integració contínua es pot integrar en només unes quantes ordres, continuem fent que aquest procés sigui encara més còmode. PVS-Studio ara té suport per convertir la sortida de l'analitzador en un format per a TeamCity - TeamCity Inspections Type. Vegem com funciona.

Informació sobre el programari utilitzat

Estudi PVS — un analitzador estàtic de codi C, C++, C# i Java, dissenyat per facilitar la tasca de trobar i corregir diversos tipus d'errors. L'analitzador es pot utilitzar a Windows, Linux i macOS. En aquest article utilitzarem activament no només el propi analitzador, sinó també algunes utilitats de la seva distribució.

CLMonitor — és un servidor de supervisió que supervisa els llançaments del compilador. S'ha d'executar immediatament abans de començar a construir el vostre projecte. En mode snooping, el servidor interceptarà les execucions de tots els compiladors compatibles. Val la pena assenyalar que aquesta utilitat només es pot utilitzar per analitzar projectes C/C++.

PlogConverter – una utilitat per convertir informes de l'analitzador en diferents formats.

Informació sobre el projecte en estudi

Provem aquesta funcionalitat amb un exemple pràctic: analitzem el projecte OpenRCT2.

OpenRCT2 - una implementació oberta del joc RollerCoaster Tycoon 2 (RCT2), ampliant-lo amb noves funcions i solucionant errors. El joc gira al voltant de la construcció i el manteniment d'un parc d'atraccions que conté atraccions, botigues i instal·lacions. El jugador ha d'intentar obtenir beneficis i mantenir la bona reputació del parc alhora que manté els convidats feliços. OpenRCT2 us permet jugar tant en escenari com en sandbox. Els escenaris requereixen que el jugador completi una tasca específica en un temps determinat, mentre que Sandbox permet al jugador construir un parc més flexible sense cap restricció ni finançament.

ajust

Per estalviar temps, probablement em saltaré el procés d'instal·lació i començaré des del moment en què tinc el servidor TeamCity en execució al meu ordinador. Hem d'anar a: localhost:{port especificat durant el procés d'instal·lació} (en el meu cas, localhost:9090) i introduir les dades d'autorització. Després d'entrar ens donaran la benvinguda:

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Feu clic al botó Crea projecte. A continuació, seleccioneu Manualment i ompliu els camps.

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Després de prémer el botó Create, ens rep una finestra amb la configuració.

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Fem clic Crea una configuració de compilació.

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Ompliu els camps i feu clic Create. Veiem una finestra que us demana que seleccioneu un sistema de control de versions. Com que les fonts ja es troben localment, feu clic Omet.

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Finalment, passem a la configuració del projecte.

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Afegim passos de muntatge, per fer-ho feu clic: Passos de creació -> Afegeix un pas de construcció.

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Aquí escollim:

  • Tipus de corredor -> Línia d'ordres
  • Executar -> Script personalitzat

Com que realitzarem anàlisis durant la compilació del projecte, el muntatge i l'anàlisi haurien de ser un pas, així que ompliu el camp Escriptura personalitzada:

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Més endavant veurem els passos individuals. És important que carregar l'analitzador, muntar el projecte, analitzar-lo, emetre l'informe i formatar-lo només requereix onze línies de codi.

L'últim que hem de fer és configurar les variables d'entorn, que he descrit algunes maneres de millorar-ne la llegibilitat. Per fer-ho, seguim: Paràmetres -> Afegeix un paràmetre nou i afegeix tres variables:

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Tot el que has de fer és prémer el botó Correr a la cantonada superior dreta. Mentre s'està muntant i analitzant el projecte, us explicaré el guió.

Guió directament

Primer, hem de descarregar la darrera distribució de PVS-Studio. Per a això fem servir el gestor de paquets Chocolatey. Per a aquells que vulguin saber més sobre això, hi ha un corresponent article:

choco install pvs-studio -y

A continuació, iniciem la utilitat de seguiment de creació del projecte CLMonitor.

%CLmon% monitor –-attach

A continuació, construirem el projecte com a variable d'entorn MSB és el camí cap a la versió de MSBuild que necessito construir

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

Introduïm l'inici de sessió i la clau de llicència per a PVS-Studio:

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

Un cop finalitzada la compilació, torneu a executar CLMonitor per generar fitxers preprocessats i anàlisi estàtica:

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

Aleshores farem servir una altra utilitat de la nostra distribució. PlogConverter converteix un informe d'un format estàndard a un format específic de TeamCity. Gràcies a això, el podrem veure directament a la finestra de compilació.

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

L'últim pas és mostrar l'informe amb format stdout, on serà recollit per l'analitzador de TeamCity.

type "C:tempptest.plog_TeamCity.txt"

Codi de script complet:

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"

Mentrestant, el muntatge i anàlisi del projecte s'ha acabat amb èxit, podem anar a la pestanya Projectes i assegureu-vos-ho.

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Ara fem clic a sobre Total d'inspeccionsper anar a veure l'informe de l'analitzador:

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Els avisos s'agrupen per números de regles de diagnòstic. Per navegar pel codi, heu de fer clic al número de línia amb l'avís. Si feu clic al signe d'interrogació de la cantonada superior dreta, s'obrirà una nova pestanya amb documentació. També podeu navegar pel codi fent clic al número de línia amb l'avís de l'analitzador. La navegació des d'un ordinador remot és possible quan s'utilitza SourceTreeRoot marcador. Qualsevol persona interessada en aquest mode de funcionament de l'analitzador pot familiaritzar-se amb la secció corresponent documentació.

Visualització dels resultats de l'analitzador

Ara que hem acabat de desplegar i configurar la compilació, donem una ullada a alguns avisos interessants que es troben al projecte que estem mirant.

Avís N1

V773 [CWE-401] L'excepció es va llançar sense alliberar el punter de "resultat". És possible una fuga de memòria. 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;
}

L'analitzador va notar un error que després d'assignar memòria dinàmicament Crea un objecte, quan es produeix una excepció, la memòria no s'esborra i es produeix una fuga de memòria.

Avís N2

V501 Hi ha subexpressions idèntiques '(1ULL << WIDX_MONTH_BOX)' a l'esquerra i a la dreta del '|' 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),
  ....
};

Poques persones que no siguin un analitzador estàtic podrien passar aquesta prova d'atenció. Aquest exemple de copiar i enganxar és bo precisament per aquest motiu.

Advertències N3

V703 És estrany que el camp "flags" de la classe derivada "RCT12BannerElement" sobreescriu el camp de la classe base "RCT12TileElementBase". Línies de control: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

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

Per descomptat, utilitzar una variable amb el mateix nom a la classe base i a la descendent no sempre és un error. Tanmateix, la pròpia tecnologia d'herència suposa que tots els camps de la classe pare estan presents a la classe secundària. En declarar camps amb el mateix nom a l'hereu, introduïm confusió.

Avís N4

V793 És estrany que el resultat de la instrucció "imageDirection / 8" sigui una part de la condició. Potser s'hauria d'haver comparat aquesta afirmació amb una altra cosa. libopenrct2 ObservationTower.cpp 38

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

Fem una ullada més de prop. Expressió Direcció de la imatge/8 serà fals si Direcció de la imatge està en el rang de -7 a 7. Segona part: (direcció de la imatge / 8) != 3 xecs Direcció de la imatge per estar fora del rang: de -31 a -24 i de 24 a 31, respectivament. Em sembla força estrany comprovar d'aquesta manera els números per incloure'ls en un rang determinat i, fins i tot si no hi ha cap error en aquest fragment de codi, recomanaria reescriure aquestes condicions per ser més explícits. Això facilitaria molt la vida a les persones que llegirien i mantindrien aquest codi.

Avís N5

V587 Una seqüència estranya d'assignacions d'aquest tipus: A = B; B = A;. Comproveu les línies: 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;
      ....
  }
  ....
}

Aquest fragment de codi probablement es va obtenir per descompilació. Aleshores, a jutjar pel comentari deixat, es va eliminar part del codi que no funcionava. Tanmateix, encara queden un parell d'operacions cursorId, que tampoc tenen gaire sentit.

Avís N6

V1004 [CWE-476] El punter "jugador" es va utilitzar de manera insegura després de verificar-lo contra nullptr. Comproveu les línies: 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);                    // <=
  }
  ....
}

Aquest codi és bastant fàcil de corregir; només cal que el reviseu una tercera vegada jugador a un punter nul o afegiu-lo al cos de la instrucció condicional. Jo proposaria la segona opció:

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);
    }
  }
  ....
}

Avís N7

V547 [CWE-570] L'expressió 'nom == nullptr' sempre és 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));
    ....
  }
  ....
}

Podeu desfer-vos d'una línia de codi difícil de llegir d'un sol cop i resoldre el problema amb la comprovació de nullptr. Suggereixo canviar el codi de la següent manera:

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);
    ....
  }
  ....
}

Avís N8

V1048 [CWE-1164] La variable 'ColumnHeaderPressedCurrentState' s'ha assignat el mateix valor. libopenrct2ui CustomListView.cpp 510

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

El codi sembla força estrany. Em sembla que hi va haver un error ortogràfic, ja sigui en la condició o en tornar a assignar la variable ColumnHeaderPressedCurrentState valors false.

Sortida

Com podem veure, integrar l'analitzador estàtic PVS-Studio al vostre projecte TeamCity és bastant senzill. Per fer-ho, n'hi ha prou amb escriure un petit fitxer de configuració. Comprovar el codi us permetrà identificar els problemes immediatament després del muntatge, cosa que ajudarà a eliminar-los quan la complexitat i el cost dels canvis encara siguin baixos.

PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2
Si voleu compartir aquest article amb un públic de parla anglesa, utilitzeu l'enllaç de traducció: Vladislav Stolyarov. PVS-Studio i Integració Contínua: TeamCity. Anàlisi del projecte Open RollerCoaster Tycoon 2.

Font: www.habr.com

Afegeix comentari