Un dels escenaris més actuals per utilitzar l'analitzador PVS-Studio és la seva integració amb sistemes CI. I encara que l'anàlisi d'un projecte PVS-Studio des de gairebé qualsevol sistema d'integració contínua es pot integrar en només unes quantes ordres, continuem fent que aquest procés sigui encara més còmode. PVS-Studio ara té suport per convertir la sortida de l'analitzador en un format per a TeamCity - TeamCity Inspections Type. Vegem com funciona.
Informació sobre el programari utilitzat
Informació sobre el projecte en estudi
Provem aquesta funcionalitat amb un exemple pràctic: analitzem el projecte OpenRCT2.
ajust
Per estalviar temps, probablement em saltaré el procés d'instal·lació i començaré des del moment en què tinc el servidor TeamCity en execució al meu ordinador. Hem d'anar a: localhost:{port especificat durant el procés d'instal·lació} (en el meu cas, localhost:9090) i introduir les dades d'autorització. Després d'entrar ens donaran la benvinguda:
Feu clic al botó Crea projecte. A continuació, seleccioneu Manualment i ompliu els camps.
Després de prémer el botó Create, ens rep una finestra amb la configuració.
Fem clic Crea una configuració de compilació.
Ompliu els camps i feu clic Create. Veiem una finestra que us demana que seleccioneu un sistema de control de versions. Com que les fonts ja es troben localment, feu clic Omet.
Finalment, passem a la configuració del projecte.
Afegim passos de muntatge, per fer-ho feu clic: Passos de creació -> Afegeix un pas de construcció.
Aquí escollim:
- Tipus de corredor -> Línia d'ordres
- Executar -> Script personalitzat
Com que realitzarem anàlisis durant la compilació del projecte, el muntatge i l'anàlisi haurien de ser un pas, així que ompliu el camp Escriptura personalitzada:
Més endavant veurem els passos individuals. És important que carregar l'analitzador, muntar el projecte, analitzar-lo, emetre l'informe i formatar-lo només requereix onze línies de codi.
L'últim que hem de fer és configurar les variables d'entorn, que he descrit algunes maneres de millorar-ne la llegibilitat. Per fer-ho, seguim: Paràmetres -> Afegeix un paràmetre nou i afegeix tres variables:
Tot el que has de fer és prémer el botó Correr a la cantonada superior dreta. Mentre s'està muntant i analitzant el projecte, us explicaré el guió.
Guió directament
Primer, hem de descarregar la darrera distribució de PVS-Studio. Per a això fem servir el gestor de paquets Chocolatey. Per a aquells que vulguin saber més sobre això, hi ha un corresponent
choco install pvs-studio -y
A continuació, iniciem la utilitat de seguiment de creació del projecte CLMonitor.
%CLmon% monitor –-attach
A continuació, construirem el projecte com a variable d'entorn MSB és el camí cap a la versió de MSBuild que necessito construir
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable
Introduïm l'inici de sessió i la clau de llicència per a PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%
Un cop finalitzada la compilació, torneu a executar CLMonitor per generar fitxers preprocessats i anàlisi estàtica:
%CLmon% analyze -l "c:ptest.plog"
Aleshores farem servir una altra utilitat de la nostra distribució. PlogConverter converteix un informe d'un format estàndard a un format específic de TeamCity. Gràcies a això, el podrem veure directament a la finestra de compilació.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"
L'últim pas és mostrar l'informe amb format stdout, on serà recollit per l'analitzador de TeamCity.
type "C:tempptest.plog_TeamCity.txt"
Codi de script complet:
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"
Mentrestant, el muntatge i anàlisi del projecte s'ha acabat amb èxit, podem anar a la pestanya Projectes i assegureu-vos-ho.
Ara fem clic a sobre Total d'inspeccionsper anar a veure l'informe de l'analitzador:
Els avisos s'agrupen per números de regles de diagnòstic. Per navegar pel codi, heu de fer clic al número de línia amb l'avís. Si feu clic al signe d'interrogació de la cantonada superior dreta, s'obrirà una nova pestanya amb documentació. També podeu navegar pel codi fent clic al número de línia amb l'avís de l'analitzador. La navegació des d'un ordinador remot és possible quan s'utilitza SourceTreeRoot marcador. Qualsevol persona interessada en aquest mode de funcionament de l'analitzador pot familiaritzar-se amb la secció corresponent
Visualització dels resultats de l'analitzador
Ara que hem acabat de desplegar i configurar la compilació, donem una ullada a alguns avisos interessants que es troben al projecte que estem mirant.
Avís 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;
}
L'analitzador va notar un error que després d'assignar memòria dinàmicament Crea un objecte, quan es produeix una excepció, la memòria no s'esborra i es produeix una fuga de memòria.
Avís 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),
....
};
Poques persones que no siguin un analitzador estàtic podrien passar aquesta prova d'atenció. Aquest exemple de copiar i enganxar és bo precisament per aquest motiu.
Advertències N3
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};
Per descomptat, utilitzar una variable amb el mateix nom a la classe base i a la descendent no sempre és un error. Tanmateix, la pròpia tecnologia d'herència suposa que tots els camps de la classe pare estan presents a la classe secundària. En declarar camps amb el mateix nom a l'hereu, introduïm confusió.
Avís N4
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}
Fem una ullada més de prop. Expressió Direcció de la imatge/8 serà fals si Direcció de la imatge està en el rang de -7 a 7. Segona part: (direcció de la imatge / 8) != 3 xecs Direcció de la imatge per estar fora del rang: de -31 a -24 i de 24 a 31, respectivament. Em sembla força estrany comprovar d'aquesta manera els números per incloure'ls en un rang determinat i, fins i tot si no hi ha cap error en aquest fragment de codi, recomanaria reescriure aquestes condicions per ser més explícits. Això facilitaria molt la vida a les persones que llegirien i mantindrien aquest codi.
Avís 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;
....
}
....
}
Aquest fragment de codi probablement es va obtenir per descompilació. Aleshores, a jutjar pel comentari deixat, es va eliminar part del codi que no funcionava. Tanmateix, encara queden un parell d'operacions cursorId, que tampoc tenen gaire sentit.
Avís 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); // <=
}
....
}
Aquest codi és bastant fàcil de corregir; només cal que el reviseu una tercera vegada jugador a un punter nul o afegiu-lo al cos de la instrucció condicional. Jo proposaria la segona opció:
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);
}
}
....
}
Avís 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));
....
}
....
}
Podeu desfer-vos d'una línia de codi difícil de llegir d'un sol cop i resoldre el problema amb la comprovació de nullptr. Suggereixo canviar el codi de la següent manera:
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);
....
}
....
}
Avís N8
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}
El codi sembla força estrany. Em sembla que hi va haver un error ortogràfic, ja sigui en la condició o en tornar a assignar la variable ColumnHeaderPressedCurrentState valors false.
Sortida
Com podem veure, integrar l'analitzador estàtic PVS-Studio al vostre projecte TeamCity és bastant senzill. Per fer-ho, n'hi ha prou amb escriure un petit fitxer de configuració. Comprovar el codi us permetrà identificar els problemes immediatament després del muntatge, cosa que ajudarà a eliminar-los quan la complexitat i el cost dels canvis encara siguin baixos.
Si voleu compartir aquest article amb un públic de parla anglesa, utilitzeu l'enllaç de traducció: Vladislav Stolyarov.
Font: www.habr.com