
En af de mest relevante anvendelsesscenarier for PVS-Studio-analyserne er deres integration med CI-systemer. Selvom analysen af et projekt med PVS-Studio næsten kan integreres fra enhver continuous integration-system med blot et par kommandoer, arbejder vi fortsat på at gøre denne proces endnu mere bekvem. PVS-Studio har fået støtte til at konvertere analysens output til et format til TeamCity — TeamCity Inspections Type. Lad os se, hvordan det fungerer.
Information om den anvendte software
— en statisk analyser af C, C++, C# og Java kode, der er designet til at lette opgaven med at finde og rette forskellige typer fejl. Analyset kan bruges på Windows, Linux og macOS. I denne artikel vil vi aktivt bruge ikke kun analysen, men også nogle værktøjer fra dens distribution.
— er en monitor server, der overvåger kørsel af compilere. Den skal startes direkte før begyndelsen af byggeriet af dit projekt. I overvågningsmodus vil serveren opfange kørsel af alle understøttede compilere. Det er værd at bemærke, at dette værktøj kun kan bruges til analyse af C/C++ projekter.
– et værktøj til konvertering af analyserapporten til forskellige formater.
Information om det undersøgte projekt
Lad os prøve denne funktionalitet med et praktisk eksempel – lad os analysere projektet OpenRCT2.
— en open source-implementering af spillet RollerCoaster Tycoon 2 (RCT2), der udvider det med nye funktioner og retter fejl. Spiloplevelsen drejer sig om at bygge og drive en forlystelsespark, hvor der findes forlystelser, butikker og bygninger. Spilleren skal forsøge at opnå profit og opretholde et godt omdømme for parken, mens gæsterne holdes glade. OpenRCT2 gør det muligt at spille både i scenarier og i sandkasse. Scenarier kræver, at spilleren udfører en bestemt opgave inden for en given tidsramme, mens sandkassen giver spilleren mulighed for at bygge en mere fleksibel park uden nogen begrænsninger eller økonomiske restriktioner.
Indstillinger
For at spare tid vil jeg springe installationsprocessen over og starte fra det punkt, hvor jeg har serveren TeamCity kørende på min computer. Vi skal gå til: localhost:{den angivne installationsport}(i mit tilfælde, localhost:9090) og indtaste autentificeringsoplysningerne. Efter indlogning vil vi blive mødt af:

Klik på knappen Opret projekt. Vælg derefter Manuel, og udfyld felterne.

Efter at have klikket på knappen Opret, møder vi et vindue med indstillinger.

Let’s press Opret byggespecifikation.

Udfyld felterne, klik på Opret. Vi ser et vindue med mulighed for valg af versionskontrolsystem. Da kilderne allerede ligger lokalt, klikker vi på Spring over.

Endelig går vi til projektindstillingerne.

Lad os tilføje byggetrin, klik på: Byggetrin -> Tilføj byggetrin.

Her vælger vi:
- Runner type -> Kommandolinje
- Kør -> Eget script
Da vi vil udføre analysen under kompileringen af projektet, skal bygning og analyse være ét trin, så vi udfylder feltet Eget script:

Vi stopper ved separate trin senere. Det er vigtigt, at indlæsningen af analysatoren, bygningen af projektet, dens analyse, rapportudskrift og formatering kun tager elleve linjer kode.
Det sidste, vi skal gøre, er at oprette miljøvariabler, som jeg har angivet nogle stier for at forbedre deres læsbarhed. Gå til: Parametre -> Tilføj ny parameter og tilføje tre variabler:

Det er muligt at klikke på knappen Kør i højre hjørne. Mens bygningen og analysen af projektet kører, vil jeg fortælle dig om scriptet.
Selve scriptet
For at starte er vi nødt til at downloade den nyeste version af PVS-Studio. For dette bruger vi pakkemanageren Chocolatey. For dem, der ønsker at vide mere om dette, er der en passende :
choco install pvs-studio -yDernæst vil vi starte værktøjet til overvågning af projektbygning CLMonitor.
%CLmon% monitor –-attachDerefter udfører vi byggeriet af projektet, hvor miljøvariablen MSB repræsenterer stien til den version af MSBuild, jeg har brug for til bygningen
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableIndtast login og licensnøgle til PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Efter at bygningen er afsluttet, skal vi igen køre CLMonitor for at generere forbehandlede filer og statisk analyse:
%CLmon% analyze -l "c:ptest.plog"Derefter bruger vi et andet værktøj fra vores distribution. PlogConverter konverterer rapporten fra standardformat til TeamCity-format. På denne måde kan vi se den direkte i byggevinduet.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Som det sidste skridt vil vi udskrive den formaterede rapport til stdout, hvor TeamCity-parseren vil fange den.
type "C:tempptest.plog_TeamCity.txt"Den fulde kode af scriptet:
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"Imens tjekket er bygningen og analysen af projektet afsluttet, kan vi gå videre til fanen Projects og bekræfte dette.

Lad os nu klikke på Inspections Total, for at gå til rapporten fra analysatoren:

Advarslerne er grupperet efter nummeret på de diagnostiske regler. For at navigere gennem koden skal du klikke på linjenummeret med advarslen. At klikke på spørgsmålstegnet i øverste højre hjørne åbner en ny fane med dokumentationen. Du kan også navigere i koden ved at trykke på linjenummeret med analysatorens advarsel. Navigation fra en fjerncomputer er mulig ved brug af SourceTreeRoot markøren. Den der er interesseret i denne tilstand for analysatoren, kan læse det relevante afsnit .
Gennemgang af resultaterne fra analysatoren
Når vi er færdige med at deployere og konfigurere bygningen, vil jeg foreslå, at vi ser på nogle interessante advarsler, der er fundet i det undersøgte projekt.
Warning N1
[CWE-401] Undtagelsen blev kastet uden at frigive 'result' pointeren. En hukommelseslækage er mulig. libopenrct2 ObjectFactory.cpp 443
Object* CreateObjectFromJson(....)
{
Object* result = nullptr;
....
result = CreateObject(entry);
....
if (readContext.WasError())
{
throw std::runtime_error("Objektet har fejl");
}
....
}
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("Ugyldig objekt type");
}
return result;
}Analysatoren bemærkede en fejl, der består i, at efter dynamisk hukommelsesallokering i CreateObject, ved undtagelsen frigives hukommelsen ikke, hvilket resulterer i en hukommelseslækage.
Warning N2
Der er identiske underudtryk '(1ULL << WIDX_MONTH_BOX)' til venstre og til højre for '|' operatoren. 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 er sjældent, at nogen, udover en statisk analyzer, kan bestå denne opmærksomhedstest. Dette eksempel på copy-paste er godt netop af denne grund.
Advarsel N3
Det er mærkeligt, at 'flags'-feltet i den afledte klasse 'RCT12BannerElement' overskriver feltet i basis-klassen 'RCT12TileElementBase'. Tjek linjer: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};Selvfølgelig er det ikke altid en fejl at bruge en variabel med samme navn i basis- og afledte klasse. Dog forudsætter arv-teknologi, at alle felter fra den overordnede klasse er til stede i datteren. Ved at erklære felter med samme navn i datteren forårsager vi forvirring.
Advarsel N4
Det er mærkeligt, at resultatet af udsagnet 'imageDirection / 8' er en del af betingelsen. Måske skulle dette udsagn sammenlignes med noget andet. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Lad os gå lidt mere i dybden. Udtrykket imageDirection / 8 vil være false, hvis imageDirection ligger i intervallet fra -7 til 7. Den anden del: (imageDirection / 8) != 3 tjekker imageDirection for at være uden for området: fra -31 til -24 og fra 24 til 31 henholdsvis. Jeg synes, det er ret underligt at tjekke tal for at se om de ligger i et bestemt interval på denne måde, og selvom der ikke er nogen fejl i dette kodefragment, vil jeg anbefale at omskrive disse betingelser til mere eksplicitte. Det ville væsentligt forenkle livet for folk, der skal læse og vedligeholde denne kode.
Advarsel N5
En mærkelig sekvens af tildelinger af denne art: A = B; B = A;. Tjek linjer: 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;
....
}
....
}Denne kodefragment er sandsynligvis blevet erhvervet ved dekonstruktion. Ifølge den efterladte kommentar blev dele af den ikke-fungerende kode fjernet. Dog er der stadig et par operationer over cursorId, som heller ikke bærer nogen særlig mening.
Advarsel N6
[CWE-476] 'player' pointeren blev brugt usikkert efter at den blev verificeret mod nullptr. Tjek linjer: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Tilføj ny spiller.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Denne kode kan rettes ret let, man skal enten tjekke for den tredje gang player for nullpointer, eller inkludere den i kroppen af den betingede operator. Jeg ville foreslå den anden mulighed:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Tilføj ny spiller.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Advarsel N7
[CWE-570] Uden for den ellers tilførte betingelse er 'name == nullptr' altid falsk. 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å én gang slippe af med den svære at læse kodelinje og løse problemet med kontrollen af nullptr. Jeg foreslår at ændre koden som følger:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = "";
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Advarsel N8
[CWE-1164] Variabelen 'ColumnHeaderPressedCurrentState' blev tildelt den samme værdi. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Koden ser ret mærkelig ud. Jeg tror, at der må være en skrivefejl enten i betingelsen eller ved den gentagne tildeling af variablen ColumnHeaderPressedCurrentState værdier false.
Output
Som vi kan se, er det ret nemt at integrere PVS-Studio statiske analysetools i sit projekt på TeamCity. Det kræver blot, at man skriver én lille konfigurationsfil. Kodekontrollen muliggør at identificere problemer umiddelbart efter bygningen, hvilket hjælper med at løse dem, mens kompleksiteten og omkostningerne ved rettelser stadig er små.
Hvis du ønsker at dele denne artikel med et engelsktalende publikum, så bedes du bruge linket til oversættelsen: Vladislav Stolyarov. .
Kilde: habr.com
