
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
— 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.
— 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.
– 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.
— 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:

Kattintsunk a Create Project gombra. Ezután válasszuk a Manually lehetőséget, töltsük ki a mezőket.

A gomb megnyomása után Létrehozás, üdvözöl minket a beállítási ablak.

Kattintsunk Hozzunk létre build konfigurációt.

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.

Végül áttérünk a projekt beállításaira.

Adjuk hozzá a build lépéseket, ehhez kattintsunk: Build steps -> Add build step.

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:

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:

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ő :
choco install pvs-studio -yEzután indítsuk el a projekt build követő eszközt, a CLMonitort.
%CLmon% monitor –-attachAztá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.

Most kattintsunk a Összes ellenőrzés, hogy megtekinthessük az elemző jelentését:

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 .
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
[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
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
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
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
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
[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
[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
[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.
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. .
Forrás: habr.com
