
Ena iz najbolj relevantnih scenarijev uporabe analizerja PVS-Studio je njegova integracija s CI sistemi. Čeprav je analizo projekta PVS-Studio mogoče integrirati v katerikoli continuous integration sistem z le nekaj ukazi, ta proces še naprej olajšujemo. V PVS-Studio je dodana podpora za pretvorbo izhoda analizerja v format za TeamCity — TeamCity Inspections Type. Oglejmo si, kako to deluje.
Informacije o uporabljeni programski opremi
— statični analizer C, C++, C# in Java kode, namenjen olajšanju iskanja in odpravljanja različnih napak. Analizer se lahko uporablja v Windows, Linux in macOS. V tem članku bomo aktivno uporabljali ne samo sam analizer, temveč tudi nekatere pripomočke iz njegove distribucije.
— predstavlja strežnik za monitoring, ki spremlja zagonih prevajalnikov. Zagnati ga je treba neposredno pred začetkom sestavljanja vašega projekta. V načinu spremljanja bo strežnik zajemal zagone vseh podprtih prevajalnikov. Omeniti velja, da se tega pripomočka lahko uporablja samo za analizo C/C++ projektov.
– pripomoček za pretvorbo poročila analizerja v različne formate.
Informacije o raziskovanem projektu
Poglejmo to funkcionalnost na praktičnem primeru – analizirajmo projekt OpenRCT2.
— odprta implementacija igre RollerCoaster Tycoon 2 (RCT2), ki razširja igro z novimi funkcijami in odpravlja napake. Igralni postopek se vrti okoli gradnje in vzdrževanja zabaviščnega parka, v katerem so atrakcije, trgovine in objekti. Igralec mora poskrbeti, da bo park uspešen in ohranjati dobro reputacijo, hkrati pa ohranjati goste srečne. OpenRCT2 omogoča igranje tako v scenarijih kot v peskovniku. Scenariji zahtevajo, da igralec opravi določeno nalogo v določenem času, medtem ko peskovnik omogoča igralcu, da gradi bolj prilagodljiv park brez kakršnih koli omejitev ali financ.
Setup
Da bi prihranil čas, bom verjetno preskočil postopek namestitve in začel s trenutkom, ko imam na računalniku zagnan strežnik TeamCity. Moramo preiti na: localhost:{naveden v postopku namestitve port}(v mojem primeru, localhost:9090) in vpisati prijavne podatke. Po prijavi nas bo sprejel:

Pritisnite gumb Create Project. Zatem izberite Manually in izpolnite polja.

Po pritisku na gumb Stworzyć, nas pozdravi okno z nastavitvami.

Pritisnite Ustvari konfiguracijo gradnje.

Izpolnimo polja, pritisnemo Stworzyć. Vidimo okno z možnostjo izbire sistema za nadzor različic. Ker so izvorne datoteke že lokalno shranjene, pritisnemo Skip.

Nazadnje, preidemo na nastavitve projekta.

Dodajmo korake gradnje, za to pritisnemo: Koraki gradnje -> Dodaj korak gradnje.

Tukaj izberemo:
- Tip izvajalca -> Ukazna vrstica
- Izvedi -> Po meri skript
Ker bomo izvajali analizo med kompilacijo projekta, morata biti gradnja in analiza en korak, zato izpolnimo polje Po meri skript:

O ločenih korakih se bomo ustavili kasneje. Pomembno je, da nalaganje analizatorja, gradnja projekta, njegova analiza, izhod poročila in njegovo oblikovanje omogoča le enajst vrstic kode.
Nazadnje, kar moramo narediti, je nastaviti okoljske spremenljivke, s katerimi sem opredelil nekatere poti za izboljšanje njihove berljivosti. Zato pojdimo na: Parametri -> Dodaj novo spremenljivko in dodajmo tri spremenljivke:

Ostane še pritisniti gumb Zaženi v zgornjem desnem kotu. Medtem ko poteka gradnja in analiza projekta, vam bom predstavil skript.
Skript
Najprej moramo prenesti svežo različico PVS-Studio. Za to uporabimo upravljalnik paketov Chocolatey. Tisti, ki bi želeli izvedeti več o tem, imajo ustrezno :
choco install pvs-studio -yNato zaženemo orodje za sledenje gradnji projekta CLMonitor.
%CLmon% monitor –-attachNato bomo izvedli gradnjo projekta, kot spremenljivka okolja MSB predstavlja pot do različice MSBuild, ki jo potrebujem za gradnjo
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableVnesimo uporabniško ime in licenčni ključ PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Po končanju gradnje ponovno zaženemo CLMonitor za generiranje predobdelanih datotek in izvedbo statične analize:
%CLmon% analyze -l "c:ptest.plog"Nato bomo uporabili še en pripomoček iz našega distribucijskega paketa. PlogConverter pretvori poročilo iz standardnega v obliko, specifično za TeamCity. Tako bomo lahko pogledali neposredno v oknu gradnje.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Kot zadnji korak bomo izpisali oblikovano poročilo v stdout, kjer ga bo pobral parser TeamCity.
type "C:tempptest.plog_TeamCity.txt"Celoten kod skripta:
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"В то же время, сборка и анализ проекта успешно завершились, мы можем перейти на вкладку Projekti и убедиться в этом.

Теперь кликнем на Итоговые инспекции, чтобы перейти к просмотру отчета анализатора:

Предупреждения сгруппированы по номерам диагностических правил. Для навигации по коду нужно кликнуть на номер строки с предупреждением. Нажатие на знак вопроса в правом верхнем углу откроет новую вкладку с документацией. Также можно осуществить навигацию по коду, кликнув на номер строки с предупреждением анализатора. Навигация с удаленного компьютера возможна при использовании SourceTreeRoot маркера. Тот, кого интересует данный режим работы анализатора, может ознакомиться с соответствующим разделом .
Просмотр результатов работы анализатора
После того, как мы закончили с развертыванием и настройкой сборки, предлагаю посмотреть на некоторые интересные предупреждения, обнаруженные в исследуемом проекте.
Предупреждение N1
[CWE-401] Исключение было выброшено без освобождения указателя 'result'. Возможна утечка памяти. libopenrct2 ObjectFactory.cpp 443
Object* CreateObjectFromJson(....)
{
Object* result = nullptr;
....
result = CreateObject(entry);
....
if (readContext.WasError())
{
throw std::runtime_error("Объект содержит ошибки");
}
....
}
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("Недопустимый тип объекта");
}
return result;
}Анализатор заметил ошибку, заключающуюся в том, что после динамического выделения памяти в CreateObject, при возникновении исключения память не очищается, соответственно, возникает утечка памяти.
Предупреждение N2
Есть идентичные подвыражения '(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 kdo, razen statičnega analizatorja, bi opravil ta test pozornosti. Ta primer kopipejsta je dober prav zaradi tega.
Opozorila N3
Nenavadno je, da polje 'flags' v podrazredu 'RCT12BannerElement' prepisuje polje v osnovnem razredu 'RCT12TileElementBase'. Preverite vrstice: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};Seveda, uporaba spremenljivke z istim imenom v osnovnem razredu in v podrazredu ni nujno napaka. Vendar pa sama tehnologija dedovanja predpostavlja prisotnost vseh polj osnovnega razreda v podrazredu. Če v podrazredu razglasimo polja z enakim imenom, vnašamo zmedo.
Opozorilo N4
Nenavadno je, da je rezultat izjave 'imageDirection / 8' del pogoja. Morda bi bilo to izjavo treba primerjati z nečim drugim. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Poglejmo malce podrobneje. Izraz imageDirection / 8 bo false v primeru, če imageDirection je v razponu od -7 do 7. Drugi del: (imageDirection / 8) != 3 preverja imageDirection da je zunaj razpona: od -31 do -24 in od 24 do 31. Zdi se mi precej nenavadno preverjati števila na vstop v določen razpon na ta način in, tudi če v tem delu kode ni napake, bi priporočal, da te pogoje prepišete na bolj jasne. To bi bistveno olajšalo življenje ljudem, ki bodo brali in vzdrževali to kodo.
Opozorilo N5
Nenavadna zaporedja dodelitev te vrste: A = B; B = A;. Preverite vrstice: 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;
....
}
....
}Ta koda, ki jo smo dobili, temelji na dekompilaciji. Glede na preostali komentar je bil odstranjen del nefunkcionalne kode. Vendar pa je ostalo nekaj operacij nad cursorId, ki prav tako nimajo posebnega smisla.
Opozorilo N6
[CWE-476] Kazalec 'player' je bil po preverjanju proti nullptr uporabljen na nevaren način. Preverite vrstice: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Dodaj novega igralca.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Ta koda je precej enostavna za popraviti, potrebno je ali tretjič preveriti player za ničelni kazalec, ali ga vključiti v telo pogojnega stavka. Predlagam drugo možnost:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Dodaj novega igralca.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Opozorilo N7
[CWE-570] Izraz 'name == nullptr' je vedno napačen. 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));
....
}
....
}Z enim potezom lahko odpravimo nerazumljivo vrstico kode in rešimo težavo s preverjanjem na nullptr. Predlagam, da spremenimo kodo na naslednji 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);
....
}
....
}Opozorilo N8
[CWE-1164] Spremenljivka 'ColumnHeaderPressedCurrentState' je bila dodeljena isti vrednosti. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Koda se zdi precej nenavadna. Zdi se mi, da je prišlo do tipkarskega napaka bodisi v pogoju bodisi pri ponovnem dodeljevanju spremenljivke ColumnHeaderPressedCurrentState pomeni false.
Izlaz
Kot vidimo, je integracija statičnega analizatorja PVS-Studio v projekt na TeamCity precej preprosta. Potrebno je le napisati en majhen konfiguracijski datoteko. Pregled kode omogoča odkrivanje težav takoj po gradnji, kar pomaga pri njihovem odpravljanju, ko so težave še majhne in stroški popravila nizki.
Če želite to besedilo deliti z angleško govorečo publiko, vas prosim, da uporabite povezavo na prevod: Vladislav Stolyarov. .
Izvor: habr.com
