
NjĂ« nga skenarĂ«t mĂ« aktualĂ« pĂ«r pĂ«rdorimin e analizatorit PVS-Studio Ă«shtĂ« integrimi i tij me sistemet CI. Edhe pse analiza e projektit PVS-Studio mund tĂ« integrohet nga çdo sistem tĂ« vazhdueshĂ«m tĂ« integrimit pĂ«rmes vetĂ«m disa komandave, ne vazhdojmĂ« ta bĂ«jmĂ« kĂ«tĂ« proces edhe mĂ« tĂ« lehtĂ«. PVS-Studio tani mbĂ«shtet transformimin e daljes sĂ« analizatorit nĂ« njĂ« format pĂ«r TeamCity â TeamCity Inspections Type. Le tĂ« shohim se si funksionon.
Informata mbi softuerin e përdorur
â njĂ« analizator statik pĂ«r kode C, C++, C# dhe Java, i destinuar pĂ«r tĂ« lehtĂ«suar detyrĂ«n e gjetjes dhe korrigjimit tĂ« ndryshimeve tĂ« ndryshme. Analizatori mund tĂ« pĂ«rdoret nĂ« Windows, Linux dhe macOS. NĂ« kĂ«tĂ« artikull, ne do tĂ« pĂ«rdorim aktivisht jo vetĂ«m analizatorin, por edhe disa nga utilitetet nga distribuimi i tij.
â pĂ«rfaqĂ«son njĂ« server monitorimi qĂ« monitoron ekzekutimet e kompajlerĂ«ve. Duhet tĂ« aktivizohet menjĂ«herĂ« para fillimit tĂ« ndĂ«rtimit tĂ« projektit tuaj. NĂ« mĂ«nyrĂ«n e monitorimit, serveri do tĂ« kapĂ« ekzekutimet e tĂ« gjithĂ« kompajlerĂ«ve tĂ« mbĂ«shtetur. Duhet theksuar se ky utilitet mund tĂ« pĂ«rdoret vetĂ«m pĂ«r analizimin e projekteve C/C++.
â njĂ« mjet pĂ«r konvertimin e raporteve tĂ« analizuesit nĂ« formate tĂ« ndryshme.
Informacion mbi projektin e studiuar
Le ta provojmĂ« kĂ«tĂ« funksionalitet me njĂ« shembull praktik â tĂ« analizojmĂ« projektin OpenRCT2.
â Ă«shtĂ« njĂ« implementim i hapur i lojĂ«s RollerCoaster Tycoon 2 (RCT2), qĂ« e zgjeron atĂ« me funksione tĂ« reja dhe rregullon gabimet. Loja pĂ«rqendrohet rreth ndĂ«rtimit dhe menaxhimit tĂ« njĂ« parku argĂ«timi, ku gjenden atraksione, dyqane dhe objekte. Lojtari duhet tĂ« pĂ«rpiqet tĂ« marrĂ« fitim dhe tĂ« mbajĂ« njĂ« reputacion tĂ« mirĂ« pĂ«r parkun, duke u siguruar njĂ«kohĂ«sisht qĂ« vizitorĂ«t tĂ« jenĂ« tĂ« lumtur. OpenRCT2 lejon qĂ« tĂ« luhet si nĂ« skenarĂ«, ashtu edhe nĂ« mĂ«nyrĂ«n sandbox. SkenarĂ«t kĂ«rkojnĂ« qĂ« lojtari tĂ« pĂ«rmbushĂ« njĂ« detyrĂ« tĂ« caktuar brenda njĂ« kohe tĂ« caktuar, ndĂ«rsa mĂ«nyra sandbox i lejon lojtarit tĂ« ndĂ«rtosh njĂ« park mĂ« fleksibĂ«l pa kufizime apo financa.
Konfigurimi
Për të kursyer kohë, ndoshta do të anashkaloj procesin e instalimit dhe do të filloj nga momenti kur kam serverin TeamCity të hapur në kompjuterin tim. Na duhet të shkojmë te: localhost:{porta e caktuar gjatë procesit të instalimit}(në rastin tim, localhost:9090) dhe të futim të dhënat për autentifikim. Pasi të hyjmë, do të na presë:

Klikoni butonin Create Project. Më pas, zgjidhim Manually dhe plotësojmë fushat.

Pas klikimit në butonin Create, na pritet një dritare me cilësimet.

Klikoni Create build configuration.

Plotësojmë fushat, klikojmë Create. Ne shohim një dritare që ofron zgjedhjen e sistemit të kontrollit të versioneve. Duke qenë se burimet tashmë ndodhen lokalitet, klikojmë Skip.

Më në fund, ne kalojmë në cilësimet e projektit.

Shtojmë hapat e ndërtimit, për këtë klikojmë: Build steps -> Add build step.

Këtu zgjidhim:
- Runner type -> Command Line
- Run -> Custom Script
Duke qenë se do të kryejmë analizën gjatë kompaktimit të projektit, ndërtimi dhe analiza duhet të jenë një hap, prandaj plotësojmë fushën Custom Script:

NĂ« hapa tĂ« veçantĂ« do tĂ« ndalemi mĂ« vonĂ«. ĂshtĂ« e rĂ«ndĂ«sishme qĂ« ngarkimi i analizuesit, ndĂ«rtimi i projektit, analiza e tij, prodhimi i raportit dhe formatimi i tij tĂ« zĂ«nĂ« vetĂ«m njĂ«mbĂ«dhjetĂ« rreshta kodi.
E fundit që na nevojitet është të vendosim variablat e ambientit, për të cilat kam shënuar disa rrugë për të përmirësuar lexueshmërinë e tyre. Për këtë do të kalojmë në: Parameters -> Add new parameter dhe do të shtojmë tre variabla:

Na mbetet të klikojmë mbi butonin Ekzekuto në këndin e sipërm të djathtë. Ndërsa ndërtimi dhe analiza e projektit po zhvillohen, do t'ju tregoj rreth skriptit.
Skripti në vetvete
Së pari, na nevojitet të shkarkojmë distribucionin e ri të PVS-Studio. Për këtë, ne do të përdorim menaxherin e paketave Chocolatey. Për ata që duan të dinë më shumë rreth kësaj, ka informacion të përkatshëm. :
choco install pvs-studio -yMë pas, do të nisnim utilitarin për monitorimin e ndërtimit të projektit CLMonitor.
%CLmon% monitor â-attachPastaj do tĂ« ndĂ«rtojmĂ« projektin, duke pĂ«rdorur si variabĂ«l mjedisi MSB i cili pĂ«rfaqĂ«son rrugĂ«n pĂ«r versionin e duhur tĂ« MSBuild pĂ«r ndĂ«rtim
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableDo të hyjmë me emrin e përdoruesit dhe çelësin e licencës për PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Pas përfundimit të ndërtimit, do të nisnim sërish CLMonitor për të gjeneruar skedarët e parapërpunuar dhe analizën statike:
%CLmon% analyze -l "c:ptest.plog"Më pas do të përdorim edhe një utilitar nga distribucioni ynë. PlogConverter konverton raportin nga formati standard në atë specifik për TeamCity. Falë kësaj, do të mund ta shohim atë drejtpërdrejt në dritaren e ndërtimit.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Si veprimin përfundimtar, do të nxjerrim raportin e formatizuar në stdout, ku do ta kapë parser-i i TeamCity.
type "C:tempptest.plog_TeamCity.txt"Kodi i plotë i skriptit:
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"Ndërkohë, ndërtimi dhe analiza e projektit përfunduan me sukses, tani mund të kalojmë te tabu Projekti dhe të sigurohemi për këtë.

Tani të klikojmë në Totali i Inspeksioneve, për të kaluar në shikimin e raportit të analizuesit:

Këmbënguljet janë grupuar sipas numrave të rregullave diagnostikuese. Për të naviguar në kod, duhet të klikoni në numrin e rreshtit me paralajmërimin. Kliki në shenjën e pyetjes në këndin e sipërm të djathtë do t'ju hapë një tab të re me dokumentacionin. gjithashtu mund të navigoni në kod duke klikuar në numrin e rreshtit me paralajmërimin e analizuesit. Navigimi nga një kompjuter i largët është i mundur duke përdorur SourceTreeRoot marker. Ata që janë të interesuar për këtë mod të funksionimit të analizuesit, mund të konsultohen me seksionin përkatës .
Shikimi i rezultateve të punës së analizuesit
Pasi përfunduam me implementimin dhe konfigurimin e ndërtimit, sugjeroj të shohim disa paralajmërime interesante që u zbuluan në projektin e shqyrtuar.
Paralajmërimi N1
[CWE-401] Përjashtimi u hodh pa lirimin e treguesit 'rezultat'. Një rrjedhje memorie është e mundur. libopenrct2 ObjectFactory.cpp 443
Object* CreateObjectFromJson(....)
{
Object* rezultat = nullptr;
....
rezultat = CreateObject(entry);
....
nëse (readContext.WasError())
{
throw std::runtime_error("Objekti ka gabime");
}
....
}
Object* CreateObject(const rct_object_entry& entry)
{
Object* rezultat;
switch (entry.GetType())
{
rast tipi_OBJECT_RIDE:
rezultat = new RideObject(entry);
break;
rast tipi_OBJECT_SMALL_SCENERY:
rezultat = new SmallSceneryObject(entry);
break;
rast tipi_OBJECT_LARGE_SCENERY:
rezultat = new LargeSceneryObject(entry);
break;
....
default:
throw std::runtime_error("Lloji i objektit është i pavlefshëm");
}
kthe rezultat;
}Analizuesi vuri re një gabim, duke theksuar se pas alokimit dinamik të memories në CreateObject, në rast të një përjashtimi, memoria nuk pastrohet, duke rezultuar kështu në një rrjedhje memorje.
Paralajmërimi N2
Ka nën-shprehje identike '(1ULL << WIDX_MONTH_BOX)' të majtë dhe të djathtë të operatorit '|'. 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),
....
};Pothuajse askush, përveç analizuesit statik, nuk do të ishte në gjendje të kalonte këtë test vëmendësie. Ky shembull i kopjimit është i mirë pikërisht për këtë.
Paralajmërimet N3
ĂshtĂ« e çuditshme qĂ« fusha 'flags' nĂ« klasĂ«n e derivateve 'RCT12BannerElement' mbulon fushĂ«n nĂ« klasĂ«n bazĂ« 'RCT12TileElementBase'. Kontrolloni rreshtat: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};Sigurisht, përdorimi i një variable me emër të njëjtë në klasën bazë dhe në trashëgimtar nuk është gjithmonë një gabim. Megjithatë, teknologjia e trashëgimisë vetë supozon praninë e të gjitha kampeve të klasës prindore në atë fëmijë. Duke shpallur në trashëgimtar kampe me emra të tillë, ne krijojmë konfuzion.
Kujdesi N4
ĂshtĂ« e çuditshme qĂ« rezultati i deklaratĂ«s 'imageDirection / 8' Ă«shtĂ« pjesĂ« e kushteve. Ndoshta, kjo deklaratĂ« duhet tĂ« ishte krahasuar me diçka tjetĂ«r. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Le të hedhim një vështrim më të afërt. Shprehja imageDirection / 8 do të jetë false në rastin kur imageDirection është në intervalin nga -7 deri në 7. Pjesa e dytë: (imageDirection / 8) != 3 kontrollon imageDirection për qëndrim jashtë intervalit: nga -31 deri në -24 dhe nga 24 deri në 31 përkatësisht. Më duket e çuditshme të kontrollosh numrat për përfshirje në një interval të caktuar në këtë mënyrë dhe, edhe nëse në këtë segment kodi nuk ka gabim, do të rekomandoja të ri-shkruhen këto kushte në mënyra më të qarta. Kjo do të thjeshtonte ndjeshëm jetën e atyre që do të lexonin dhe mbështetnin këtë kod.
Kujdesi N5
Një sekuencë e çuditshme e caktimeve të kësaj lloj: A = B; B = A;. Kontrolloni rreshtat: 1115, 1118. libopenrct2ui MouseInput.cpp 1118
void process_mouse_over(....)
{
....
switch (window->widgets[widgetId].type)
{
case WWT_VIEWPORT:
ebx = 0;
edi = cursorId; // <=
// Ngjarja e dritares WE_UNKNOWN_0E u thirr këtu,
// por asnjë dritare në të vërtetë nuk implementoi një menaxher dhe
// nuk dihet për çfarë ishte
cursorId = edi; // <=
if ((ebx & 0xFF) != 0)
{
set_cursor(cursorId);
return;
}
break;
....
}
....
}Ky fragment kodi, për siguri, është marrë përmes dekompilimit. Pastaj, sipas komentit të lënë, është fshirë një pjesë e kodit të papunuar. Megjithatë, ka mbetur një çift operacionesh mbi cursorId, të cilat gjithashtu nuk kanë ndonjë kuptim të veçantë.
Kujdesi N6
[CWE-476] Pika 'player' u përdor në mënyrë të pasigurt pas verifikimit kundrejt nullptr. Kontrolloni rreshtat: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Shtoni lojtarin e ri.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Ky kod është shumë i lehtë të rregullohet, është e nevojshme ose të kontrolloni për herë të tretë player në një tregues të null, ose ta përfshini atë në trupin e operatorit kushtor. Do të sugjeroja opsionin e dytë:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Shto lojtar të ri.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Paralajmërimi N7
[CWE-570] Shprehja 'name == nullptr' gjithmonë është e pavërtetë. 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));
....
}
....
}Mund të heqim njëherazi një varg të vështirë për t'u lexuar dhe të zgjidhim problemin me kontrollimin e nullptr. Propozoj të ndryshojmë kodin në këtë mënyrë:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = "";
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Paralajmërimi N8
[CWE-1164] Variabla 'ColumnHeaderPressedCurrentState' u caktoi të njëjtin vlerë. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Kodi duket mjaft i çuditshëm. Më duket se ka pasur një gabim qoftë në kusht, ose gjatë ripërdorimit të variablës. KolonaHeaderPressedCurrentState vlerat false.
Përfundim
Siç e shohim, integrimi i analizuesit statik PVS-Studio në projektin tuaj në TeamCity është mjaft i thjeshtë. Mjafton të shkruani vetëm një skedar të vogël konfigurimi. Kontrolli i kodit do të ndihmojë në identifikimin e problemeve menjëherë pas ndërtimit, duke lejuar që të zgjidhen atëherë kur kompleksiteti dhe kostoja e korrigjimeve janë akoma të vogla.
Nëse dëshironi të ndani këtë artikull me një audiencë anglishtfolëse, ju lutem përdorni lidhjen për përkthimin: Vladislav Stolyarov. .
Burimi: habr.com
