
Jedním z nejaktuálnějších scénářů použití analyzátoru PVS-Studio je jeho integrace s CI systémy. Ačkoli je analýzu projektu pomocí PVS-Studio možné téměř v jakémkoli systému kontinuální integrace integrovat jen několika příkazy, stále se snažíme tento proces ještě více ulehčit. V PVS-Studio byla přidána podpora pro převod výstupu analyzátoru do formátu pro TeamCity — TeamCity Inspections Type. Podívejme se, jak to funguje.
Informace o používaném softwaru
— statický analyzátor C, C++, C# a Java kódu, určený k usnadnění úkolu hledání a opravování různých chyb. Analyzátor lze používat na Windows, Linux a macOS. V tomto článku se zaměříme nejen na samotný analyzátor, ale i na některé nástroje z jeho distribuce.
— je server monitorování, který sleduje spuštění překladačů. Je nutné ho spustit přímo před začátkem sestavení vašeho projektu. V režimu sledování server zachytí spuštění všech podporovaných překladačů. Je třeba zmínit, že tento nástroj lze používat pouze pro analýzu C/C++ projektů.
– nástroj pro konverzi zprávy analyzátoru do různých formátů.
Informace o analyzovaném projektu
Pojďme vyzkoušet tuto funkčnost na praktickém příkladu – analyzujeme projekt OpenRCT2.
— otevřená implementace hry RollerCoaster Tycoon 2 (RCT2), která ji rozšiřuje o nové funkce a opravuje chyby. Herní proces se točí kolem stavby a údržby zábavního parku, ve kterém se nacházejí atrakce, obchody a objekty. Hráč se musí snažit dosáhnout zisku a udržovat dobrou pověst parku, přičemž se snaží udržet hosty šťastné. OpenRCT2 umožňuje hrát jak scénáře, tak v písečné hře. Scénáře vyžadují, aby hráč splnil určitý úkol v daném čase, zatímco písečná hra umožňuje hráči stavět flexibilnější park bez jakýchkoli omezení nebo financí.
Nastavení
Abych ušetřil čas, vynechám proces instalace a začnu v okamžiku, kdy mám na počítači spuštěný server TeamCity. Musíme přejít na: localhost:{port uvedený v instalačním procesu}(v mém případě localhost:9090) a zadat přihlašovací údaje. Po přihlášení nás přivítá:

Klikneme na tlačítko Vytvořit projekt. Dále vybereme Ručně, vyplníme pole.

Po stisknutí tlačítka Vytvořit, setkáme se s oknem s nastavením.

Klikněte Vytvořit konfiguraci sestavení.

Vyplníme pole, klikneme na Vytvořit. Vidíme okno s nabídkou výběru systému správy verzí. Protože zdrojové kódy již leží v místním počítači, klikneme na Přeskočit.

Nakonec přejdeme k nastavení projektu.

Přidáme kroky sestavení, k tomu klikneme na: Kroky sestavení -> Přidat krok sestavení.

Zde vybereme:
- Typ běžce -> Příkazový řádek
- Spustit -> Vlastní skript
Protože budeme provádět analýzu během kompilace projektu, sestavení a analýza by měly být jedním krokem, vyplníme pole Vlastní skript:

Na jednotlivých krocích se zastavíme později. Je důležité, aby načítání analyzátoru, sestavení projektu, jeho analýza, generování zprávy a její formátování nezabralo více než jedenáct řádků kódu.
Poslední, co musíme udělat, je nastavit proměnné prostředí, kterými jsem označil některé cesty pro zlepšení jejich přehlednosti. K tomu přejdeme na: Parametry -> Přidat novou proměnnou a přidáme tři proměnné:

Zbývá kliknout na tlačítko Spustit v pravém horním rohu. Zatímco probíhá sestavení a analýza projektu, povím vám o skriptu.
Přímo skript
Nejprve musíme stáhnout čerstvý distribuční balíček PVS-Studio. K tomu použijeme správce balíčků Chocolatey. Pro ty, kteří se chtějí dozvědět více, je zde odpovídající :
choco install pvs-studio -yNyní spustíme nástroj pro sledování sestavení projektu CLMonitor.
%CLmon% monitor --attachPak provedeme sestavení projektu, jako proměnnou prostředí MSB je cesta k verzi MSBuild, kterou potřebuju pro sestavení
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableZadáme uživatelské jméno a klíč licence PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Po dokončení sestavení znovu spustíme CLMonitor pro generování předzpracovaných souborů a statické analýzy:
%CLmon% analyze -l "c:ptest.plog"Poté použijeme další nástroj z našeho distribučního balíčku. PlogConverter převede zprávu ze standardního do specifického formátu pro TeamCity. Díky tomu se na ní můžeme podívat přímo v okně sestavení.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Posledním krokem vyvedeme naformátovanou zprávu do stdout, kde ji zachytí parser TeamCity.
type "C:tempptest.plog_TeamCity.txt"Celý kód skriptu:
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"Mezitím byla sestavení a analýza projektu úspěšně dokončena, můžeme přejít na záložku Projekty a ujistit se o tom.

Nyní klikneme na Celkový počet kontrol, abychom přešli na zobrazení zprávy analyzátoru:

Upozornění jsou seskupena podle čísel diagnostických pravidel. Pro navigaci v kódu je třeba kliknout na číslo řádku s upozorněním. Kliknutím na otazník v pravém horním rohu se otevře nová záložka s dokumentací. Navigaci v kódu lze také procházet kliknutím na číslo řádku s upozorněním analyzátoru. Navigace z dalekého počítače je možná při použití SourceTreeRoot markeru. Ti, kteří mají zájem o tento režim práce analyzátoru, se mohou seznámit s příslušnou sekcí .
Zobrazení výsledků práce analyzátoru
Poté, co jsme dokončili nasazení a nastavení sestavení, navrhuji podívat se na některá zajímavá upozornění nalezená v prozkoumaném projektu.
Upozornění N1
[CWE-401] Výjimka byla vyhozena bez uvolnění ukazatele 'result'. Může dojít k úniku paměti. libopenrct2 ObjectFactory.cpp 443
Object* CreateObjectFromJson(....)
{
Object* result = nullptr;
....
result = CreateObject(entry);
....
if (readContext.WasError())
{
throw std::runtime_error("Objekt má chyby");
}
....
}
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("Neplatný typ objektu");
}
return result;
}Analyzátor si všiml chyby, že po dynamickém alokaci paměti v CreateObject, při vyvolání výjimky není paměť vyčištěna, což znamená, že dochází k úniku paměti.
Upozornění N2
Existují identické podvýrazy '(1ULL << WIDX_MONTH_BOX)' vlevo a vpravo od operátoru '|'. 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),
....
};Málokdo kromě statického analyzátoru by dokázal projít tento test pozornosti. Tento příklad kopírování a vkládání je dobrý právě tímto způsobem.
Varování N3
Je podivné, že pole 'flags' ve třídě 'RCT12BannerElement' přepisuje pole v základní třídě 'RCT12TileElementBase'. Zkontrolujte řádky: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};Samozřejmě, použití proměnné se stejným názvem v základní třídě a v dědici není vždy chybou. Nicméně technologie dědění předpokládá, že všechna pole základní třídy jsou v dceřiné. Pokud však v dědici deklarujeme pole se stejným názvem, vytváříme zmatení.
Varování N4
Je podivné, že výsledek výrazu 'imageDirection / 8' je součástí podmínky. Možná by tento výraz měl být porovnán s něčím jiným. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Pojďme se podívat podrobněji. Výraz imageDirection / 8 bude false v případě, že imageDirection je v rozmezí od -7 do 7. Druhá část: (imageDirection / 8) != 3 prověřuje imageDirection vzhledem k tomu, že se nachází mimo rozsah: od -31 do -24 a od 24 do 31. Působí mi to docela divně, kontrolovat čísla na vniknutí do určitého rozsahu tímto způsobem, a i když v tomto úryvku kódu není chyba, doporučoval bych přepsat tato podmínky na jasnější. To by značně usnadnilo život lidem, kteří budou číst a udržovat tento kód.
Varování N5
Podivná sekvence přiřazení tohoto druhu: A = B; B = A;. Zkontrolujte řádky: 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;
....
}
....
}Tento fragment kódu byl pravděpodobně získán dekompilací. Poté byla, jak naznačuje ponechaný komentář, odstraněna část nefunkčního kódu. Nicméně zůstala některá operace nad cursorId, která také nemá výrazný význam.
Upozornění N6
[CWE-476] Ukazatel 'player' byl použit nebezpečně poté, co byl ověřen proti nullptr. Zkontrolujte řádky: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Přidat nového hráče.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Tento kód lze poměrně jednoduše opravit, je potřeba buď potřetí zkontrolovat player na nullptr, nebo ho zahrnout do těla podmínkového příkazu. Navrhuji druhou variantu:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Přidat nového hráče.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Upozornění N7
[CWE-570] Výraz 'name == nullptr' je vždy nepravdivý. 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));
....
}
....
}Jedním krokem lze odstranit obtížně čitelný řádek kódu a vyřešit problém s kontrolou na nullptr. Navrhuji změnit kód následujícím způsobem:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = "";
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Upozornění N8
[CWE-1164] Proměnné 'ColumnHeaderPressedCurrentState' byla přiřazena stejná hodnota. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Kód vypadá docela podivně. Myslím, že došlo k překlepu buď v podmínce, nebo při opakovaném přiřazení proměnné ColumnHeaderPressedCurrentState hodnoty false.
Výstup
Jak vidíme, integrace statického analyzátoru PVS-Studio do projektu na TeamCity je docela jednoduchá. Stačí napsat jeden malý konfigurační soubor. Kontrola kódu umožní odhalit problémy ihned po sestavení, což pomůže je odstranit, když jsou obtížnost a náklady na opravy ještě nízké.
Pokud chcete sdílet tento článek s anglicky mluvícím publikem, prosím použijte odkaz na překlad: Vladislav Stolyarov. .
Zdroj: habr.com
