
NjĂ« nga skenarĂ«t mĂ« tĂ« rĂ«ndĂ«sishĂ«m tĂ« pĂ«rdorimit tĂ« analizatorit PVS-Studio Ă«shtĂ« integrimi i tij me sistemet CI. Edhe pse analiza e projektit me PVS-Studio mund tĂ« integrohet nĂ« çdo sistem continuous integration me vetĂ«m disa komanda, ne vazhdojmĂ« ta bĂ«jmĂ« kĂ«tĂ« proces edhe mĂ« tĂ« lehtĂ«. PVS-Studio tani ka mbĂ«shtetje pĂ«r shndĂ«rrimin e daljes sĂ« analizatorit nĂ« formatin pĂ«r TeamCity â Lloji i Inspektimeve TeamCity. Le tĂ« shohim se si funksionon kjo.
Informacion mbi softuerin e përdorur
â njĂ« analizator statik pĂ«r kodin nĂ« C, C++, C# dhe Java, i cili Ă«shtĂ« i destinuar pĂ«r tĂ« lehtĂ«suar detyrĂ«n e gjetjes dhe korrigjimit tĂ« gabimeve 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 mjete nga shpĂ«rndarja e tij.
â Ă«shtĂ« njĂ« server monitorimi qĂ« monitoron nisjet e kompajlerĂ«ve. Ai duhet tĂ« nisĂ« pĂ«rpara se tĂ« filloni ndĂ«rtimin e projektit tuaj. NĂ« modin e monitorimit, serveri do tĂ« kapĂ« tĂ« gjitha nisjet e kompajlerĂ«ve tĂ« mbĂ«shtetur. Duhet theksuar se kjo mjet mund tĂ« pĂ«rdoret vetĂ«m pĂ«r analizĂ«n e projekteve C/C++.
â njĂ« mjet pĂ«r konvertimin e raporteve tĂ« analizatorit nĂ« formate tĂ« ndryshme.
Informacion mbi projektin e hulumtuar
Le tĂ« provojmĂ« kĂ«tĂ« funksionalitet me njĂ« shembull praktik â tĂ« analizojmĂ« projektin OpenRCT2.
â njĂ« implementim i hapur i lojĂ«s RollerCoaster Tycoon 2 (RCT2), qĂ« e zgjeron atĂ« me karakteristika tĂ« reja dhe korrigjon gabimet. Procesi i lojĂ«s pĂ«rqendrohet nĂ« ndĂ«rtimin dhe menaxhimin e njĂ« parku tematik, ku ndodhen atraksione, dyqane dhe objekte. Lojtari duhet tĂ« pĂ«rpiqet tĂ« arrijĂ« fitim dhe tĂ« ruajĂ« njĂ« reputacion tĂ« mirĂ« pĂ«r parkun, duke mbajtur gjithashtu vizitorĂ«t tĂ« lumtur. OpenRCT2 lejon tĂ« luhet si nĂ« skenarĂ«, ashtu edhe nĂ« modin sandbox. SkenarĂ«t kĂ«rkojnĂ« qĂ« lojtari tĂ« pĂ«rfundojĂ« njĂ« detyrĂ« specifike brenda njĂ« kohe tĂ« caktuar, ndĂ«rsa sandbox-i lejon lojtarin tĂ« ndĂ«rtojĂ« njĂ« park mĂ« fleksibĂ«l pa asnjĂ« kufizim apo financa.
Configuration
Për të kursyer kohë, ndoshta do ta anashkaloja procesin e instalimit dhe do të filloja nga momenti kur unë kam serverin TeamCity të hapur në kompjuterin tim. Ne duhet të kalojmë në: localhost:{porta e caktuar gjatë procesit të instalimit}(në rastin tim, localhost:9090) dhe të futim të dhënat për autorizar. Pas hyrjes do të na mirëpritet:

Klikoni butonin Krijo Projekt. Më pas, zgjidhni Manualisht dhe plotësoni fushat.

Pas klikimit në butonin Krijo, na pret një dritare me cilësimet.

Të klikojmë Krijo konfigurimin e ndërtimit.

Plotësoni fushat, klikoni Krijo. Shihni një dritare që ofron zgjedhjen e sistemit të kontrollit të versioneve. Nëse burimet tanë janë mbajtur lokal, klikoni Shkalo.

Në fund, kalojmë në cilësimet e projektit.

Do të shtojmë hapat e ndërtimit, për këtë klikoni: Hapat e ndërtimit -> Shto hap ndërtimi.

Këtu zgjidhni:
- Lloji i runner-it -> Linjë komandash
- Ekzekuto -> Skript i personalizuar
Duke qenë se do të kryejmë analizën gjatë kompilimit të projektit, ndërtimi dhe analiza duhet të jenë një hap i vetëm, kështu që plotësoni fushën Skript i personalizuar:

Do tĂ« ndalemi nĂ« hapa tĂ« veçantĂ« mĂ« vonĂ«. ĂshtĂ« e rĂ«ndĂ«sishme qĂ« ngarkimi i analizatorit, ndĂ«rtimi i projektit, analiza e tij, gjenerimi i raportit dhe formatimi tĂ« zgjasin vetĂ«m njĂ«mbĂ«dhjetĂ« rreshta kod.
E fundit që na nevojitet të bëjmë është të vendosim variablat e ambientit, të cilat kam shënuar disa rrugë për ta bërë më të lehtë lexueshmërinë e tyre. Për këtë kalojmë në: Parametrat -> Shto parametrin e ri dhe shtojmë tri variabla:

Tani mbetet të klikoni butonin Ekzekuto në këndin e sipërm të djathtë. Ndërsa ndodhet në proces ndërtimi dhe analize, do t'ju flas për skriptin.
Skripti në fjalë
Së pari, na nevojitet të shkarkojmë versionin më të ri të PVS-Studio. Përdorim menaxherin e paketave Chocoaltey. Për ata që dëshirojnë të dinë më shumë, ka një informacion të nevojshëm. :
choco install pvs-studio -yMë pas, do të nisim mjetin e monitorimit të ndërtimit të projektit CLMonitor.
%CLmon% monitor --bashkëngjitMë pas do të kryejmë ndërtimin e projektit, duke përdorur si variabël ambienti MSB që paraqet rrugën për versionin e nevojshëm për ndërtimin e MSBuild
%MSB% %ProjPath% /t:pastro
%MSB% %ProjPath% /t:rinovim /p:konfigurimi=zgjedhje
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublikimPortativDo të futim emrin e përdoruesit dhe çelësin e licencës PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Pas përfundimit të ndërtimit, do të nisim sërish CLMonitor për të gjeneruar skedarët e përpunuar dhe analizën statike:
%CLmon% analizo -l "c:ptest.plog"Më pas, do të përdorim një tjetër mjet nga distibutivi ynë. PlogConverter konverton raportin nga formati standard në atë specifik për TeamCity. Kështu, do të mund ta shohim drejtpërdrejt në dritaren e ndërtimit.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Veprimi i fundit do të jetë të nxjerrim raportin e formatizuar në stdout, ku do të kapet nga 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, mund të kalojmë në skedën Projektet dhe të sigurohemi për këtë.

Tani të klikojmë në Inspections Total, për të vazhduar në shikimin e raportit të analizatorit:

Paralajmërimet grupohen sipas numrave të rregullave diagnostike. Për të naviguar në kod, duhet të klikoni në numrin e rreshtit me paralajmërim. Klikimi në shenjën e pyetjes në këndin e sipërm të djathtë do t'iu hapë një skedë të re me dokumentacion. Mund të bëni gjithashtu navigim në kod duke klikuar në numrin e rreshtit me paralajmërim të analizatorit. Navigimi nga një kompjuter i largët është i mundur me aplikimin e SourceTreeRoot markerit. Ai që është i interesuar për këtë mod të punës së analizatorit mund të njohë me seksionin përkatës .
Shikimi i rezultateve të punës së analizatorit
Pasi përfunduam me vendosjen dhe konfigurimin e ndërtimit, propozoi të shikojmë disa paralajmërime interesante që u zbuluan në projektin e shqyrtuar.
Kujdesi N1
[CWE-401] Po e përjashtojmë pa çliruar treguesin 'result'. Një rrjedhje memorjeje është e mundshme. libopenrct2 ObjectFactory.cpp 443
Object* CreateObjectFromJson(....)
{
Object* result = nullptr;
....
result = CreateObject(entry);
....
if (readContext.WasError())
{
throw std::runtime_error("Objekti ka gabime");
}
....
}
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("Lloji i objektit është i pavlefshëm");
}
return result;
}Analizatori vuri re një gabim që, pas ndarjes dinamike të memorjes në CreateObject, gjatë hedhjes së një përjashtimi, memoria nuk pastrohet, për rrjedhojë ndodhin rrjedhjet e memorjes.
Kujdesi N2
Ka nën-shprehje identike '(1ULL << WIDX_MONTH_BOX)' në 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),
....
};Pak një analizë statike do të ishte i vetmi që do të kalonte këtë provim vëmendësie. Ky shembull kopipast është i mirë pikërisht për këtë.
Paralajmërimet N3
ĂshtĂ« e çuditshme qĂ« fusha 'flags' nĂ« klasĂ«n e nxjerrĂ« '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ë variabli me të njëjtin emër në klasën bazë dhe në trashëgimtar nuk është gjithmonë një gabim. Megjithatë, teknologjia e trashëgimisë vetë supozon praninë e të gjitha fushave të klasës prind në klasën fëmijë. Duke shpallur fusha me të njëjtin emër në trashëgimtar, krijojmë konfuzion.
Paralajmërimi N4
ĂshtĂ« e çuditshme qĂ« rezultati i deklaratĂ«s 'imageDirection / 8' Ă«shtĂ« njĂ« pjesĂ« e kushtit. Ndoshta, kjo deklaratĂ« duhej 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ë shqyrtojmë më në detaje. Shprehja imageDirection / 8 do të jetë false në rastin kur imageDirection ndodhet në intervalin nga -7 në 7. Pjesa e dytë: (imageDirection / 8) != 3 kontrollon imageDirection për të kaluar jashtë intervalit: nga -31 në -24 dhe nga 24 në 31 përkatësisht. Më duket mjaft 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ë fragment kodi nuk ka ndonjë gabim, do të rekomandoja të rishkruhej këto kushte në një format më të qartë. Kjo do t'i thjeshtësonte ndjeshëm jetën atyre që do të lexonin dhe mirëmbanin këtë kod.
Kujdesi N5
Një sekuencë e çuditshme e caktimeve të këtij lloji: 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; // <=
// 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;
....
}
....
}Ky këtë fragment kodi, me siguri, e morëm përmes dekodimit. Pastaj, sipas komentit të lënë, është hequr një pjesë e kodit që nuk funksiononte. Megjithatë, mbeturinë disa operacione mbi cursorId, të cilat gjithashtu nuk kanë ndonjë kuptim të veçantë.
Alarmi 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)
{
// Shto lojtar të ri.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Ky kod është mjaft e thjeshtë për t'u rregulluar, duhet ose një herë të tretë të kontrollohet player për pointerin null, ose të përfshihet brenda trupit të operatorit kusht. 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);
}
}
....
}Alarmi N7
[CWE-570] Shprehja 'name == nullptr' është gjithmonë e gabuar. 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ë eliminojmë njëherë e përgjithmonë këtë varg të vështirë për t'u lexuar dhe të zgjidhim problemin me verifikimin e nullptr. Propozoj të ndryshojmë kodin si më poshtë:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = "";
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Alarmi N8
[CWE-1164] Variabli 'ColumnHeaderPressedCurrentState' iu dha e njëjta vlerë. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Kode duket mjaft e çuditshme. Më duket se ka pasur një gabim ose në kusht, ose gjatë ri-shpërndarjes së variablit ColumnHeaderPressedCurrentState kuptime false.
Përfundimi
Si e shohim, integrohet analizatori statik PVS-Studio në projektin tuaj në TeamCity mjaft lehtë. Mjafton të shkruani një skedar të vogël konfigurimi. Kontrollimi i kodit do të ndihmojë në identifikimin e problemeve menjëherë pas ndërtimit, duke ndihmuar në rregullimin e tyre kur kompleksiteti dhe kostoja e korrigjimeve janë ende të ulëta.
Nëse dëshironi të ndani këtë artikull me audiencën anglisht-folëse, ju lutem përdorni lidhjen për përkthimin: Vladislav Stolyarov. .
Burimi: habr.com
