
Uno degli scenari di utilizzo più rilevanti per l'analizzatore PVS-Studio è la sua integrazione con i sistemi CI. Anche se l'analisi di un progetto con PVS-Studio può essere integrata in praticamente qualsiasi sistema di continuous integration con solo pochi comandi, continuiamo a rendere questo processo ancora più semplice. PVS-Studio ha introdotto il supporto per la conversione dell'output dell'analizzatore in un formato per TeamCity — TeamCity Inspections Type. Vediamo come funziona.
Informazioni sul software utilizzato
è un analizzatore statico per codice C, C++, C# e Java, progettato per semplificare il compito di ricerca e correzione di vari tipi di errori. L'analizzatore può essere utilizzato su Windows, Linux e macOS. In questo articolo utilizzeremo attivamente non solo l'analizzatore stesso, ma anche alcune utility del suo pacchetto.
è un server di monitoraggio che tiene traccia delle esecuzioni dei compilatori. Deve essere avviato immediatamente prima dell'inizio della compilazione del tuo progetto. In modalità di monitoraggio, il server intercetterà le esecuzioni di tutti i compilatori supportati. È importante notare che questa utility può essere utilizzata solo per l'analisi di progetti C/C++.
– utility per la conversione del report dell'analizzatore in vari formati.
Informazioni sul progetto esaminato
Proviamo questa funzionalità con un esempio pratico: analizziamo il progetto OpenRCT2.
è un'implementazione open source del gioco RollerCoaster Tycoon 2 (RCT2), che lo arricchisce con nuove funzionalità e corregge bug. Il gameplay si concentra sulla costruzione e gestione di un parco divertimenti, dove si trovano attrazioni, negozi e strutture. Il giocatore deve cercare di ottenere profitti e mantenere una buona reputazione del parco, assicurando che gli ospiti siano contenti. OpenRCT2 consente di giocare sia in modalità scenario che in modalità sandbox. Gli scenari richiedono che il giocatore completi un determinato compito entro un tempo stabilito, mentre la modalità sandbox permette al giocatore di costruire un parco più flessibile senza limitazioni o vincoli economici.
Impostazione
Per risparmiare tempo, salterò il processo di installazione e inizierò dal momento in cui ho il server TeamCity in esecuzione sul mio computer. Dobbiamo andare su: localhost:{porta specificata nel processo di installazione}(nel mio caso, localhost:9090) e inserire i dati di accesso. Dopo l'accesso, ci troveremo di fronte a:

Clicchiamo sul pulsante Crea Progetto. Successivamente selezioniamo Manualmente e compiliamo i campi.

Dopo aver premuto il pulsante Crea, ci troviamo di fronte a una finestra con le impostazioni.

Premiamo Crea configurazione di build.

Compiliamo i campi e premiamo Crea. Vediamo una finestra che ci offre di scegliere il sistema di controllo versioni. Poiché i sorgenti sono già presenti localmente, clicchiamo su Salta.

Infine, passiamo alle impostazioni del progetto.

Aggiungiamo i passaggi di build, per fare ciò clicchiamo su: Passi di build -> Aggiungi passaggio di build.

Qui scegliamo:
- Tipo di runner -> Command Line
- Esegui -> Script personalizzato
Poiché eseguiremo l'analisi durante la compilazione del progetto, la build e l'analisi devono essere un passo unico, quindi compiliamo il campo Script personalizzato:

Ci fermeremo su passaggi separati più avanti. È importante che il caricamento dell'analizzatore, la compilazione del progetto, la sua analisi, l'output del rapporto e la sua formattazione richiedano solo undici righe di codice.
L'ultima cosa che dobbiamo fare è impostare delle variabili d'ambiente, per le quali ho indicato alcuni percorsi per migliorarne la leggibilità. Per farlo, procediamo su: Parametri -> Aggiungi nuovo parametro e aggiungiamo tre variabili:

Rimane da cliccare sul pulsante Esegui nell'angolo in alto a destra. Mentre la build e l'analisi del progetto sono in corso, vi parlerò dello script.
Lo script stesso
Per cominciare, dobbiamo scaricare l'ultima versione di PVS-Studio. Utilizzeremo il gestore di pacchetti Chocoatey. Per chi vuole saperne di più, c'è un'adeguata :
choco install pvs-studio -ySuccessivamente, avvieremo lo strumento di monitoraggio delle costruzioni del progetto, CLMonitor.
%CLmon% monitor –-attachPoi compiliamo il progetto, utilizzando come variabile d'ambiente MSB che rappresenta il percorso della versione di MSBuild necessaria per la compilazione
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableInseriamo il nome utente e la chiave di licenza di PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Dopo aver completato la compilazione, eseguiamo nuovamente CLMonitor per generare i file preelaborati e l'analisi statistica:
%CLmon% analyze -l "c:ptest.plog"Successivamente, utilizzeremo un altro strumento del nostro pacchetto. PlogConverter converte il rapporto dal formato standard a quello specifico per TeamCity. In questo modo, potremo visualizzarlo direttamente nella finestra di costruzione.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Infine, genereremo il rapporto formattato in stdout, dove verrà acquisito dal parser di TeamCity.
type "C:tempptest.plog_TeamCity.txt"Codice completo dello script:
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"Nel frattempo, la compilazione e l'analisi del progetto sono state completate con successo, possiamo passare alla scheda Progetti e verificarlo.

Ora clicchiamo su Inspections Total, per accedere alla visualizzazione del report dell'analizzatore:

Gli avvisi sono raggruppati in base ai numeri delle regole diagnostiche. Per navigare nel codice, è sufficiente cliccare sul numero della riga con l'avviso. Cliccando sul punto interrogativo nell'angolo in alto a destra si aprirà una nuova scheda con la documentazione. È anche possibile navigare nel codice cliccando sul numero della riga con l'avviso dell'analizzatore. La navigazione da un computer remoto è possibile utilizzando SourceTreeRoot il marcatore. Chi è interessato a questo modo di operare dell'analizzatore può consultare la sezione corrispondente .
Visualizzazione dei risultati dell'analizzatore
Dopo aver completato il deployment e la configurazione della build, vi propongo di dare un'occhiata ad alcuni interessanti avvisi rilevati nel progetto esaminato.
Avviso N1
[CWE-401] L'eccezione è stata sollevata senza rilasciare il puntatore 'result'. Potrebbe verificarsi una perdita di memoria. libopenrct2 ObjectFactory.cpp 443
Object* CreateObjectFromJson(....)
{
Object* result = nullptr;
....
result = CreateObject(entry);
....
if (readContext.WasError())
{
throw std::runtime_error("L'oggetto presenta errori");
}
....
}
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("Tipo di oggetto non valido");
}
return result;
}L'analizzatore ha rilevato un errore, in quanto dopo l'allocazione dinamica della memoria in CreateObject, in caso di eccezione, la memoria non viene deallocata, quindi si verifica una perdita di memoria.
Avviso N2
Ci sono sottospressioni identiche '(1ULL << WIDX_MONTH_BOX)' a sinistra e a destra dell'operatore '|'. 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),
....
};Pochi, oltre a un analizzatore statico, sarebbero in grado di superare questo test di attenzione. Questo esempio di copia e incolla è valido proprio per questo motivo.
Avvisi N3
È strano che il campo 'flags' nella classe derivata 'RCT12BannerElement' sovrascriva il campo nella classe base 'RCT12TileElementBase'. Controlla le righe: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};Certo, l'uso di una variabile con lo stesso nome nella classe base e nell'erede non è sempre un errore. Tuttavia, la tecnologia dell'ereditarietà implica che tutti i campi della classe genitore siano presenti in quella figlia. Dichiarando campi con lo stesso nome nell'erede, si crea confusione.
Avviso N4
È strano che il risultato dell'istruzione 'imageDirection / 8' faccia parte della condizione. Forse, questa istruzione avrebbe dovuto essere confrontata con qualcos'altro. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Approfondiamo un po'. L'espressione imageDirection / 8 resterà falsa se imageDirection è compreso nell'intervallo da -7 a 7. La seconda parte: (imageDirection / 8) != 3 controlla imageDirection se è fuori dall'intervallo: da -31 a -24 e da 24 a 31 rispettivamente. Trovo piuttosto strano controllare i numeri per vedere se rientrano in un determinato intervallo in questo modo e, anche se in questo frammento di codice non c'è errore, consiglierei di riscrivere queste condizioni in modo più chiaro. Questo semplificherebbe notevolmente la vita a chi leggerà e manterrà questo codice.
Avviso N5
Una sequenza strana di assegnazioni di questo tipo: A = B; B = A;. Controlla le righe: 1115, 1118. libopenrct2ui MouseInput.cpp 1118
void process_mouse_over(....)
{
....
switch (window->widgets[widgetId].type)
{
case WWT_VIEWPORT:
ebx = 0;
edi = cursorId; // <=
// L'evento della finestra WE_UNKNOWN_0E è stato chiamato qui,
// ma nessuna finestra ha effettivamente implementato un gestore e
// non è noto per cosa fosse
cursorId = edi; // <=
if ((ebx & 0xFF) != 0)
{
set_cursor(cursorId);
return;
}
break;
....
}
....
}Questo frammento di codice è stato probabilmente ottenuto tramite decompilazione. Successivamente, a giudicare dal commento rimasto, è stata rimossa parte del codice non funzionante. Tuttavia, è rimasta un paio di operazioni su cursorId, che non hanno particolarmente senso.
Avviso N6
[CWE-476] Il puntatore 'player' è stato utilizzato in modo non sicuro dopo essere stato verificato contro nullptr. Controlla le righe: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Aggiungi un nuovo giocatore.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Questo codice è piuttosto semplice da correggere, bisogna o controllare una terza volta player sul puntatore nullo, oppure inserirlo nel corpo dell'operatore condizionale. Ti suggerirei la seconda opzione:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Aggiungi il nuovo giocatore.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Avviso N7
[CWE-570] L'espressione 'name == nullptr' è sempre falsa. 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));
....
}
....
}Si può eliminare in un colpo solo la stringa di codice poco leggibile e risolvere il problema con il controllo su nullptr. Propongo di modificare il codice come segue:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = "";
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Avviso N8
[CWE-1164] La variabile 'ColumnHeaderPressedCurrentState' è stata assegnata lo stesso valore. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Il codice appare piuttosto strano. Penso che ci sia stato un errore di battitura o nella condizione, o nell'assegnazione ripetuta della variabile. ColumnHeaderPressedCurrentState i valori false.
Risultato
Come vediamo, integrare l'analizzatore statico PVS-Studio nel proprio progetto su TeamCity è abbastanza semplice. È sufficiente scrivere un piccolo file di configurazione. La verifica del codice consentirà di rilevare i problemi subito dopo la compilazione, il che aiuterà a risolverli quando la complessità e il costo delle correzioni sono ancora contenuti.
Se desiderate condividere questo articolo con un pubblico anglofono, vi prego di utilizzare il link alla traduzione: Vladislav Stolyarov. .
Fonte: habr.com
