Jedním z nejaktuálnějších scénářů použití analyzátoru PVS-Studio je jeho integrace se systémy CI. A přestože analýzu projektu PVS-Studio z téměř jakéhokoli systému kontinuální integrace lze zabudovat do několika příkazů, i nadále je tento proces ještě pohodlnější. PVS-Studio nyní podporuje převod výstupu analyzátoru do formátu pro TeamCity – TeamCity Inspections Type. Pojďme se podívat, jak to funguje.
Informace o použitém softwaru
Informace o studovaném projektu
Pojďme si tuto funkcionalitu vyzkoušet na praktickém příkladu – pojďme si rozebrat projekt OpenRCT2.
Nastavení
Abych ušetřil čas, pravděpodobně vynechám proces instalace a začnu od okamžiku, kdy mám na počítači spuštěný server TeamCity. Musíme přejít na: localhost:{port zadaný během procesu instalace} (v mém případě localhost:9090) a zadat autorizační údaje. Po vstupu nás přivítá:
Klikněte na tlačítko Vytvořit projekt. Dále vyberte Ručně a vyplňte pole.
Po stisknutí tlačítka Vytvořit, přivítá nás okno s nastavením.
Pojďme kliknout Vytvořte konfiguraci sestavení.
Vyplňte pole a klikněte Vytvořit. Vidíme okno s výzvou k výběru systému správy verzí. Protože jsou zdroje již umístěny lokálně, klikněte na Přeskočit.
Nakonec přejdeme k nastavení projektu.
Přidáme kroky sestavení, provedete to kliknutím: Kroky sestavení -> Přidat krok sestavení.
Zde vybíráme:
- Typ běžce -> Příkazový řádek
- Spustit -> Vlastní skript
Vzhledem k tomu, že analýzu budeme provádět při sestavování projektu, montáž a analýza by měly být jedním krokem, proto pole vyplňte Vlastní skript:
Na jednotlivé kroky se podíváme později. Je důležité, že načtení analyzátoru, sestavení projektu, jeho analýza, výstup zprávy a její formátování zabere pouze jedenáct řádků kódu.
Poslední věc, kterou musíme udělat, je nastavit proměnné prostředí, kterým jsem nastínil několik způsobů, jak zlepšit jejich čitelnost. Chcete-li to provést, pojďme dále: Parametry -> Přidat nový parametr a přidejte tři proměnné:
Jediné, co musíte udělat, je stisknout tlačítko Běh v pravém horním rohu. Zatímco se projekt skládá a analyzuje, povím vám o scénáři.
Přímo skript
Nejprve si musíme stáhnout nejnovější distribuci PVS-Studio. K tomu používáme správce balíčků Chocolatey. Pro ty, kteří se o tom chtějí dozvědět více, existuje odpovídající
choco install pvs-studio -y
Dále spustíme nástroj pro sledování sestavení projektu CLMonitor.
%CLmon% monitor –-attach
Poté projekt postavíme jako proměnnou prostředí MSB je cesta k verzi MSBuild, kterou potřebuji sestavit
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable
Zadáme přihlašovací a licenční klíč pro PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%
Po dokončení sestavení spusťte znovu CLMonitor a vygenerujte předzpracované soubory a statickou analýzu:
%CLmon% analyze -l "c:ptest.plog"
Pak použijeme jinou utilitu z naší distribuce. PlogConverter převede zprávu ze standardního formátu do formátu specifického pro TeamCity. Díky tomu si jej budeme moci prohlédnout přímo v okně sestavení.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"
Posledním krokem je zobrazení naformátované sestavy stdout, kde jej vyzvedne analyzátor TeamCity.
type "C:tempptest.plog_TeamCity.txt"
Úplný 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 úspěšně dokončena montáž a analýza projektu, můžeme přejít na záložku Projekty и убедиться v эtom.
Nyní klikneme na Kontroly celkempřejděte k zobrazení zprávy analyzátoru:
Varování jsou seskupena podle čísel diagnostických pravidel. Chcete-li procházet kódem, musíte kliknout na číslo řádku s upozorněním. Kliknutím na otazník v pravém horním rohu se vám otevře nová karta s dokumentací. Kód můžete také procházet kliknutím na číslo řádku s varováním analyzátoru. Při použití je možná navigace ze vzdáleného počítače SourceTreeRoot popisovač. Každý, kdo má zájem o tento způsob provozu analyzátoru, se může seznámit s odpovídající částí
Zobrazení výsledků analyzátoru
Nyní, když jsme dokončili nasazení a konfiguraci sestavení, pojďme se podívat na některá zajímavá varování nalezená v projektu, na který se díváme.
Varování N1
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;
}
Analyzátor zaznamenal chybu, která po dynamickém přidělování paměti v CreateObject, když dojde k výjimce, paměť se nevymaže a dojde k nevracení paměti.
Varování N2
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),
....
};
Jen málo lidí kromě statického analyzátoru by mohlo projít tímto testem pozornosti. Tento příklad kopírování a vkládání je dobrý právě z tohoto důvodu.
Varování N3
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};
Samozřejmě, že použití proměnné se stejným názvem v základní třídě a v potomkovi není vždy chyba. Samotná technologie dědičnosti však předpokládá, že všechna pole nadřazené třídy jsou přítomna v podřízené třídě. Prohlášením polí se stejným názvem v dědici vnášíme zmatek.
Varování N4
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}
Pojďme se na to blíže podívat. Výraz imageDirection/8 bude nepravdivé, pokud imageDirection je v rozsahu od -7 do 7. Druhá část: (imageDirection / 8) != 3 kontroly imageDirection mimo rozsah: od -31 do -24 a od 24 do 31, v tomto pořadí. Zdá se mi docela zvláštní kontrolovat čísla pro zahrnutí v určitém rozsahu tímto způsobem, a i když v tomto kusu kódu není žádná chyba, doporučil bych přepsat tyto podmínky, aby byly jasnější. To by výrazně usnadnilo život lidem, kteří by tento kód četli a udržovali ho.
Varování N5
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 s největší pravděpodobností získán dekompilací. Poté, soudě podle zanechaného komentáře, byla část nefunkčního kódu odstraněna. Zbývá však ještě několik operací id kurzoru, což také nedává moc smysl.
Varování N6
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Add new player.
player = AddPlayer("", "");
if (player) // <=
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}
Tento kód je poměrně snadné opravit, stačí jej zkontrolovat potřetí hráč na nulový ukazatel nebo jej přidejte do těla podmíněného příkazu. Navrhoval bych druhou možnost:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Add new player.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}
Varování N7
std::optional<ServerListEntry> 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 tahem se můžete zbavit těžko čitelného řádku kódu a vyřešit problém s kontrolou nullptr. Navrhuji změnit kód následovně:
std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = ""
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}
Varování N8
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}
Kód vypadá dost divně. Zdá se mi, že došlo k překlepu buď v podmínce, nebo při opětovném přiřazení proměnné ColumnHeaderPressedCurrentState hodnoty nepravdivý.
Výkon
Jak vidíme, integrace statického analyzátoru PVS-Studio do vašeho projektu TeamCity je poměrně jednoduchá. K tomu stačí napsat pouze jeden malý konfigurační soubor. Kontrola kódu vám umožní identifikovat problémy ihned po sestavení, což je pomůže odstranit, když jsou složitost a náklady na změny stále nízké.
Pokud chcete tento článek sdílet s anglicky mluvícím publikem, použijte prosím odkaz na překlad: Vladislav Stolyarov.
Zdroj: www.habr.com