PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
A PVS-Studio analizátor használatának egyik legfrissebb forgatókönyve a CI rendszerekkel való integrációja. És bár egy PVS-Studio projekt elemzése szinte bármilyen folyamatos integrációs rendszerből néhány parancsba beépíthető, továbbra is ezt a folyamatot még kényelmesebbé tesszük. A PVS-Studio mostantól támogatja az analizátor kimenetének TeamCity - TeamCity Inspections Type formátumba való átalakítását. Lássuk, hogyan működik.

Információ a használt szoftverről

PVS-Stúdió — C, C++, C# és Java kód statikus elemzője, amelyet a különféle típusú hibák megtalálásának és kijavításának megkönnyítésére terveztek. Az elemző Windows, Linux és macOS rendszeren használható. Ebben a cikkben nemcsak magát az analizátort, hanem annak elosztásából származó segédprogramokat is aktívan használjuk.

CLMonitor — egy megfigyelő szerver, amely figyeli a fordító elindítását. Közvetlenül a projekt felépítésének megkezdése előtt kell futtatni. Snooping módban a szerver elfogja az összes támogatott fordító futtatását. Érdemes megjegyezni, hogy ez a segédprogram csak C/C++ projektek elemzésére használható.

PlogConverter – segédprogram az analizátor jelentések különböző formátumokba konvertálására.

Információk a vizsgált projektről

Próbáljuk ki ezt a funkciót egy gyakorlati példán – elemezzük az OpenRCT2 projektet.

OpenRCT2 - a RollerCoaster Tycoon 2 (RCT2) játék nyílt megvalósítása, új funkciókkal bővítve és hibák kijavításával. A játékmenet egy vidámpark építése és karbantartása körül forog, ahol lovaglók, üzletek és létesítmények találhatók. A játékosnak meg kell próbálnia profitot termelni és megőrizni a park jó hírnevét, miközben a vendégek elégedettek maradnak. Az OpenRCT2 lehetővé teszi a játékot forgatókönyvben és homokozóban is. A forgatókönyvek megkövetelik, hogy a játékos egy adott feladatot teljesítsen egy meghatározott időn belül, míg a Sandbox lehetővé teszi a játékos számára, hogy rugalmasabb parkot építsen minden korlátozás és pénzügy nélkül.

beállítás

Az időmegtakarítás érdekében valószínűleg kihagyom a telepítési folyamatot, és attól a pillanattól kezdem, amikor a TeamCity szerver fut a számítógépemen. El kell mennünk a következő helyre: localhost:{telepítési folyamat során megadott port} (esetemben localhost:9090), és meg kell adnunk az engedélyezési adatokat. Belépés után fogad minket:

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
Kattintson a Projekt létrehozása gombra. Ezután válassza a Manuálisan lehetőséget, és töltse ki a mezőket.

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
A gomb megnyomása után Teremt, beállításokat tartalmazó ablak fogad minket.

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
Kattintsunk Építési konfiguráció létrehozása.

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
Töltse ki a mezőket, és kattintson a gombra Teremt. Megjelenik egy ablak, amely arra kéri, hogy válasszon verziókezelő rendszert. Mivel a források már helyileg találhatók, kattintson a gombra Tovább.

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
Végül áttérünk a projektbeállításokra.

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
Adjuk hozzá az összeállítási lépéseket, ehhez kattintson: Építési lépések -> Építési lépés hozzáadása.

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
Itt választunk:

  • Futó típusa -> Parancssor
  • Futtatás -> Egyéni szkript

Mivel a projekt összeállítása során elemzést végzünk, ezért az összeállítás és az elemzés egy lépésből áll, ezért töltse ki a mezőt Egyéni parancsfájl:

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
A későbbiekben megvizsgáljuk az egyes lépéseket. Fontos, hogy az analizátor betöltése, a projekt összeállítása, elemzése, a jelentés kiadása és formázása mindössze tizenegy sornyi kódot vesz igénybe.

Az utolsó dolog, amit meg kell tennünk, az a környezeti változók beállítása, amelyekben felvázoltam néhány módszert az olvashatóság javítására. Ehhez lépjünk tovább: Paraméterek -> Új paraméter hozzáadása és adjunk hozzá három változót:

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
Nincs más dolgod, mint megnyomni a gombot futás a jobb felső sarokban. Amíg a projekt összeállítása és elemzése folyik, mesélek a forgatókönyvről.

Közvetlenül forgatókönyv

Először is le kell töltenünk a legújabb PVS-Studio disztribúciót. Ehhez a Chocolatey csomagkezelőt használjuk. Azok számára, akik többet szeretnének tudni erről, van egy megfelelő cikk:

choco install pvs-studio -y

Ezután indítsuk el a CLMonitor projekt build-követő segédprogramját.

%CLmon% monitor –-attach

Ezután a projektet környezeti változóként építjük fel MSB az az elérési út az MSBuild verziójához, amelyet meg kell építeni

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

Adjuk meg a PVS-Studio bejelentkezési és licenckulcsát:

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

A felépítés befejezése után futtassa újra a CLMonitort az előfeldolgozott fájlok és a statikus elemzés létrehozásához:

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

Ezután egy másik segédprogramot fogunk használni a disztribúciónkból. A PlogConverter a jelentést szabványos formátumból TeamCity-specifikus formátumba konvertálja. Ennek köszönhetően közvetlenül a build ablakban tudjuk majd megtekinteni.

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

Az utolsó lépés a formázott jelentés megjelenítése stdout, ahol azt a TeamCity elemző fogja felvenni.

type "C:tempptest.plog_TeamCity.txt"

Teljes szkript kód:

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"

Időközben a projekt összeállítása, elemzése sikeresen befejeződött, mehetünk a fülre projektek és győződjön meg róla.

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
Most pedig kattintsunk Ellenőrzések Összesenaz analizátor jelentésének megtekintéséhez:

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
A figyelmeztetések diagnosztikai szabályszámok szerint vannak csoportosítva. A kódban való navigáláshoz a figyelmeztetést tartalmazó sorszámra kell kattintani. A jobb felső sarokban lévő kérdőjelre kattintva egy új lap nyílik meg a dokumentációval. A kódban úgy is navigálhat, hogy rákattint az elemző figyelmeztető sorszámára. Használat közben lehetséges a navigáció távoli számítógépről SourceTreeRoot jelző. Bárki, akit érdekel az analizátor ezen üzemmódja, megismerkedhet a megfelelő fejezettel dokumentáció.

Az analizátor eredményeinek megtekintése

Most, hogy befejeztük a build telepítését és konfigurálását, vessünk egy pillantást néhány érdekes figyelmeztetésre, amelyek az éppen vizsgált projektben találhatók.

Figyelmeztetés N1

V773 [CWE-401] A kivétel az „eredmény” mutató elengedése nélkül történt. Memóriaszivárgás lehetséges. libopenct2 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;
}

Az analizátor hibát észlelt a memória dinamikus lefoglalása után CreateObject, amikor kivétel történik, a memória nem törlődik, és memóriaszivárgás lép fel.

Figyelmeztetés N2

V501 A „|” bal és jobb oldalán azonos részkifejezések találhatók: '(1ULL << WIDX_MONTH_BOX)' operátor. libopenct2ui Csalások.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),
  ....
};

A statikus analizátoron kívül kevés ember tudott átmenni ezen a figyelmességi teszten. Ez a másolás-beillesztés példa éppen ezért jó.

Figyelmeztetések N3

V703 Furcsa, hogy az 'RCT12BannerElement' származtatott osztály 'flags' mezője felülírja az 'RCT12TileElementBase' alaposztály mezőjét. Ellenőrző sorok: RCT12.h:570, RCT12.h:259. libopenct2 RCT12.h 570

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

Természetesen nem mindig hiba, ha az alaposztályban és a leszármazottban azonos nevű változót használunk. Maga az öröklődési technológia azonban azt feltételezi, hogy a szülőosztály összes mezője jelen van a gyermekosztályban. Azzal, hogy az örökösben azonos nevű mezőket deklarálunk, zavart okozunk.

Figyelmeztetés N4

V793 Furcsa, hogy az „imageDirection / 8” utasítás eredménye a feltétel része. Talán ezt a kijelentést valami mással kellett volna összehasonlítani. libopenct2 ObservationTower.cpp 38

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

Nézzük meg közelebbről. Kifejezés imageDirection/8 hamis lesz, ha imageDirection -7 és 7 közötti tartományban van. Második rész: (imageDirection / 8) != 3 csekkeket imageDirection a tartományon kívülre: -31 és -24, illetve 24 és 31 között. Számomra elég furcsának tűnik, hogy ilyen módon ellenőrzöm a számokat egy bizonyos tartományban, és még ha nincs is hiba ebben a kódrészletben, azt javaslom, hogy írd át ezeket a feltételeket, hogy egyértelműbbé váljanak. Ez sokkal könnyebbé tenné azoknak az embereknek az életét, akik olvassák és karbantartják ezt a kódot.

Figyelmeztetés N5

V587 Az ilyen típusú hozzárendelések páratlan sorozata: A = B; B = A;. Ellenőrző sorok: 1115, 1118. libopenct2ui 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;
      ....
  }
  ....
}

Ezt a kódrészletet nagy valószínűséggel dekompilációval kaptuk. Aztán a hagyott megjegyzésből ítélve a nem működő kód egy részét eltávolították. Azonban még hátra van néhány műtét cursorId, aminek szintén nincs sok értelme.

Figyelmeztetés N6

V1004 [CWE-476] A 'lejátszó' mutatót nem biztonságosan használták, miután ellenőrizték a nullptr ellen. Ellenőrző sorok: 2085, 2094. libopenct2 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);                    // <=
  }
  ....
}

Ez a kód meglehetősen könnyen javítható, csak harmadszor kell ellenőriznie játékos egy null mutatóhoz, vagy adja hozzá a feltételes utasítás törzséhez. Én a második lehetőséget javaslom:

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

Figyelmeztetés N7

V547 [CWE-570] A 'name == nullptr' kifejezés mindig hamis. libopenct2 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));
    ....
  }
  ....
}

Egy csapásra megszabadulhat egy nehezen olvasható kódsortól, és megoldhatja a problémát a nullptr. Javaslom a kód megváltoztatását az alábbiak szerint:

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

Figyelmeztetés N8

V1048 [CWE-1164] A 'ColumnHeaderPressedCurrentState' változó ugyanazt az értéket kapta. libopenct2ui CustomListView.cpp 510

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

A kód elég furcsán néz ki. Számomra úgy tűnik, hogy vagy a feltételben, vagy a változó újbóli hozzárendelésekor volt elírás ColumnHeaderPressedCurrentState jelentését hamis.

Teljesítmény

Amint látjuk, a PVS-Studio statikus analizátor integrálása a TeamCity projektbe meglehetősen egyszerű. Ehhez elég csak egy kis konfigurációs fájlt írni. A kód ellenőrzése lehetővé teszi a problémák azonnali azonosítását az összeszerelés után, ami segít kiküszöbölni azokat, amikor a változtatások bonyolultsága és költsége még mindig alacsony.

PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése
Ha meg szeretné osztani ezt a cikket egy angolul beszélő közönséggel, használja a fordítási linket: Vladislav Stolyarov. PVS-Studio és folyamatos integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.

Forrás: will.com

Hozzászólás