
Unul dintre cele mai relevante scenarii de utilizare a analizei PVS-Studio este integrarea sa cu sistemele CI. Deși analiza proiectului PVS-Studio poate fi integrată în aproape orice sistem de continuous integration cu doar câteva comenzi, continuăm să facem acest proces și mai convenabil. PVS-Studio a adăugat suport pentru transformarea ieșirii analizei în formatul pentru TeamCity — TeamCity Inspections Type. Să vedem cum funcționează acest lucru.
Informații despre software-ul utilizat
— un analizator static pentru cod C, C++, C# și Java, destinat să faciliteze găsirea și corectarea diferitelor tipuri de erori. Analizatorul poate fi utilizat pe Windows, Linux și macOS. În acest articol, vom folosi activ nu doar analizatorul în sine, ci și unele utilitare din distribuția sa.
— este un server de monitorizare care urmărește lansările compilatoarelor. Trebuie să fie pornit chiar înainte de a începe compilarea proiectului dumneavoastră. În modul de urmărire, serverul va intercepta lansările tuturor compilatoarelor suportate. Este important de menționat că această unealtă poate fi utilizată doar pentru analiza proiectelor C/C++.
– o unealtă pentru conversia raportului analizei în diferite formate.
Informații despre proiectul studiat
Să încercăm această funcționalitate pe un exemplu practic – să analizăm proiectul OpenRCT2.
— o implementare deschisă a jocului RollerCoaster Tycoon 2 (RCT2), care îl extinde cu noi funcții și corectează erorile. Gameplay-ul se concentrează pe construcția și întreținerea unui parc de distracții, care include atracții, magazine și obiective. Jucătorul trebuie să încerce să obțină profit și să mențină o bună reputație a parcului, menținând totodată vizitatorii fericiți. OpenRCT2 permite jocul atât în scenarii, cât și în modul sandbox. Scenariile necesită ca jucătorul să îndeplinească o sarcină specifică într-un timp limitat, în timp ce modul sandbox permite jucătorului să construiască un parc mai flexibil fără nicio restricție sau limită financiară.
Configurare
Pentru a economisi timp, voi sări peste procesul de instalare și voi începe din momentul în care serverul TeamCity rulează pe computerul meu. Trebuie să navigăm la: localhost:{portul specificat în procesul de instalare}(în cazul meu, localhost:9090) și să introducem datele de autentificare. După autentificare, ne va întâlni:

Vom apăsa butonul Creare Proiect. Apoi vom alege Manual, completând câmpurile.

După apăsarea butonului Creează, ne întâmpină o fereastră cu setările.

Să facem clic Creare configurație de construire.

Completăm câmpurile, apăsăm Creează. Vedem o fereastră cu opțiunea de alegere a sistemului de control al versiunilor. Deoarece sursele sunt deja stocate local, apăsăm Sari peste.

În sfârșit, trecem la setările proiectului.

Vom adăuga pașii de construire, pentru aceasta apăsăm: Pași de construire -> Adăugați pas de construire.

Aici vom alege:
- Tip runner -> Linie de comandă
- Executare -> Script personalizat
Deoarece vom efectua analiza în timpul compilării proiectului, construcția și analiza trebuie să fie un singur pas, așa că vom completa câmpul Script personalizat:

Ne vom opri asupra pașilor separați mai târziu. Este important ca încărcarea analistului, construcția proiectului, analiza acestuia, generarea raportului și formatarea să dureze doar unsprezece linii de cod.
Ultimul lucru pe care trebuie să-l facem este să setăm variabilele de mediu, pentru care am denumit unele căi pentru a îmbunătăți lizibilitatea. Pentru asta, vom merge la: Parametrii -> Adăugați parametru nou și vom adăuga trei variabile:

Rămâne să apăsăm butonul Run din colțul din dreapta sus. În timp ce se desfășoară construcția și analiza proiectului, vă voi povesti despre script.
Scriptul propriu-zis
Pentru început, trebuie să descărcăm cea mai recentă versiune a PVS-Studio. Pentru aceasta folosim managerul de pachete Chocolatey. Pentru cei care doresc să afle mai multe, există un :
choco install pvs-studio -yApoi vom porni utilitarul de monitorizare a construcției proiectului CLMonitor.
%CLmon% monitor –-attachApoi vom construi proiectul, ca variabilă de mediu, MSB reprezintă calea către versiunea MSBuild necesară pentru construcție
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableVom introduce numele de utilizator și cheia de licență PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%După finalizarea construcției, vom rula din nou CLMonitor pentru a genera fișierele preprocesate și analiza statică:
%CLmon% analyze -l "c:ptest.plog"După aceasta, vom folosi un alt utilitar din distribuția noastră. PlogConverter va transforma raportul din formatul standard în formatul specific TeamCity. Datorită acestui lucru, vom putea să-l vizualizăm direct în fereastra de construcție.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Ultimul pas va fi să generăm raportul formatat în stdout, unde îl va prelua parserul TeamCity.
type "C:tempptest.plog_TeamCity.txt"Codul complet al scriptului:
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"Între timp, compilarea și analiza proiectului au fost finalizate cu succes, putem trece la fila Projects și ne putem asigura de acest lucru.

Acum să facem clic pe Inspections Total, pentru a merge la vizualizarea raportului analistului:

Avertizările sunt grupate după numerele regulilor de diagnosticare. Pentru a naviga în cod, trebuie să facem clic pe numărul liniei cu avertisment. Făcând clic pe semnul întrebării din colțul din dreapta sus, se va deschide o filă nouă cu documentația. De asemenea, se poate naviga în cod făcând clic pe numărul liniei cu avertismentul analistului. Navigarea de pe un computer la distanță este posibilă utilizând SourceTreeRoot marker. Cei interesați de acest mod de funcționare al analistului pot consulta secțiunea corespunzătoare .
Vizualizarea rezultatelor analistului
După ce am terminat cu desfășurarea și configurarea compilării, vă propun să aruncăm o privire asupra unor avertizări interesante descoperite în proiectul analizat.
Avertisment N1
[CWE-401] Excepția a fost aruncată fără a elibera pointerul 'result'. Este posibilă o scurgere de memorie. libopenrct2 ObjectFactory.cpp 443
Object* CreateObjectFromJson(....)
{
Object* result = nullptr;
....
result = CreateObject(entry);
....
if (readContext.WasError())
{
throw std::runtime_error("Obiect are erori");
}
....
}
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("Tip de obiect invalid");
}
return result;
}Analistul a observat o eroare care constă în faptul că, după alocarea dinamică a memoriei în CreateObject, atunci când apare o excepție, memoria nu este eliberată, rezultând astfel o scurgere de memorie.
Avertisment N2
Există sub-exprimări identice '(1ULL << WIDX_MONTH_BOX)' la stânga și la dreapta operatorului '|'. 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),
....
};Puțini, în afară de un analizor static, ar putea trece acest test de atenție. Acest exemplu de copy-paste este bun tocmai prin asta.
Avertizări N3
Este ciudat că câmpul 'flags' din clasa derivate 'RCT12BannerElement' suprascrie câmpul din clasa de bază 'RCT12TileElementBase'. Verificați liniile: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};Desigur, utilizarea unei variabile cu același nume în clasa de bază și în cea moștenitoare nu este întotdeauna o greșeală. Cu toate acestea, tehnologia moștenirii presupune în mod implicit existența tuturor câmpurilor clasei părinte în cea fiică. Declarând însă în moștenitor câmpuri cu același nume, introducem confuzie.
Avertizare N4
Este ciudat că rezultatul declarației 'imageDirection / 8' este parte a condiției. Poate, această declarație ar fi trebuit comparată cu ceva diferit. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Haideți să analizăm mai în detaliu. Expresia imageDirection / 8 va fi false în cazul în care imageDirection este în intervalul de la -7 la 7. A doua parte: (imageDirection / 8) != 3 verifică imageDirection dacă este în afara intervalului: de la -31 la -24 și de la 24 la 31, respectiv. Mi se pare ciudat să verifici numerele pentru apartenența la un anumit interval în acest fel și, chiar dacă în acest fragment de cod nu există o greșeală, aș recomanda rescrierea acestor condiții într-un mod mai evident. Acest lucru ar simplifica considerabil viața celor care vor citi și întreține acest cod.
Avertizarea N5
O secvență ciudată de atribuiri de acest tip: A = B; B = A;. Verificați liniile: 1115, 1118. libopenrct2ui MouseInput.cpp 1118
void process_mouse_over(....)
{
....
switch (window->widgets[widgetId].type)
{
case WWT_VIEWPORT:
ebx = 0;
edi = cursorId; // <=
// Eventul de fereastră WE_UNKNOWN_0E a fost apelat aici,
// dar nicio fereastră nu a implementat un handler și
// nu se știe pentru ce a fost
cursorId = edi; // <=
if ((ebx & 0xFF) != 0)
{
set_cursor(cursorId);
return;
}
break;
....
}
....
}Acest fragment de cod a fost probabil obținut prin decompilare. Așadar, conform comentariului lăsat, a fost eliminată o parte din codul nefuncțional. Cu toate acestea, au rămas câteva operații asupra cursorId, care de asemenea nu au un sens deosebit.
Avertizare N6
[CWE-476] Punctatorul 'player' a fost folosit nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Adaugă un jucător nou.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Acest cod este destul de simplu de refacut, trebuie fie să se verifice a treia oară player pentru un pointer nul, fie să fie inclus în corpul operatorului condițional. Aș sugera varianta a doua:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Adaugă un jucător nou.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Avertizare N7
[CWE-570] Expresia 'name == nullptr' este întotdeauna falsă. 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));
....
}
....
}Se poate elimina dintr-o dată acea linie de cod greu de citit și rezolva problema cu verificarea pentru nullptr. Propun să modificăm codul în felul următor:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = "";
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Avertizare N8
[CWE-1164] Variabila 'ColumnHeaderPressedCurrentState' a fost atribuită aceeași valoare. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Codul arată destul de ciudat. Mi se pare că a fost o greșeală fie în condiție, fie la atribuire repetată a variabilei ColumnHeaderPressedCurrentState valorile false.
Ieșire
Observăm că integrarea analizatorului static PVS-Studio în proiectul vostru pe TeamCity este destul de simplă. Este suficient să scrieți un singur fișier de configurare mic. Verificarea codului va permite identificarea problemelor imediat după compilare, ceea ce va ajuta la remedierea acestora atunci când complexitatea și costul corecțiilor sunt încă mici.
Dacă doriți să împărtășiți acest articol cu o audiență anglofonă, vă rog să folosiți linkul la traducere: Vladislav Stolyarov. .
Sursa: habr.com
