
En av de mest aktuella anvĂ€ndningsscenarierna för PVS-Studio-analyzatorn Ă€r dess integration med CI-system. Ăven om analysen av projektet med PVS-Studio kan integreras i nĂ€stan vilket som helst continuous integration-system med bara nĂ„gra fĂ„ kommandon, fortsĂ€tter vi att göra denna process Ă€nnu bekvĂ€mare. PVS-Studio har fĂ„tt stöd för att konvertera analyzatorns output till TeamCity-format â TeamCity Inspections Type. LĂ„t oss titta pĂ„ hur detta fungerar.
Information om den anvÀnda programvaran
â en statisk analysator för C, C++, C# och Java-kod, avsedd för att underlĂ€tta sökandet och Ă„tgĂ€rdandet av olika typer av fel. Analyzatorn kan anvĂ€ndas i Windows, Linux och macOS. I denna artikel kommer vi att anvĂ€nda inte bara sjĂ€lva analyzatorn utan Ă€ven vissa verktyg frĂ„n dess distribution.
â Ă€r en övervakningsserver som spĂ„rar körningar av kompilatorer. Den mĂ„ste startas direkt innan byggandet av ditt projekt inleds. I övervakningslĂ€ge kommer servern att fĂ„nga upp körningar av alla stödda kompilatorer. Observera att detta verktyg endast kan anvĂ€ndas för analys av C/C++-projekt.
â ett verktyg för att konvertera analyzatorns rapport till olika format.
Information om det undersökta projektet
LĂ„t oss prova denna funktionalitet med ett praktiskt exempel â vi ska analysera projektet OpenRCT2.
â en öppen implementation av spelet RollerCoaster Tycoon 2 (RCT2), som utökar det med nya funktioner och Ă„tgĂ€rdar fel. Spelmekaniken kretsar kring byggandet och skötseln av en nöjespark dĂ€r det finns attraktioner, butiker och objekt. Spelaren mĂ„ste strĂ€va efter att göra en vinst och upprĂ€tthĂ„lla ett gott rykte för parken, samtidigt som gĂ€sterna hĂ„lls nöjda. OpenRCT2 tillĂ„ter att spela antingen i scenarielĂ€get eller i sandlĂ„dan. Scenarier krĂ€ver att spelaren fullföljer en viss uppgift inom en angiven tidsram, medan sandlĂ„dan tillĂ„ter spelaren att bygga en mer flexibel park utan nĂ„gra begrĂ€nsningar eller ekonomiska hinder.
InstÀllning
För att spara tid kommer jag att hoppa över installationsprocessen och börja frÄn det ögonblicket dÄ servern TeamCity Àr igÄng pÄ min dator. Vi behöver gÄ till: localhost:{angett under installationsprocessen port}(i mitt fall, localhost:9090) och ange autentiseringsuppgifter. Efter inloggning möts vi av:

Klicka pÄ knappen Skapa projekt. DÀrefter vÀljer vi Manuellt och fyller i fÀlten.

Efter att ha tryckt pÄ knappen Skapa, möts vi av ett fönster med instÀllningar.

Klicka Skapa byggkonfiguration.

Fyll i fÀlten, klicka pÄ Skapa. Vi ser ett fönster med förslag pÄ versionskontrollsystem. Eftersom kÀllkoden redan finns lokalt, klicka pÄ Hoppa över.

Till sist gÄr vi vidare till projektsinstÀllningarna.

LÀgg till byggelement, för detta klicka pÄ: Byggelement -> LÀgg till byggelement.

HÀr vÀljer vi:
- Körartyp -> Kommando Rad
- Kör -> Anpassad skript
Eftersom vi kommer att genomföra analysen under projektkompileringen, bör bygg och analys vara ett steg, sÄ vi fyller i fÀltet Anpassad skript:

Vi kommer att stanna vid separata steg senare. Det Àr viktigt att laddningen av analysatorn, byggandet av projektet, dess analys, rapportgenerering och formatering tar totalt elva rader kod.
Sista steget Àr att stÀlla in miljövariabler, för vilka jag har angett vissa vÀgar för att förbÀttra deras lÀsbarhet. För detta gÄr vi till: Parametrar -> LÀgg till ny parameter och lÀgger till tre variabler:

Det ÄterstÄr bara att klicka pÄ knappen Run i det övre högra hörnet. Medan byggandet och analysen av projektet pÄgÄr, ska jag berÀtta om skriptet.
SjÀlva skriptet
För att börja mÄste vi ladda ner den senaste versionen av PVS-Studio. För detta anvÀnder vi paketchefen Chocolatey. För dem som vill veta mer finns det motsvarande :
choco install pvs-studio -yDÀrefter startar vi verktyget för att spÄra byggprojektet CLMonitor.
%CLmon% monitor â-attachSedan kommer vi att bygga projektet, som miljövariabeln MSB representerar vĂ€gen till rĂ€tt version av MSBuild som jag behöver för att bygga
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableVi anger anvÀndarnamn och licensnyckel för PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Efter bygget kör vi CLMonitor igen för att generera preprocessade filer och statisk analys:
%CLmon% analyze -l "c:ptest.plog"EfterÄt anvÀnder vi Ànnu ett verktyg frÄn vÄrt distributionspaket. PlogConverter konverterar rapporten frÄn standardformat till TeamCity-specifikt format. Tack vare detta kan vi titta pÄ den direkt i byggfönstret.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Som sista ÄtgÀrd skriver vi ut den formaterade rapporten i stdout, dÀr den plockas upp av TeamCity-parsern.
type "C:tempptest.plog_TeamCity.txt"FullstÀndig kod av skriptet:
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"Under tiden har byggandet och analysen av projektet slutförts framgÄngsrikt, sÄ vi kan gÄ till fliken Projekt och bekrÀfta detta.

LÄt oss nu klicka pÄ Inspections Total, för att gÄ till analysrapporten:

Varningar Àr grupperade efter diagnostiska regelnummer. För att navigera i koden, klicka pÄ radnumret med varningen. Att klicka pÄ frÄgetecknet i det övre högra hörnet öppnar en ny flik med dokumentation. Du kan ocksÄ navigera i koden genom att klicka pÄ radnumret med analysatorns varning. Navigering frÄn en fjÀrrdator Àr möjlig genom att anvÀnda SourceTreeRoot markören. De som Àr intresserade av denna analysatormodus kan lÀsa avsnittet om .
Visa analysresultat
Efter att ha slutfört distributionen och konfigurationen av bygget tÀnker jag att vi kan titta pÄ nÄgra intressanta varningar som upptÀcktes i det granskade projektet.
Varning N1
[CWE-401] Undantaget kastades utan att frigöra 'result'-pekaren. En minneslÀcka kan vara möjlig. libopenrct2 ObjectFactory.cpp 443
Object* CreateObjectFromJson(....)
{
Object* result = nullptr;
....
result = CreateObject(entry);
....
if (readContext.WasError())
{
throw std::runtime_error("Objektet har fel");
}
....
}
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("Ogiltig objekttyp");
}
return result;
}Analysatorn noterade ett fel, nÀmligen att efter dynamisk minnesallokering i CreateObject, vid uppkomsten av ett undantag stÀdas inte minnet, vilket resulterar i en minneslÀcka.
Varning N2
Det finns identiska sub-uttryck '(1ULL << WIDX_MONTH_BOX)' till vÀnster och till höger om '|' operatorn. 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),
....
};Det Àr fÄ som, förutom en statisk analysator, skulle klara detta uppmÀrksamhetstest. Detta exempel pÄ kopiering och klisjéer Àr bra just dÀrför.
Varning N3
Det Àr mÀrkligt att fÀltet 'flags' i den hÀrledda klassen 'RCT12BannerElement' överskrider fÀltet i basklassen 'RCT12TileElementBase'. Kontrollera rader: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};SjÀlvklart Àr det inte alltid ett misstag att anvÀnda en variabel med samma namn i basklassen och den hÀrledda klassen. Men arvsteknologi förutsÀtter att alla fÀlt frÄn basklassen ocksÄ finns i den hÀrledda klassen. Genom att deklarera fÀlt med samma namn i den hÀrledda klassen skapar vi förvirring.
Varning N4
Det Àr mÀrkligt att resultatet av uttalandet 'imageDirection / 8' ingÄr som en del av villkoret. Kanske borde detta uttalande ha jÀmförts med nÄgot annat. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}LÄt oss gÄ igenom det mer noggrant. Uttrycket imageDirection / 8 kommer att vara false om imageDirection ligger inom intervallet -7 till 7. Andra delen: (imageDirection / 8) != 3 kontrollerar imageDirection om det ligger utanför intervallet: frÄn -31 till -24 och frÄn 24 till 31. Jag tycker att det Àr ganska mÀrkligt att kontrollera tal för att se om de ligger inom ett visst intervall pÄ det hÀr sÀttet och Àven om det inte finns nÄgot fel i denna kodfragment skulle jag rekommendera att omskriva dessa villkor till mer tydliga former. Det skulle avsevÀrt förenkla livet för dem som lÀser och underhÄller denna kod.
Varning N5
En mÀrklig sekvens av tilldelningar av den hÀr typen: A = B; B = A;. Kontrollera rader: 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;
....
}
....
}Denna kodsnutt har troligtvis erhÄllits genom dekompilering. UtifrÄn den kvarlÀmnade kommentaren verkar det som att en del av den icke-fungerande koden har tagits bort. Det finns dock nÄgra operationer kvar som cursorId, vilka inte heller bÀr nÄgon sÀrskild mening.
Varning N6
[CWE-476] Pekaren 'player' anvÀndes osÀkert efter att den kontrollerades mot nullptr. Kontrollera rader: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// LĂ€gg till ny spelare.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Denna kod Àr ganska enkel att rÀtta till, antingen genom att kontrollera player mot nullpekare för tredje gÄngen, eller genom att inkludera det i kroppen av villkorssatsen. Jag skulle föreslÄ det andra alternativet:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// LĂ€gg till ny spelare.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Varning N7
[CWE-570] Uttrycket 'name == nullptr' Àr alltid falskt. 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));
....
}
....
}Man kan pÄ en gÄng bli av med den svÄrlÀsta kodraden och lösa problemet med kontrollen pÄ nullptr. Jag föreslÄr att koden Àndras pÄ följande sÀtt:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = ""
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Varning N8
[CWE-1164] Variabeln 'ColumnHeaderPressedCurrentState' tilldelades samma vÀrde. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Koden verkar ganska konstig. Jag tror att det kan ha varit en skrivfel antingen i villkoret eller vid den upprepade tilldelningen av variabeln ColumnHeaderPressedCurrentState vÀrden false.
Utdata
Som vi ser, Àr det ganska enkelt att integrera PVS-Studio statisk analysator i sitt projekt pÄ TeamCity. För detta rÀcker det att skriva en liten konfigurationsfil. Koden kommer att kontrolleras för att identifiera problem direkt efter byggprocessen, vilket hjÀlper till att ÄtgÀrda dem nÀr komplexiteten och kostnaden för Àndringar fortfarande Àr lÄg.
Om du vill dela denna artikel med en engelsktalande publik, vÀnligen anvÀnd lÀnken till översÀttningen: Vladislav Stolyarov. .
KĂ€lla: habr.com
