
Üks kõige asjakohasemaid PVS-Studio analüsaatori kasutusstsenaariume on selle integreerimine CI süsteemidega. Kuigi PVS-Studio projektianalüüsi saab peaaegu igasuguste pideva integratsiooni süsteemidega integreerida vaid paarikümne käsuga, jätkame selle protsessi mugavamaks muutmist. PVS-Studios on lisatud tugi analüsaatori väljundi konverteerimiseks TeamCity vormingusse — TeamCity Inspections Type. Vaadakem, kuidas see töötab.
Teave kasutatava tarkvara kohta
— staatiline C, C++, C# ja Java koodi analüsaator, mis on mõeldud erinevate vigade leidmise ja parandamise ülesande lihtsustamiseks. Analüsaatorit saab kasutada Windowsis, Linuxis ja macOSis. Selles artiklis kasutame aktiivselt mitte ainult analüsaatorit, vaid ka mõningaid tööriistu selle jaotises.
— on jälgimise server, mis jälgib kompilaatorite käivitusi. Seda tuleb käivitada otse enne oma projekti koostamise alustamist. Jälgimisrežiimis püüab server kinni kõigi toetatud kompilaatorite käivitused. Oluline on märkida, et seda utiliiti saab kasutada ainult C/C++ projektide analüüsimiseks.
– on utiliit analüsaatori aruande konverteerimiseks erinevatesse formaatidesse.
Teave uuritava projekti kohta
Katsume seda funktsionaalsust praktilise näite põhjal - analüüsime OpenRCT2 projekti.
— on RollerCoaster Tycoon 2 (RCT2) mängu avatud rakendus, mis laiendab seda uute funktsioonide ja vigade parandamisega. Mängu protsess keerleb lõbustuspargi ehitamise ja hooldamise ümber, kus asuvad atraktsioonid, poed ja objektiid. Mängija peab püüdma teenida kasumit ja säilitama pargi head mainet, hoides seejuures külastajad õnnelikena. OpenRCT2 võimaldab mängida nii stsenaariumides kui ka liivakastis. Stsenaariumid nõuavad, et mängija täidaks teatud ülesande määratud aja jooksul, samas kui liivakast võimaldab mängijal luua paindlikuma pargi ilma igasuguste piirangute või rahaga seotud küsimusteta.
Seadistamine
Aja kokkuhoiu huvides jätan ma tõenäoliselt vahele installimisprotsessi ja alustan hetkest, mil mul on arvutis käivitatud TeamCity server. Peame minema: localhost:{installimisprotsessis määratud port}(minu puhul localhost:9090) ja sisestama sisselogimise andmed. Pärast sisselogimist kohtume järgmisega:

Klõpsame nuppu Create Project. Järgmine, valime Manually ja täidame väljad.

Pärast nupu vajutamist Loo, meid tervitab seadistuste aken.

Klõpsame Create build configuration.

Täidame väljad, klõpsame Loo. Me näeme akent, kus on versioonihaldussüsteemi valiku ettepanek. Kuna lähtekoodid asuvad juba kohalikult, vajutame Skip.

Lõpuks liigume projekti seadistuste juurde.

Lisame ehitusetapid, selleks klõpsame: Build steps -> Add build step.

Siit valime:
- Runner type -> Command Line
- Run -> Custom Script
Kuna me teeme analüüsi projekti kompileerimise ajal, peavad ehitamine ja analüüs olema ühes etapis, seega täidame väljaku Custom Script:

Loodud etappidel peatume hiljem. Oluline on, et analüsaatori laadimine, projekti ehitamine, selle analüüs, aruande genereerimine ja vormindamine võtaks kokku ainult üheksa rida koodi.
Viimane asi, mida peame tegema, on seadistada keskkonnamuutujad, mille kaudu olen mõned teed parandanud, et need oleksid paremini loetavad. Selleks liigume: Parameters -> Add new parameter ja lisame kolm muutuja:

Jääb vaid vajutada nuppu Käita paremas ülanurgas. Kui projekti ehitamine ja analüüs käib, räägin teile skriptist.
Otse skript
Esialgu peame alla laadima värske PVS-Studio distributsiooni. Selleks kasutame pakihaldurit Ch chocolatey. Neile, kes soovivad rohkem teada saada, on olemas vastav :
choco install pvs-studio -yJärgmisena käivitame projekti ehitamise jälgimise utiliidi CLMonitor.
%CLmon% monitor --attachSiis viime projekti ehitamiseni, keskkonnamuutujana MSB on tee vajaliku MSBuildi versiooni jaoks
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableSisestame PVS-Studio kasutajanime ja litsentsi võtme:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Peale ehitamise lõppu käivitame taas CLMonitori, et genereerida eelprotsessitud faile ja staatilist analüüsi:
%CLmon% analyze -l "c:ptest.plog"Seejärel kasutame veel ühte utiliiti meie distributsioonist. PlogConverter muudab aruande standardse vormi TeamCity vormiks. Tänu sellele saame seda vaadata otse ehitamise aknas.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Viimase sammuna väljastame vormindatud aruande stdout, kus TeamCity parser selle haarab.
type "C:tempptest.plog_TeamCity.txt"Skripti täielik kood:
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"Vahepeal, projekti kogumine ja analüüs on edukalt lõpule viidud, saame minna vahekaardile Projects ja veenduda, et see nii on.

Nüüd klõpsame Inspections Total, et minna üle analüsaatori aruande vaatamisele:

Hoiatused on grupeeritud diagnostiliste reeglite numbrite kaupa. Koodi navigeerimiseks tuleb klõpsata hoiatuses oleva rida numbril. Paremas ülanurgas asuvale küsimärgi ikoonile klõpsamine avab uue vahekaardi dokumentatsiooniga. Samuti on võimalik koodis navigeerida, klõpsates analüsaatori hoiatuses oleva rida numbril. Eemalt navigeerimine on võimalik, kasutades SourceTreeRoot markerit. Need, kes on huvitatud analüsaatori töörežiimist, saavad tutvuda vastava osaga .
Analüsaatori töö tulemuste vaatamine
Pärast sellest ülesande ja seadistamise lõpetamist, soovitan vaadata mõningaid huvitavaid hoiatusi, mis leiti uuritavast projektist.
Hoiatus N1
[CWE-401] Erand visati ilma 'result' pointerit vabastamata. Võimalik mäluleke. 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;
}Analüsaator märkis vea, mille kohaselt pärast dünaamilist mälu eraldamist CreateObject, erandite tekkimisel mälu ei vabastata, mis omakorda viib mälu lekkimiseni.
Hoiatus N2
On olemas identsed alamekspressioonid '(1ULL << WIDX_MONTH_BOX)' vasakul ja paremal '|' operaatorist. 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),
....
};Vähegsed on vaid staatilised analüsaatorid, kes suudavad selle tähelepanutesti läbi teha. See näidis kopeerimisest ongi sellepärast hea.
Hoiatus N3
On kummaline, et „flags” väli tuletatud klassis „RCT12BannerElement” ületab väljade klassis „RCT12TileElementBase”. Kontrollige ridu: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};Muidugi ei ole üks ja sama nimega muutuja kasutamine aluseks ja tuletatud klassis alati viga. Siiski eeldab pärimise tehnoloogia, et tuletatud klassis on kõik aluse klassi väljad. Kui kuulutame järglas klassis väljad sama nimega, põhjustame segadust.
Hoiatus N4
On kummaline, et 'imageDirection / 8' avaldise tulemus on osa tingimusest. Võib-olla peaks see avaldis olema võrreldud millegi muuga. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Vaatame seda lähemalt. Avaldise imageDirection / 8 on vale, kui imageDirection on vahemikus -7 kuni 7. Teine osa: (imageDirection / 8) != 3 kontrollib imageDirection vahemikust väljajäämist: -31 kuni -24 ja 24 kuni 31 vastavalt. Tundub väga kummaline kontrollida numbreid mingisse kindlasse vahemikku nii, ja isegi kui selle koodilõigu kontekstis pole viga, soovitaksin neid tingimusi ümber kirjutada selgemaks. See lihtsustaks oluliselt elu inimestele, kes seda koodi loevad ja toetavad.
Hoiatus N5
Kummaline määramissegmenteerimise järjestus: A = B; B = A;. Kontrollige ridu: 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;
....
}
....
}See koodifragmendi on tõenäoliselt saadud dekompileerimise teel. Järeldub, et kustutati osa toimimatust koodist. Siiski on alles mõned operatsioonid üle cursorId, mis samuti ei sisalda erilist tähendust.
Hoiatus N6
[CWE-476] Näidatakse, et 'player' pointerit kasutati ohtlikult pärast nulli kontrollimist. Kontrollige ridu: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Lisa uus mängija.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Seda koodi on üsna lihtne parandada, tuleb kas kolmandat korda kontrollida player nulli väärtuse suhtes või tuua see tingimuse keha sisse. Soovitaksin teist varianti:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Lisa uus mängija.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Hoiatus N7
[CWE-570] Väljend 'name == nullptr' on alati vale. 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));
....
}
....
}Ühe käega saab vaevalt loetavast koodireast lahti ja lahendada probleemi kontrollimise osas nullptr. Soovitaksin muuta koodi järgmiselt:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = "";
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Hoiatus N8
[CWE-1164] Muutujale 'ColumnHeaderPressedCurrentState' omistati sama väärtus. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Kood näeb välja üsna veider. Mulle tundub, et tingimuses või muutuja taaskirjutamisel oli trükiviga ColumnHeaderPressedCurrentState mõisted false.
Kokkuvõte
Kuidas me näeme, PVS-Studio staatilise analüsaatori integreerimine oma projekti TeamCity'sse on suhteliselt lihtne. Selleks piisab, kui kirjutada vaid üks väike konfiguratsioonifail. Koodi kontrollimine võimaldab tuvastada probleemid kohe pärast kompileerimist, mis aitab neid kõrvaldada, kui parandamise keerukus ja maksumus on veel madalad.
Kui soovite seda artiklit ingliskeelse publikuga jagada, siis palun kasutage tõlke linki: Vladislav Stolyarov. .
Allikas: habr.com
