
Jedan od najnovijih scenarija za korištenje PVS-Studio analizatora je njegova integracija sa CI sustavima. I premda se analiza PVS-Studio projekta iz gotovo bilo kojeg kontinuiranog integracijskog sustava može ugraditi u samo nekoliko naredbi, nastavljamo ovaj proces činiti još praktičnijim. PVS-Studio sada ima podršku za pretvaranje izlaza analizatora u format za TeamCity - TeamCity Inspections Type. Pogledajmo kako radi.
Informacije o korištenom softveru
— статический анализатор С, С++, C# и Java кода, предназначенный для облегчения задачи поиска и исправления различного рода ошибок. Анализатор можно использовать в Windows, Linux и macOS. В данной статье мы будем активно использовать не только сам анализатор, но и некоторые утилиты из его дистрибутива.
— je poslužitelj za nadgledanje koji prati pokretanje kompilatora. Mora se pokrenuti neposredno prije početka izgradnje vašeg projekta. U načinu njuškanja, poslužitelj će presresti izvođenja svih podržanih prevoditelja. Važno je napomenuti da se ovaj uslužni program može koristiti samo za analizu C/C++ projekata.
– uslužni program za pretvaranje izvješća analizatora u različite formate.
Informacije o projektu koji se proučava
Isprobajmo ovu funkcionalnost na praktičnom primjeru – analizirajmo projekt OpenRCT2.
- otvorena implementacija igre RollerCoaster Tycoon 2 (RCT2), proširujući je novim funkcijama i ispravljajući greške. Igra se vrti oko izgradnje i održavanja zabavnog parka koji sadrži vožnje, trgovine i objekte. Igrač mora pokušati zaraditi i održati dobru reputaciju parka, a da gosti budu zadovoljni. OpenRCT2 vam omogućuje igranje u scenariju i sandboxu. Scenariji zahtijevaju od igrača da izvrši određeni zadatak unutar postavljenog vremena, dok Sandbox omogućuje igraču da izgradi fleksibilniji park bez ikakvih ograničenja ili financija.
podešavanje
Kako bih uštedio vrijeme, vjerojatno ću preskočiti proces instalacije i započeti od trenutka kada imam pokrenut TeamCity server na mom računalu. Moramo otići na: localhost:{port naveden tijekom postupka instalacije} (u mom slučaju, localhost:9090) i unijeti podatke za autorizaciju. Nakon ulaska pozdravit će nas:

Pritisnite gumb Kreiraj projekt. Zatim odaberite Ručno i ispunite polja.

Nakon pritiska na tipku stvoriti, dočekuje nas prozor s postavkama.

Kliknimo Stvorite konfiguraciju gradnje.

Ispunite polja i kliknite stvoriti. Vidimo prozor koji traži da odaberete sustav kontrole verzija. Budući da se izvori već nalaze lokalno, kliknite Preskočiti.

Konačno, prelazimo na postavke projekta.

Dodajmo korake sastavljanja, za ovo kliknite: Koraci izrade -> Dodaj korak izrade.

Ovdje biramo:
- Vrsta trkača -> Naredbeni redak
- Pokreni -> Prilagođena skripta
Budući da ćemo tijekom kompilacije projekta izvršiti analizu, montaža i analiza trebaju biti jedan korak, stoga ispunite polje Prilagođena skripta:

Kasnije ćemo pogledati pojedinačne korake. Važno je da učitavanje analizatora, sastavljanje projekta, njegova analiza, ispisivanje izvješća i formatiranje zahtijeva samo jedanaest redaka koda.
Zadnje što trebamo učiniti je postaviti varijable okruženja, za koje sam naveo neke načine za poboljšanje njihove čitljivosti. Da bismo to učinili, idemo dalje: Parametri -> Dodaj novi parametar i dodajte tri varijable:

Sve što trebate učiniti je pritisnuti gumb trčanje u gornjem desnom kutu. Dok se projekt sastavlja i analizira, pričat ću vam o scenariju.
Izravno scenarij
Prvo moramo preuzeti najnoviju PVS-Studio distribuciju. Za to koristimo upravitelj paketa Chocolatey. Za one koji žele znati više o ovome, postoji odgovarajući :
choco install pvs-studio -yZatim, pokrenimo uslužni program za praćenje izgradnje projekta CLMonitor.
%CLmon% monitor –-attachZatim ćemo izgraditi projekt kao varijablu okruženja MSB je put do verzije MSBuild-a koju trebam izgraditi
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableUnesite prijavu i licencni ključ za PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Nakon završetka izgradnje, ponovno pokrenite CLMonitor za generiranje prethodno obrađenih datoteka i statičku analizu:
%CLmon% analyze -l "c:ptest.plog"Zatim ćemo koristiti drugi pomoćni program iz naše distribucije. PlogConverter pretvara izvješće iz standardnog formata u format specifičan za TeamCity. Zahvaljujući tome, moći ćemo ga vidjeti izravno u prozoru za izradu.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Zadnji korak je prikazati formatirano izvješće stdout, gdje će ga pokupiti parser TeamCity.
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, montaža i analiza projekta je uspješno završena, možemo prijeći na karticu Projekti i uvjeriti se u ovome.

Sada kliknimo na Inspekcije Ukupnoza odlazak na pregled izvješća analizatora:

Upozorenja su grupirana prema brojevima dijagnostičkih pravila. Za navigaciju kodom morate kliknuti na broj retka s upozorenjem. Klikom na upitnik u gornjem desnom kutu otvorit će vam se novi tab s dokumentacijom. Također se možete kretati kroz kod klikom na broj retka s upozorenjem analizatora. Prilikom korištenja moguća je navigacija s udaljenog računala SourceTreeRoot marker. Svatko tko je zainteresiran za ovaj način rada analizatora može se upoznati s odgovarajućim odjeljkom .
Pregled rezultata analizatora
Sada kada smo završili s implementacijom i konfiguracijom izgradnje, pogledajmo neka zanimljiva upozorenja koja se nalaze u projektu koji gledamo.
Upozorenje N1
[CWE-401] Iznimka je bačena bez otpuštanja pokazivača 'rezultata'. Moguće 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 uočio pogrešku koja je nakon dinamičke dodjele memorije u CreateObject, kada se dogodi iznimka, memorija se ne briše i dolazi do curenja memorije.
Upozorenje N2
Postoje identični podizrazi '(1ULL << WIDX_MONTH_BOX)' lijevo i desno od '|' operater. 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 je ljudi osim statičkog analizatora moglo proći ovaj test pažnje. Ovaj copy-paste primjer je dobar upravo iz tog razloga.
Upozorenja N3
Čudno je da polje 'flags' u izvedenoj klasi 'RCT12BannerElement' prepisuje polje u osnovnoj klasi 'RCT12TileElementBase'. Provjerite linije: 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 iu potomku nije uvijek pogreška. Međutim, sama tehnologija nasljeđivanja pretpostavlja da su sva polja roditeljske klase prisutna u podređenoj klasi. Proglašavanjem istoimenih polja u nasljedniku unosimo zabunu.
Upozorenje N4
Čudno je da je rezultat naredbe 'imageDirection / 8' dio uvjeta. Možda je ovu izjavu trebalo usporediti s nečim drugim. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Pogledajmo pobliže. Izraz imageDirection/8 bit će lažno ako imageDirection je u rasponu od -7 do 7. Drugi dio: (imageDirection / 8) != 3 provjere imageDirection jer su izvan raspona: od -31 do -24 odnosno od 24 do 31. Čini mi se prilično čudnim provjeravati brojeve za uključivanje u određeni raspon na ovaj način i, čak i ako nema pogreške u ovom dijelu koda, preporučio bih da se ti uvjeti ponovno napišu kako bi bili eksplicitniji. To bi znatno olakšalo život ljudima koji bi čitali i održavali ovaj kod.
Upozorenje N5
Neparan niz zadataka ove vrste: A = B; B = A;. Provjerite 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. Tada je, sudeći po ostavljenom komentaru, uklonjen dio neradnog koda. No, ostalo je još par operacija cursorId, što također nema previše smisla.
Upozorenje N6
[CWE-476] Pokazivač 'player' korišten je nesigurno nakon što je provjeren u odnosu na nullptr. Provjerite linije: 2085, 2094. libopenrct2 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); // <=
}
....
}Ovaj kod je prilično lako ispraviti; samo ga trebate provjeriti treći put igrač na nulti pokazivač ili ga dodajte u tijelo uvjetne naredbe. Ja bih predložio drugu opciju:
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);
}
}
....
}Upozorenje N7
[CWE-570] Izraz 'name == nullptr' uvijek je lažan. libopenrct2 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));
....
}
....
}Možete se riješiti teško čitljivog retka koda jednim potezom i riješiti problem s provjerom nullptr. Predlažem promjenu koda na sljedeći način:
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);
....
}
....
}Upozorenje N8
[CWE-1164] Varijabli 'ColumnHeaderPressedCurrentState' dodijeljena je ista vrijednost. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Kod izgleda prilično čudno. Čini mi se da je došlo do tipfelera ili u uvjetu ili kod ponovnog dodjeljivanja varijable ColumnHeaderPressedCurrentState smisao lažan.
Izlaz
Kao što vidimo, integracija PVS-Studio statičkog analizatora u vaš TeamCity projekt prilično je jednostavna. Da biste to učinili, dovoljno je napisati samo jednu malu konfiguracijsku datoteku. Provjera koda omogućit će vam da identificirate probleme odmah nakon sklapanja, što će pomoći u njihovom otklanjanju kada su složenost i cijena promjena još uvijek niski.
Ako želite podijeliti ovaj članak s publikom koja govori engleski, upotrijebite vezu za prijevod: Vladislav Stolyarov. .
Izvor: www.habr.com
