PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Jedan od najvažnijih scenarija korištenja analize PVS-Studio je njegova integracija s CI sustavima. Iako se analiza projekta PVS-Studio praktički može ugraditi u bilo koji sustav kontinuirane integracije s nekoliko naredbi, nastavljamo ovaj proces činiti još prikladnijim. U PVS-Studio je dodana podrška za pretvaranje izlaza analize u format za TeamCity — TeamCity Inspections Type. Pogledajmo kako to funkcionira.

Informacije o korištenom softveru

PVS-Studio — statički analizator C, C++, C# i Java koda, namijenjen olakšavanju zadatka pronalaženja i ispravljanja raznih vrsta grešaka. Analizator se može koristiti na Windows, Linux i macOS. U ovom ćemo članku aktivno koristiti ne samo sam analizator, već i neke alate iz njegovog distribucijskog paketa.

CLMonitor — predstavlja server za nadzor, koji prati pokretanje kompajlera. Potrebno ga je pokrenuti neposredno prije početka gradnje vašeg projekta. U načinu praćenja server će presresti pokretanja svih podržanih kompajlera. Valja napomenuti da se ovaj alat može koristiti samo za analizu C/C++ projekata.

PlogConverter – alat za konverziju izvještaja analize u različite formate.

Informacije o istraživanom projektu

Pogledajmo ovu funkcionalnost na praktičnom primjeru – analizirat ćemo projekt OpenRCT2.

OpenRCT2 — otvorena implementacija igre RollerCoaster Tycoon 2 (RCT2), koja proširuje igru novim funkcijama i ispravlja greške. Igranje se vrti oko izgradnje i održavanja zabavnog parka u kojem se nalaze atrakcije, trgovine i objekti. Igrač treba nastojati ostvariti profit i održavati dobru reputaciju parka, istovremeno držeći posjetitelje sretnima. OpenRCT2 omogućuje igranje u scenarijima i u sandbox modu. Scenariji zahtijevaju da igrač ispuni određeni zadatak u zadanom vremenu, dok sandbox omogućuje igraču da gradi fleksibilniji park bez ikakvih ograničenja ili financijskih restrikcija.

Podešavanje

Kako bismo uštedjeli vrijeme, preskočit ću proces instalacije i započeti od trenutka kada mi je na računalu pokrenut TeamCity server. Moramo pristupiti: localhost:{port naveden u procesu instalacije}(u mom slučaju, localhost:9090) i unijeti podatke za prijavu. Nakon prijave dočekat će nas:

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Kliknimo na gumb Create Project. Zatim odabiremo Manually i ispunjavamo polja.

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Nakon što pritisnemo gumb Create, susreće nas prozor s postavkama.

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Kliknimo Create build configuration.

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Ispunjavamo polja, pritisnemo Create. Vidimo prozor s prijedlogom za izbor sustava kontrole verzija. Budući da izvorni kodovi već leže lokalno, kliknemo Skip.

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Na kraju, prelazimo na postavke projekta.

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Dodajmo korake izgradnje, za to kliknemo: Build steps -> Add build step.

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Ovdje odabiremo:

  • Runner type -> Command Line
  • Run -> Custom Script

Budući da ćemo provoditi analizu tijekom kompaktiranja projekta, izgradnja i analiza trebaju biti jedan korak, stoga ispunjavamo polje Custom Script:

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Na posebnim koracima ćemo se zadržati kasnije. Važno je da učitavanje analizatora, izgradnja projekta, njegova analiza, ispis izvještaja i njegovo formatiranje traje ukupno jedanaest redaka koda.

Posljednje što trebamo učiniti je postaviti varijable okruženja, kojima sam označio neke puteve radi poboljšanja čitljivosti. Za to prelazimo: Parameters -> Add new parameter i dodajmo tri varijable:

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Preostaje pritisnuti gumb Run u gornjem desnom kutu. Dok traju izgradnja i analiza projekta, ispričat ću vam o skripti.

Skripta

Za početak, moramo preuzeti svježi distribucijski paket PVS-Studio. Za to koristimo paket menadžer Chocoaltey. Za one koji žele saznati više, postoji odgovarajući članak:

choco install pvs-studio -y

Zatim ćemo pokrenuti alat za praćenje izgradnje projekta CLMonitor.

%CLmon% monitor --attach

Nakon toga izvršit ćemo izgradnju projekta, kao varijabla okruženja MSB je put do verzije MSBuild koja mi je potrebna za izgradnju

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

Unijet ćemo korisničko ime i ključ licence PVS-Studio:

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

Nakon završetka izgradnje ponovno ćemo pokrenuti CLMonitor za generiranje prethodno obrađenih datoteka i statičku analizu:

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

Nakon toga koristit ćemo još jedan alat iz našeg distribucijskog paketa. PlogConverter pretvara izvještaj iz standardnog u specifičan za TeamCity format. Zbog toga ćemo ga moći pregledati izravno u prozoru za gradnju.

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

Posljednjim korakom ispisujemo formatirani izvještaj u stdout, gdje ga preuzima TeamCity parser.

type "C:tempptest.plog_TeamCity.txt"

Cijeli kod skripte:

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"

U međuvremenu, gradnja i analiza projekta su uspješno završeni, možemo prijeći na karticu Projects i uvjeriti se u to.

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Sada kliknemo na Inspections Total, kako bismo prešli na pregled izvještaja analize:

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Upozorenja su grupirana prema brojevima dijagnostičkih pravila. Za navigaciju kroz kod potrebno je kliknuti na broj linije s upozorenjem. Klikom na znak pitanja u gornjem desnom kutu otvorit će se nova kartica s dokumentacijom. Također možete navigirati kroz kod klikom na broj linije s upozorenjem analize. Navigacija s udaljenog računala moguća je uz primjenu SourceTreeRoot marker. Oni kojima je zanimljiv ovaj režim rada analize mogu se upoznati s relevantnim odjeljkom dokumentaciju.

Pregled rezultata rada analize

Nakon što smo završili s postavljanjem i konfiguracijom gradnje, predlažem da pogledamo neka zanimljiva upozorenja otkrivena u analiziranom projektu.

Upozorenje N1

V773 [CWE-401] Izuzetak je izbačen bez oslobađanja pokazivača 'result'. Moguća je curenje memorije. 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;
}

Analizator je primijetio grešku koja se sastoji u tome da nakon dinamičkog dodjeljivanja memorije u CreateObject, pri pojavi izuzetka memorija se ne pročišćava, što dovodi do curenja memorije.

Upozorenje N2

V501 S lijeve i desne strane operatora '|' postoje identične podizraze '(1ULL << WIDX_MONTH_BOX)'. 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),
  ....
};

Malo tko osim statičkog analizatora mogao bi proći ovaj test pažnje. Ovaj primjer kopiranja i lijepljenja dobar je upravo zbog toga.

Upozorenja N3

Čudno je što 'flags' polje u izvedenoj klasi 'RCT12BannerElement' prepisuje polje u osnovnoj klasi 'RCT12TileElementBase'. Provjerite redak: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570 struct RCT12SpriteBase { .... uint8_t flags; .... }; struct rct1_peep : RCT12SpriteBase { .... uint8_t flags; .... };

Naravno, korištenje varijable s istim imenom u osnovnoj klasi i u izvedenoj klasi nije uvijek greška. Međutim, tehnologija nasljeđivanja sama po sebi podrazumijeva prisutnost svih polja roditeljske klase u potomku. Ako u izvedenoj klasi izjavimo polja s istim imenom, unosimo konfuziju.

Upozorenje N4

V793

Čudno je da je rezultat izraza 'imageDirection / 8' dio uvjeta. Možda bi ovaj izraz trebao biti uspoređen s nečim drugim. libopenrct2 ObservationTower.cpp 38 void vehicle_visual_observation_tower(...., int32_t imageDirection, ....) { if ((imageDirection / 8) && (imageDirection / 8) != 3) { .... } .... }

Pogledajmo malo detaljnije. Izraz

imageDirection / 8 bit će false u slučaju kada imageDirection nalazi se u rasponu od -7 do 7. Drugi dio: (imageDirection / 8) != 3 provjerava nalazi li se izvan raspona: od -31 do -24 i od 24 do 31. Čini mi se prilično čudnim provjeravati brojeve za pripadnost određenom rasponu na ovakav način i, čak i ako u ovom fragmentu koda nema greške, preporučio bih da se ova stanja prepisuju na jasnije. To bi znatno olakšalo život ljudima koji će čitati i održavati ovaj kod. nalazi se u rasponu od -7 do 7. Drugi dio: Upozorenje N5

V587

Neobična sekvenca dodjela ovakvog tipa: A = B; B = A;. Provjerite retke: 1115, 1118. libopenrct2ui MouseInput.cpp 1118 Neobična sekvenca dodela ovog tipa: A = B; B = A;. Proverite linije: 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;
      ....
  }
  ....
}

Ovaj fragment koda najvjerojatnije je dobiven dekompilacijom. Prema ostavljenom komentaru, dio neispravnog koda je uklonjen. Međutim, ostala su dva operacija nad cursorId, koji također nemaju posebno značenje.

Upozorenje N6

V1004 [CWE-476] Pokazivač 'player' korišten je nesigurno nakon što je verificiran s nullptr. Provjerite redove: 2085, 2094. libopenrct2 Network.cpp 2094

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

Ovaj kod se prilično lako može popraviti, potrebno je ili treći put provjeriti player na null pokazivač, ili ga uključiti u uvjetnu strukturu. Predlažem drugu opciju:

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

Upozorenje N7

V547 [CWE-570] Izraz 'name == nullptr' je uvijek lažan. 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));
    ....
  }
  ....
}

Jednim potezom se može riješiti problem sa teškom i čitljivom linijom koda i provjerom na nullptr. Predlažem da promijenite kod na sljedeći način:

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

Upozorenje N8

V1048 [CWE-1164] Varijabla 'ColumnHeaderPressedCurrentState' dodijeljena je istom vrijednosti. libopenrct2ui CustomListView.cpp 510

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

Kod izgleda prilično čudno. Mislim da se dogodila greška ili u uvjetu ili kod ponovnog dodjeljivanja varijable ColumnHeaderPressedCurrentState vrijednosti false.

Izvještaj

Kao što vidimo, integracija statičkog analizatora PVS-Studio u vaš projekt na TeamCityju je prilično jednostavna. Sve što trebate učiniti je napisati jedan mali konfiguracijski datoteka. Provjera koda omogućava otkrivanje problema odmah nakon izgradnje, što pomaže u njihovom rješavanju dok su složenost i troškovi ispravki još uvijek mali.

PVS-Studio i Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Ako želite podijeliti ovaj članak s anglofonom publikom, koristite link na prijevod: Vladislav Stolyarov. PVS-Studio i Kontinuirana Integracija: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2.

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster