PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
A PVS-Studio elemzőjének egyik legaktívabb felhasználási forgatókönyve a CI rendszerekkel való integrációja. Bár a PVS-Studio projekt elemzését gyakorlatilag bármely folyamatos integrációs rendszerbe néhány parancssorral be lehet építeni, továbbra is azon dolgozunk, hogy ezt a folyamatot még kényelmesebbé tegyük. A PVS-Studio támogatást kapott az elemző kimenetének TeamCity formátumba való átalakításához — TeamCity Inspections Type. Nézzük meg, hogyan működik ez.

A használt szoftverről szóló információk

PVS-Studio — egy statikus elemző C, C++, C# és Java kódhoz, melynek célja, hogy megkönnyítse a különböző hibák keresésének és javításának feladatát. Az elemző használható Windows, Linux és macOS rendszereken. Ebben a cikkben aktívan használni fogjuk nemcsak magát az elemzőt, hanem néhány utility-t is a disztribúciójából.

CLMonitor — egy monitorozó szerver, amely a fordítók indításának nyomon követését végzi. Az Ön projektjének összeállítása előtt közvetlenül el kell indítani. A nyomkövetési módban a szerver minden támogatott fordítónak az indítását elfogja. Érdemes megjegyezni, hogy ezt az eszközt csak C/C++ projektek elemzésére lehet használni.

PlogConverter – egy utility, amely az elemző jelentésének különböző formátumokra való konvertálására szolgál.

A vizsgált projektről szóló információk

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

OpenRCT2 — a RollerCoaster Tycoon 2 (RCT2) játék nyílt forráskódú megvalósítása, amely új funkciókkal bővíti és javítja a hibákat. A játékmenet a vidámpark megépítése és fenntartása körül forog, amelyben attrakciók, boltok és létesítmények találhatók. A játékosnak meg kell próbálnia nyereséget szerezni és fenntartani a park jó hírnevét, miközben a vendégeket boldogan tartja. Az OpenRCT2 lehetővé teszi, hogy a játékos akár forgatókönyvekben, akár szabadon játsszon. A forgatókönyvek megkövetelik, hogy a játékos egy adott feladatot teljesítsen meghatározott időn belül, míg a szabad játék lehetővé teszi a játékos számára, hogy korlátok és költségek nélkül építhessen egy rugalmasabb parkot.

Beállítás

Időmegtakarítás céljából most kihagyom a telepítési folyamatot, és attól a pillanattól kezdem, amikor a TeamCity szerver a számítógépemen fut. Át kell lépnünk: localhost:{a telepítési folyamat során megadott port}(az én esetemben, localhost:9090), és be kell írni a bejelentkezési adatokat. Belépés után a következő vár ránk:

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
Kattintsunk a Create Project gombra. Ezután válasszuk a Manually lehetőséget, töltsük ki a mezőket.

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
A gomb megnyomása után Létrehozás, üdvözöl minket a beállítási ablak.

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
Kattintsunk Hozzunk létre build konfigurációt.

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
Töltsük ki a mezőket, kattintsunk Létrehozás. Látni fogjuk a verziókezelő rendszer választási lehetőségét. Mivel a forrásfájlok már helyben vannak, kattintsunk Skip.

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
Végül áttérünk a projekt beállításaira.

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
Adjuk hozzá a build lépéseket, ehhez kattintsunk: Build steps -> Add build step.

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
Itt válasszuk:

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

Mivel a projekt fordítása során elemzést végzünk, a fordításnak és az elemzésnek egy lépésben kell megtörténnie, ezért töltsük ki a Custom Script:

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
mezőt. Külön lépésekről később fogunk beszélni. Fontos, hogy az elemző betöltése, a projekt fordítása, az elemzés, a jelentés generálása és a formázás mindössze tizenegy kódsort igényeljen.

Az utolsó lépés, amire szükségünk van, az környezeti változók beállítása, amelyekkel néhány útvonalat kijelöltem a jobb olvashatóság érdekében. Ehhez navigáljunk: Parameters -> Add new parameter és adjunk hozzá három változót:

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
Marad annyit tennünk, hogy rákattintunk a Run gombra a jobb felső sarokban. Miközben zajlik a projekt fordítása és elemzése, mesélek a skriptről.

Kifejezetten a skriptről

Először le kell töltenünk a friss PVS-Studio disztribúciót. Ehhez a Chocolatey csomagkezelőt használjuk. Aki többet szeretne tudni erről, annak van egy megfelelő cikk:

choco install pvs-studio -y

Ezután indítsuk el a projekt build követő eszközt, a CLMonitort.

%CLmon% monitor –-attach

Aztán fordítsuk le a projektet, amelynek környezeti változója MSB az MSBuild megfelelő verziója, amelyre szükségem van a fordításhoz

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

Írjuk be a PVS-Studio felhasználónevet és a licenckulcsot:

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

A fordítás befejezése után még egyszer futtassuk a CLMonitort a preprocesszált fájlok és a statikus elemzés generálása érdekében:

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

Ezután használjunk egy másik eszközt az elosztónkból. A PlogConverter átkonvertálja a jelentést a standardról a TeamCity-specifikus formátumra. Ennek köszönhetően közvetlenül a build ablakában is megtekinthetjük.

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

Az utolsó lépésben kiadjuk a formázott jelentést a stdout, ahol azt a TeamCity parsera megkapja.

type "C:tempptest.plog_TeamCity.txt"

A skript teljes kódja:

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"

Közben a projekt építése és elemzése sikeresen befejeződött, át tudunk lépni a fülre Projects és ellenőrizhetjük ezt.

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
Most kattintsunk a Összes ellenőrzés, hogy megtekinthessük az elemző jelentését:

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
A figyelmeztetések a diagnosztikai szabályok száma alapján vannak csoportosítva. A kód navigálásához kattints a figyelmeztetések sorának számára. A jobb felső sarokban található kérdőjelre kattintva új fülön nyithatod meg a dokumentációt. Kód navigációt is végezhetsz a figyelmeztetés sorának számára kattintva. Távoli számítógépről való navigáció a SourceTreeRoot jelölő alkalmazásával lehetséges. Azok, akiket érdekel ez az elemző munkamódszer, nézhetik a megfelelő szakaszt dokumentáció.

Az elemző eredményeinek megtekintése

Miután befejeztük az építési telepítést és beállítást, javaslom, hogy nézzünk meg néhány érdekes figyelmeztetést, amelyeket az elemzett projektben találtak.

1. figyelmeztetés

V773 [CWE-401] A kivételt a 'result' pointer felszabadítása nélkül dobta el. Memória szivárgás lehetséges. libopenrct2 ObjectFactory.cpp 443

Object* CreateObjectFromJson(....)
{
  Object* result = nullptr;
  ....
  result = CreateObject(entry);
  ....
  if (readContext.WasError())
  {
    throw std::runtime_error("Az objektumnak hibái vannak");
  }
  ....
}

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("Érvénytelen objektumtípus");
  }
  return result;
}

Az elemző észlelte a hibát, miszerint a memória dinamikus allokálása után CreateObject, kivétel lép fel, a memória nincs felszabadítva, így memória szivárgás léphet fel.

2. figyelmeztetés

V501 Az '|' operátor bal és jobb oldalán azonos szubexpressziók vannak '(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),
  ....
};

Kevesen tudnák teljesíteni ezt a figyelmességi tesztet a statikus elemzőn kívül. Ez a példaként használt másolat éppen emiatt jó.

Figyelmeztetések N3

V703 Furcsa, hogy a 'flags' mező az 'RCT12BannerElement' származtatott osztályban felülírja az 'RCT12TileElementBase' alap osztály mezőjét. Ellenőrizd a következő sorokat: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

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

Természetesen nem mindig hibás, ha egy változó ugyanazzal a névvel szerepel az alap- és a származtatott osztályban. Azonban a származtatás technológiája önmagában feltételezi, hogy az alap osztály összes mezőjének jelen kell lennie a származtatott osztályban. Ha a származtatott osztályban a mezőket ugyanazzal a névvel deklaráljuk, zavart okozunk.

Figyelmeztetés N4

V793 Furcsa, hogy az 'imageDirection / 8' kifejezés eredménye a feltétel része. Talán ezt a kifejezést valami mással kellett volna összehasonlítani. libopenrct2 ObservationTower.cpp 38

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

Nézzük meg közelebbről. A kifejezés imageDirection / 8 hamis lesz, ha imageDirection a -7 és 7 közötti tartományban van. A második rész: (imageDirection / 8) != 3 ellenőrzi imageDirection a tartományon kívül: -31 és -24, valamint 24 és 31 között. Eléggé furcsának tartom, hogy a számok tartományba való illeszkedését ilyen módon ellenőrizzük, és még ha ebben a kódrészletben nincs is hiba, javasolnám a feltételek világosabb átírását. Ez jelentősen megkönnyítené azok életét, akik ezt a kódot olvassák és karbantartják.

Figyelmeztetés N5

V587 Furcsa hozzárendelési sorrend: A = B; B = A;. Ellenőrizd a következő sorokat: 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;
      ....
  }
  ....
}

Ez a kódrészlet valószínűleg dekompilálás révén került előtérbe. Úgy tűnik, a megmaradt megjegyzés alapján egy nem működő kódrészlet eltávolításra került. Azonban maradt egy pár művelet cursorId, amelyek szintén nem hordoznak túl sok jelentést.

Figyelmeztetés N6

V1004 [CWE-476] A 'player' mutató biztonságtalanul került felhasználásra, miután ellenőrizték a nullptr ellen. Ellenőrizze a sorokat: 2085, 2094. libopenrct2 Network.cpp 2094

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Új játékos hozzáadása.
    player = AddPlayer("", "");
    if (player)                                          // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
       {
         _serverConnection->Player = player;
       }
    }
    newPlayers.push_back(player->Id);                    // <=
  }
  ....
}

Ezt a kódot viszonylag egyszerűen lehetne javítani, szükség van egy harmadik ellenőrzésre az null mutató ellen, vagy bele kellene helyezni a feltételes operátor testébe. Javasolnám a második lehetőséget: player null mutatóra, vagy bele kellene helyezni a feltételes operátor testébe. Javasolnám a második lehetőséget:

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Új játékos hozzáadása.
    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] Az 'expr = nullptr' kifejezés mindig hamis. 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));
    ....
  }
  ....
}

Egy lépésben megszabadulhatunk a nehezen olvasható kódsoroktól, és megoldhatjuk az ellenőrzési problémát nullptr. Javaslom, hogy a kódot a következőképpen módosítsuk:

std::optional 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ónak ugyanazt az értéket rendelték. libopenrct2ui CustomListView.cpp 510

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

A kód meglehetősen furcsán néz ki. Úgy tűnik, hogy elírás történt vagy a feltételben, vagy a változó újbóli hozzárendelésénél. ColumnHeaderPressedCurrentState a jelentéseket hamis.

Következtetés

Ahogy látjuk, a PVS-Studio statikus elemző beépítése a TeamCity projektbe meglehetősen egyszerű. Ehhez mindössze egy kis konfigurációs fájlt kell írni. A kód ellenőrzése lehetővé teszi, hogy a problémákat azonnal a build után észleljük, ami segít azokat olyan időben orvosolni, amikor a javítások költsége és nehézsége még alacsony.

PVS-Studio és a Continuous Integration: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.
Ha szeretnéd megosztani ezt a cikket az angol nyelvű közönséggel, kérlek használd a fordítás linkjét: Vladislav Stolyarov. PVS-Studio és Folyamatos Integráció: TeamCity. Az Open RollerCoaster Tycoon 2 projekt elemzése.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster