PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Uno dei scenari più attuali per l'utilizzo dell'analizzatore PVS-Studio è la sua integrazione con i sistemi CI. Anche se l'analisi del progetto PVS-Studio può essere integrata in praticamente qualsiasi sistema di continuous integration con solo pochi comandi, continuiamo a rendere questo processo ancora più conveniente. In PVS-Studio è stata aggiunta la supporto per la conversione dell'output dell'analizzatore nel formato per TeamCity - TeamCity Inspections Type. Vediamo come funziona.

Informazioni sul software utilizzato

PVS-Studio — un analizzatore statico per codice C, C++, C# e Java, progettato per semplificare la ricerca e la correzione di vari tipi di errori. L'analizzatore può essere utilizzato su Windows, Linux e macOS. In questo articolo useremo non solo l'analizzatore stesso, ma anche alcune utilità dal suo pacchetto.

CLMonitor — è un server di monitoraggio che tiene traccia delle esecuzioni dei compilatori. Deve essere avviato direttamente 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 utilità può essere utilizzata solo per l'analisi dei progetti C/C++.

PlogConverter – un'utilità per convertire il rapporto dell'analizzatore in vari formati.

Informazioni sul progetto esaminato

Proviamo questa funzionalità con un esempio pratico: analizziamo il progetto OpenRCT2.

OpenRCT2 — è una realizzazione open source del gioco RollerCoaster Tycoon 2 (RCT2), che lo arricchisce con nuove funzionalità e corregge errori. Il gameplay ruota attorno alla costruzione e alla gestione di un parco divertimenti, dove si trovano attrazioni, negozi e servizi. Il giocatore deve cercare di ottenere un profitto e mantenere una buona reputazione del parco, tenendo i visitatori felici. OpenRCT2 consente di giocare sia in modalità scenari che in modalità sandbox. Gli scenari richiedono che il giocatore completi un compito specifico entro un determinato tempo, mentre la modalità sandbox consente al giocatore di costruire un parco più flessibile senza alcuna restrizione o vincolo finanziario.

Configurazione

Per risparmiare tempo, probabilmente tralascerò il processo di installazione e inizierò dal momento in cui il server TeamCity è attivo sul mio computer. Dobbiamo passare a: localhost:{porta indicata nel processo di installazione} (nel mio caso, localhost:9090) e inserire i dati per l'autenticazione. Dopo aver effettuato l'accesso, ci troveremo di fronte a:

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Clicchiamo sul pulsante Crea Progetto. Poi selezioniamo Manualmente, compilando i campi.

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Dopo aver premuto il pulsante Crea, ci si presenta una finestra con le impostazioni.

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Clicchiamo Crea configurazione build.

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Compiliamo i campi e clicchiamo Crea. Vediamo una finestra che offre la scelta del sistema di controllo versioni. Since the source code is already local, clicchiamo Ignora.

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Infine, passiamo alle impostazioni del progetto.

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Aggiungiamo i passaggi di build, per farlo clicchiamo su: Passaggi di build -> Aggiungi passaggio di build.

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Qui selezioniamo:

  • Tipo di Runner -> Linea di comando
  • Esegui -> Script personalizzato

Poiché analizzeremo durante la compilazione del progetto, la build e l'analisi devono essere un unico passaggio, quindi compiliamo il campo Script personalizzato:

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Ci fermeremo su singoli passaggi più tardi. È importante che il caricamento dell'analizzatore, la compilazione del progetto, la sua analisi, la generazione del rapporto e la sua formattazione occupino un totale di undici righe di codice.

L'ultima cosa che dobbiamo fare è impostare le variabili d'ambiente, per le quali ho contrassegnato alcuni percorsi per migliorarne la leggibilità. A questo punto, andiamo a: Parametri -> Aggiungi nuovo parametro e aggiungiamo tre variabili:

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Rimane da cliccare sul pulsante Esegui in alto a destra. Mentre la compilazione e l'analisi del progetto sono in corso, ti parlerò dello script.

Lo script propriamente detto

Per iniziare, dobbiamo scaricare l'ultima distribuzione di PVS-Studio. Per farlo, usiamo il gestore pacchetti ChocoLTEy. Per chi desidera ulteriori dettagli, è disponibile una corrispondente su Habr.:

choco install pvs-studio -y

Dopo, avviamo l'utilità di monitoraggio della build del progetto CLMonitor.

%CLmon% monitor –-attach

Poi eseguiremo la build del progetto, dove come variabile d'ambiente MSB viene utilizzato il percorso per la versione MSBuild necessaria alla build

%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable

Inseriamo il login e la chiave di licenza PVS-Studio:

%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%

Dopo aver completato la build, eseguiamo nuovamente CLMonitor per generare file preprocessati e l'analisi statica:

%CLmon% analyze -l "c:ptest.plog"

Dopo utilizzeremo un'altra utility dal nostro pacchetto. PlogConverter trasforma il report da un formato standard a uno specifico per TeamCity. Grazie a questo potremo visualizzarlo direttamente nella finestra di compilazione.

%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"

Come ultimo passaggio, creeremo un report 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, e possiamo passare alla scheda Progetti e assicurarci di ciò.

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Ora facciamo clic su Inspections Total, per passare alla visualizzazione del report dell'analizzatore:

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
I warning sono raggruppati per numeri di regole diagnostiche. Per navigare nel codice, fai clic sul numero di riga con il warning. Cliccando sul punto interrogativo in alto a destra si aprirà una nuova scheda con la documentazione. È possibile anche navigare nel codice cliccando sul numero di riga con il warning dell'analizzatore. La navigazione da un computer remoto è possibile utilizzando SourceTreeRoot come marcatore. Chi è interessato a questa modalità di lavoro dell'analizzatore può consultare la sezione corrispondente documentazione.

Visualizzazione dei risultati dell'analizzatore

Dopo aver concluso l'implementazione e la configurazione della compilazione, propongo di dare un'occhiata ad alcuni warning interessanti trovati nel progetto esaminato.

Warning N1

V773 [CWE-401] L'eccezione è stata generata senza rilasciare il puntatore 'result'. È possibile che ci sia 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, consistente nel fatto che dopo l'allocazione dinamica di memoria in CreateObject, in caso di eccezione, la memoria non viene liberata, il che porta a una perdita di memoria.

Warning N2

V501 Ci sono sottoscrizioni 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 all'analizzatore statico, sarebbero in grado di superare questo test di attenzione. Questo esempio di copia-incolla è buono proprio per questo.

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 di ereditarietà implica la presenza di tutti i campi della classe genitore nel figlio. Dichiarando nei figli campi con lo stesso nome, creiamo confusione.

Avviso N4

V793

È 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) { .... } .... }

Cerchiamo di approfondire. L'espressione

imageDirection / 8 sarà false nel caso in cui imageDirection sia compreso tra -7 e 7. La seconda parte: (imageDirection / 8) != 3 controlla se è fuori dall'intervallo: da -31 a -24 e da 24 a 31 rispettivamente. Mi sembra piuttosto strano controllare i numeri per la loro appartenenza a 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ù esplicito. Questo semplificherebbe notevolmente la vita a chi dovrà leggere e mantenere questo codice. sia compreso tra -7 e 7. La seconda parte: Avviso N5

V587

Una sequenza strana di assegnamenti di questo tipo: A = B; B = A;. Controlla le righe: 1115, 1118. libopenrct2ui MouseInput.cpp 1118 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 a cosa servisse
      cursorId = edi;                                 // <=
      if ((ebx & 0xFF) != 0)
      {
        set_cursor(cursorId);
        return;
      }
      break;
      ....
  }
  ....
}

Questo frammento di codice è stato probabilmente ottenuto mediante decompilazione. Successivamente, a giudicare dal commento lasciato, è stata rimossa una parte di codice non funzionante. Tuttavia, sono rimaste un paio di operazioni su cursorId, che anch'esse non hanno un significato particolare.

Avviso N6

V1004 [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 può essere facilmente corretto, è necessario controllare un'altra volta player per un puntatore nullo, oppure inserirlo all'interno del corpo dell'operatore condizionale. Propongo la seconda opzione:

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Aggiungi un 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

V547 [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 linea di codice difficilmente leggibile e risolvere il problema della verifica 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

V1048 [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 sembra piuttosto strano. Mi sembra che ci sia stata una svista o nella condizione o durante la ri-assegnazione della variabile ColumnHeaderPressedCurrentState i valori false.

Conclusione

Come possiamo vedere, integrare l'analizzatore statico PVS-Studio nel proprio progetto su TeamCity è piuttosto semplice. È sufficiente scrivere un piccolo file di configurazione. Il controllo del codice consente di identificare i problemi immediatamente dopo la compilazione, il che aiuta a risolverli quando la complessità e il costo delle correzioni sono ancora contenuti.

PVS-Studio e Integrazione Continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Se desideri condividere questo articolo con un pubblico anglofono, ti prego di utilizzare il link alla traduzione: Vladislav Stolyarov. PVS-Studio e Continuous Integration: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster