PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
En af de mest relevante anvendelsesscenarier for PVS-Studio-analyserne er deres integration med CI-systemer. Selvom analysen af et projekt med PVS-Studio næsten kan integreres fra enhver continuous integration-system med blot et par kommandoer, arbejder vi fortsat på at gøre denne proces endnu mere bekvem. PVS-Studio har fået støtte til at konvertere analysens output til et format til TeamCity — TeamCity Inspections Type. Lad os se, hvordan det fungerer.

Information om den anvendte software

PVS-Studio — en statisk analyser af C, C++, C# og Java kode, der er designet til at lette opgaven med at finde og rette forskellige typer fejl. Analyset kan bruges på Windows, Linux og macOS. I denne artikel vil vi aktivt bruge ikke kun analysen, men også nogle værktøjer fra dens distribution.

CLMonitor — er en monitor server, der overvåger kørsel af compilere. Den skal startes direkte før begyndelsen af byggeriet af dit projekt. I overvågningsmodus vil serveren opfange kørsel af alle understøttede compilere. Det er værd at bemærke, at dette værktøj kun kan bruges til analyse af C/C++ projekter.

PlogConverter – et værktøj til konvertering af analyserapporten til forskellige formater.

Information om det undersøgte projekt

Lad os prøve denne funktionalitet med et praktisk eksempel – lad os analysere projektet OpenRCT2.

OpenRCT2 — en open source-implementering af spillet RollerCoaster Tycoon 2 (RCT2), der udvider det med nye funktioner og retter fejl. Spiloplevelsen drejer sig om at bygge og drive en forlystelsespark, hvor der findes forlystelser, butikker og bygninger. Spilleren skal forsøge at opnå profit og opretholde et godt omdømme for parken, mens gæsterne holdes glade. OpenRCT2 gør det muligt at spille både i scenarier og i sandkasse. Scenarier kræver, at spilleren udfører en bestemt opgave inden for en given tidsramme, mens sandkassen giver spilleren mulighed for at bygge en mere fleksibel park uden nogen begrænsninger eller økonomiske restriktioner.

Indstillinger

For at spare tid vil jeg springe installationsprocessen over og starte fra det punkt, hvor jeg har serveren TeamCity kørende på min computer. Vi skal gå til: localhost:{den angivne installationsport}(i mit tilfælde, localhost:9090) og indtaste autentificeringsoplysningerne. Efter indlogning vil vi blive mødt af:

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Klik på knappen Opret projekt. Vælg derefter Manuel, og udfyld felterne.

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Efter at have klikket på knappen Opret, møder vi et vindue med indstillinger.

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Let’s press Opret byggespecifikation.

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Udfyld felterne, klik på Opret. Vi ser et vindue med mulighed for valg af versionskontrolsystem. Da kilderne allerede ligger lokalt, klikker vi på Spring over.

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Endelig går vi til projektindstillingerne.

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Lad os tilføje byggetrin, klik på: Byggetrin -> Tilføj byggetrin.

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Her vælger vi:

  • Runner type -> Kommandolinje
  • Kør -> Eget script

Da vi vil udføre analysen under kompileringen af projektet, skal bygning og analyse være ét trin, så vi udfylder feltet Eget script:

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Vi stopper ved separate trin senere. Det er vigtigt, at indlæsningen af analysatoren, bygningen af projektet, dens analyse, rapportudskrift og formatering kun tager elleve linjer kode.

Det sidste, vi skal gøre, er at oprette miljøvariabler, som jeg har angivet nogle stier for at forbedre deres læsbarhed. Gå til: Parametre -> Tilføj ny parameter og tilføje tre variabler:

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Det er muligt at klikke på knappen Kør i højre hjørne. Mens bygningen og analysen af projektet kører, vil jeg fortælle dig om scriptet.

Selve scriptet

For at starte er vi nødt til at downloade den nyeste version af PVS-Studio. For dette bruger vi pakkemanageren Chocolatey. For dem, der ønsker at vide mere om dette, er der en passende artikel:

choco install pvs-studio -y

Dernæst vil vi starte værktøjet til overvågning af projektbygning CLMonitor.

%CLmon% monitor –-attach

Derefter udfører vi byggeriet af projektet, hvor miljøvariablen MSB repræsenterer stien til den version af MSBuild, jeg har brug for til bygningen

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

Indtast login og licensnøgle til PVS-Studio:

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

Efter at bygningen er afsluttet, skal vi igen køre CLMonitor for at generere forbehandlede filer og statisk analyse:

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

Derefter bruger vi et andet værktøj fra vores distribution. PlogConverter konverterer rapporten fra standardformat til TeamCity-format. På denne måde kan vi se den direkte i byggevinduet.

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

Som det sidste skridt vil vi udskrive den formaterede rapport til stdout, hvor TeamCity-parseren vil fange den.

type "C:tempptest.plog_TeamCity.txt"

Den fulde kode af scriptet:

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"

Imens tjekket er bygningen og analysen af projektet afsluttet, kan vi gå videre til fanen Projects og bekræfte dette.

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Lad os nu klikke på Inspections Total, for at gå til rapporten fra analysatoren:

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Advarslerne er grupperet efter nummeret på de diagnostiske regler. For at navigere gennem koden skal du klikke på linjenummeret med advarslen. At klikke på spørgsmålstegnet i øverste højre hjørne åbner en ny fane med dokumentationen. Du kan også navigere i koden ved at trykke på linjenummeret med analysatorens advarsel. Navigation fra en fjerncomputer er mulig ved brug af SourceTreeRoot markøren. Den der er interesseret i denne tilstand for analysatoren, kan læse det relevante afsnit dokumentationen.

Gennemgang af resultaterne fra analysatoren

Når vi er færdige med at deployere og konfigurere bygningen, vil jeg foreslå, at vi ser på nogle interessante advarsler, der er fundet i det undersøgte projekt.

Warning N1

V773 [CWE-401] Undtagelsen blev kastet uden at frigive 'result' pointeren. En hukommelseslækage er mulig. libopenrct2 ObjectFactory.cpp 443

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

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("Ugyldig objekt type");
  }
  return result;
}

Analysatoren bemærkede en fejl, der består i, at efter dynamisk hukommelsesallokering i CreateObject, ved undtagelsen frigives hukommelsen ikke, hvilket resulterer i en hukommelseslækage.

Warning N2

V501 Der er identiske underudtryk '(1ULL << WIDX_MONTH_BOX)' til venstre og til højre for '|' operatoren. 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),
  ....
};

Det er sjældent, at nogen, udover en statisk analyzer, kan bestå denne opmærksomhedstest. Dette eksempel på copy-paste er godt netop af denne grund.

Advarsel N3

V703 Det er mærkeligt, at 'flags'-feltet i den afledte klasse 'RCT12BannerElement' overskriver feltet i basis-klassen 'RCT12TileElementBase'. Tjek linjer: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

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

Selvfølgelig er det ikke altid en fejl at bruge en variabel med samme navn i basis- og afledte klasse. Dog forudsætter arv-teknologi, at alle felter fra den overordnede klasse er til stede i datteren. Ved at erklære felter med samme navn i datteren forårsager vi forvirring.

Advarsel N4

V793 Det er mærkeligt, at resultatet af udsagnet 'imageDirection / 8' er en del af betingelsen. Måske skulle dette udsagn sammenlignes med noget andet. libopenrct2 ObservationTower.cpp 38

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

Lad os gå lidt mere i dybden. Udtrykket imageDirection / 8 vil være false, hvis imageDirection ligger i intervallet fra -7 til 7. Den anden del: (imageDirection / 8) != 3 tjekker imageDirection for at være uden for området: fra -31 til -24 og fra 24 til 31 henholdsvis. Jeg synes, det er ret underligt at tjekke tal for at se om de ligger i et bestemt interval på denne måde, og selvom der ikke er nogen fejl i dette kodefragment, vil jeg anbefale at omskrive disse betingelser til mere eksplicitte. Det ville væsentligt forenkle livet for folk, der skal læse og vedligeholde denne kode.

Advarsel N5

V587 En mærkelig sekvens af tildelinger af denne art: A = B; B = A;. Tjek linjer: 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;
      ....
  }
  ....
}

Denne kodefragment er sandsynligvis blevet erhvervet ved dekonstruktion. Ifølge den efterladte kommentar blev dele af den ikke-fungerende kode fjernet. Dog er der stadig et par operationer over cursorId, som heller ikke bærer nogen særlig mening.

Advarsel N6

V1004 [CWE-476] 'player' pointeren blev brugt usikkert efter at den blev verificeret mod nullptr. Tjek linjer: 2085, 2094. libopenrct2 Network.cpp 2094

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

Denne kode kan rettes ret let, man skal enten tjekke for den tredje gang player for nullpointer, eller inkludere den i kroppen af den betingede operator. Jeg ville foreslå den anden mulighed:

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

Advarsel N7

V547 [CWE-570] Uden for den ellers tilførte betingelse er 'name == nullptr' altid falsk. 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));
    ....
  }
  ....
}

Man kan på én gang slippe af med den svære at læse kodelinje og løse problemet med kontrollen af nullptr. Jeg foreslår at ændre koden som følger:

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

Advarsel N8

V1048 [CWE-1164] Variabelen 'ColumnHeaderPressedCurrentState' blev tildelt den samme værdi. libopenrct2ui CustomListView.cpp 510

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

Koden ser ret mærkelig ud. Jeg tror, at der må være en skrivefejl enten i betingelsen eller ved den gentagne tildeling af variablen ColumnHeaderPressedCurrentState værdier false.

Output

Som vi kan se, er det ret nemt at integrere PVS-Studio statiske analysetools i sit projekt på TeamCity. Det kræver blot, at man skriver én lille konfigurationsfil. Kodekontrollen muliggør at identificere problemer umiddelbart efter bygningen, hvilket hjælper med at løse dem, mens kompleksiteten og omkostningerne ved rettelser stadig er små.

PVS-Studio og Continuous Integration: TeamCity. Analyse af projektet Open RollerCoaster Tycoon 2
Hvis du ønsker at dele denne artikel med et engelsktalende publikum, så bedes du bruge linket til oversættelsen: Vladislav Stolyarov. PVS-Studio og kontinuerlig integration: TeamCity. Analyse af Open RollerCoaster Tycoon 2 projektet.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster