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

Kliknimo na gumb Create Project. Zatim odabiremo Manually i ispunjavamo polja.

Nakon što pritisnemo gumb Create, susreće nas prozor s postavkama.

Kliknimo Create build configuration.

Ispunjavamo polja, pritisnemo Create. Vidimo prozor s prijedlogom za izbor sustava kontrole verzija. Budući da izvorni kodovi već leže lokalno, kliknemo Skip.

Na kraju, prelazimo na postavke projekta.

Dodajmo korake izgradnje, za to kliknemo: Build steps -> Add build step.

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:

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:

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 :
choco install pvs-studio -yZatim ćemo pokrenuti alat za praćenje izgradnje projekta CLMonitor.
%CLmon% monitor --attachNakon 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:PublishPortableUnijet ć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.

Sada kliknemo na Inspections Total, kako bismo prešli na pregled izvještaja analize:

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 .
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
[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
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
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
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....) { if ((imageDirection / 8) && (imageDirection / 8) != 3) { .... } .... }
Pogledajmo malo detaljnije. IzrazimageDirection / 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 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
[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
[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
[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.
Ako želite podijeliti ovaj članak s anglofonom publikom, koristite link na prijevod: Vladislav Stolyarov. .
Izvor: habr.com
