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 degli scenari più attuali per l'utilizzo dell'analizzatore PVS-Studio è la sua integrazione con i sistemi CI. E sebbene l'analisi di un progetto PVS-Studio da quasi tutti i sistemi di integrazione continua possa essere integrata in pochi comandi, continuiamo a rendere questo processo ancora più conveniente. PVS-Studio ora supporta la conversione dell'output dell'analizzatore in un formato per TeamCity - Tipo di ispezione TeamCity. Vediamo come funziona.

Informazioni sul software utilizzato

Studio PVS — un analizzatore statico di codice C, C++, C# e Java, progettato per facilitare il compito di trovare e correggere 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 utilità della sua distribuzione.

CLMonitor — è un server di monitoraggio che monitora gli avvii del compilatore. Deve essere eseguito immediatamente prima di iniziare a costruire il tuo progetto. In modalità snooping, il server intercetterà le esecuzioni di tutti i compilatori supportati. Vale la pena notare che questa utility può essere utilizzata solo per analizzare progetti C/C++.

PlogConverter – un'utilità per convertire i report dell'analizzatore in diversi formati.

Informazioni sul progetto in studio

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

ApriRCT2 - un'implementazione aperta del gioco RollerCoaster Tycoon 2 (RCT2), espandendolo con nuove funzioni e risolvendo bug. Il gioco ruota attorno alla costruzione e alla manutenzione di un parco divertimenti contenente giostre, negozi e strutture. Il giocatore deve cercare di realizzare un profitto e mantenere la buona reputazione del parco mantenendo felici gli ospiti. OpenRCT2 ti consente di giocare sia in scenario che in sandbox. Gli scenari richiedono al giocatore di completare un'attività specifica entro un tempo prestabilito, mentre Sandbox consente al giocatore di costruire un parco più flessibile senza restrizioni o finanziamenti.

registrazione

Per risparmiare tempo, probabilmente 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 durante il processo di installazione} (nel mio caso, localhost:9090) e inserire i dati di autorizzazione. Dopo essere entrati verremo accolti da:

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Fare clic sul pulsante Crea progetto. Successivamente, seleziona Manualmente e compila i campi.

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Dopo aver premuto il pulsante Creare, siamo accolti da una finestra con le impostazioni.

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Facciamo clic Crea la configurazione della build.

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Compila i campi e clicca Creare. Vediamo una finestra che ti chiede di selezionare un sistema di controllo della versione. Poiché le fonti sono già localizzate localmente, fare clic su Saltare.

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Passiamo infine alle impostazioni del progetto.

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Aggiungiamo passaggi di assemblaggio, per fare questo clic: Passaggi di creazione -> Aggiungi passaggio di creazione.

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Qui scegliamo:

  • Tipo di corridore -> Riga di comando
  • Esegui -> Script personalizzato

Poiché eseguiremo l'analisi durante la compilazione del progetto, l'assemblaggio e l'analisi dovrebbero essere un unico passaggio, quindi compila il campo Script personalizzato:

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Vedremo più avanti i singoli passaggi. È importante che caricare l'analizzatore, assemblare il progetto, analizzarlo, produrre il report e formattarlo richieda solo undici righe di codice.

L'ultima cosa che dobbiamo fare è impostare le variabili d'ambiente, di cui ho delineato alcuni modi per migliorarne la leggibilità. Per fare ciò, andiamo avanti: Parametri -> Aggiungi nuovo parametro e aggiungi tre variabili:

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Tutto quello che devi fare è premere il pulsante Correre nell'angolo in alto a destra. Mentre il progetto viene assemblato e analizzato, ti parlerò della sceneggiatura.

Script direttamente

Per prima cosa dobbiamo scaricare l'ultima distribuzione di PVS-Studio. Per questo utilizziamo il gestore pacchetti Chocolatey. Per coloro che vogliono saperne di più, c'è un corrispondente articolo:

choco install pvs-studio -y

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

%CLmon% monitor –-attach

Quindi costruiremo il progetto come variabile di ambiente MSB è il percorso della versione di MSBuild che devo creare

%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 per PVS-Studio:

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

Al termine della compilazione, eseguire nuovamente CLMonitor per generare file preelaborati e analisi statiche:

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

Quindi utilizzeremo un'altra utilità dalla nostra distribuzione. PlogConverter converte un report da un formato standard a un formato specifico di TeamCity. Grazie a questo potremo visualizzarlo direttamente nella finestra di build.

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

L'ultimo passaggio consiste nel visualizzare il report formattato in stdout, dove verrà prelevato dal parser TeamCity.

type "C:tempptest.plog_TeamCity.txt"

Codice script completo:

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 l'assemblaggio e l'analisi del progetto sono stati completati con successo, possiamo andare alla scheda Progetti e убедиться в этом.

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Ora clicchiamo su Totale ispezioniper andare alla visualizzazione del report dell'analizzatore:

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Gli avvisi sono raggruppati in base ai numeri delle regole diagnostiche. Per navigare nel codice è necessario fare clic sul numero di riga con l'avviso. Facendo clic sul punto interrogativo nell'angolo in alto a destra si aprirà una nuova scheda con la documentazione. Puoi anche navigare nel codice facendo clic sul numero di riga con l'avviso dell'analizzatore. Durante l'utilizzo è possibile la navigazione da un computer remoto SourceTreeRoot marcatore. Chiunque sia interessato a questa modalità di funzionamento dell'analizzatore può familiarizzare con la sezione corrispondente documentazione.

Visualizzazione dei risultati dell'analizzatore

Ora che abbiamo finito di distribuire e configurare la build, diamo un'occhiata ad alcuni avvisi interessanti trovati nel progetto che stiamo esaminando.

Avvertimento N1

V773 [CWE-401] L'eccezione è stata lanciata senza rilasciare il puntatore al "risultato". È possibile una perdita di memoria. libopenrct2 ObjectFactory.cpp 443

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'analizzatore ha notato un errore che dopo aver allocato dinamicamente la memoria in CreateObject, quando si verifica un'eccezione, la memoria non viene cancellata e si verifica una perdita di memoria.

Avvertimento N2

V501 Ci sono sottoespressioni identiche '(1ULL << WIDX_MONTH_BOX)' a sinistra e a destra di '|' 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),
  ....
};

Poche persone oltre ad un analizzatore statico potrebbero superare questo test di attenzione. Questo esempio di copia-incolla è utile proprio per questo motivo.

Avvertenze N3

V703 È strano che il campo "flags" nella classe derivata "RCT12BannerElement" sovrascriva il campo nella classe base "RCT12TileElementBase". Controllare le righe: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

struct RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};
struct rct1_peep : RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};

Naturalmente, utilizzare una variabile con lo stesso nome nella classe base e nella discendente non è sempre un errore. Tuttavia, la stessa tecnologia di ereditarietà presuppone che tutti i campi della classe genitore siano presenti nella classe figlia. Dichiarando campi con lo stesso nome nell'erede creiamo confusione.

Avvertimento N4

V793 È strano che il risultato dell'istruzione 'imageDirection / 8' faccia parte della condizione. Forse questa affermazione avrebbe dovuto essere paragonata a qualcos'altro. libopenrct2 Torre di osservazione.cpp 38

void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
  if ((imageDirection / 8) && (imageDirection / 8) != 3)
  {
    ....
  }
  ....
}

Diamo uno sguardo più da vicino. Espressione Direzioneimmagine/8 sarà falso se imageDirection è compreso tra -7 e 7. Seconda parte: (Direzioneimmagine / 8) != 3 assegni imageDirection per essere fuori intervallo: rispettivamente da -31 a -24 e da 24 a 31. Mi sembra piuttosto strano controllare in questo modo i numeri da includere in un determinato intervallo e, anche se non ci sono errori in questo pezzo di codice, consiglierei di riscrivere queste condizioni per essere più esplicite. Ciò renderebbe la vita molto più semplice per le persone che leggerebbero e manterrebbero questo codice.

Avvertimento N5

V587 Una strana sequenza di assegnazioni di questo tipo: A = B; B = UN;. 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;                                 // <=
      // 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;
      ....
  }
  ....
}

Questo frammento di codice è stato molto probabilmente ottenuto mediante decompilazione. Quindi, a giudicare dal commento lasciato, parte del codice non funzionante è stata rimossa. Rimangono però ancora un paio di operazioni cursoreId, anche questo non ha molto senso.

Avvertimento N6

V1004 [CWE-476] Il puntatore 'giocatore' è stato utilizzato in modo non sicuro dopo essere stato verificato rispetto a nullptr. Controlla le righe: 2085, 2094. libopenrct2 Network.cpp 2094

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);                    // <=
  }
  ....
}

Questo codice è abbastanza facile da correggere; devi solo controllarlo una terza volta giocatore a un puntatore nullo o aggiungerlo al corpo dell'istruzione condizionale. Io suggerirei la seconda opzione:

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);
    }
  }
  ....
}

Avvertimento N7

V547 [CWE-570] L'espressione 'name == nullptr' è sempre falsa. libopenrct2 ServerList.cpp 102

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));
    ....
  }
  ....
}

Puoi sbarazzarti di una riga di codice difficile da leggere in un colpo solo e risolvere il problema con il controllo nullptr. Suggerisco di modificare il codice come segue:

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);
    ....
  }
  ....
}

Avvertimento N8

V1048 [CWE-1164] Alla variabile "ColumnHeaderPressedCurrentState" è stato assegnato 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 stato un errore di battitura nella condizione o durante la riassegnazione della variabile ColumnHeaderPressedCurrentState senso falso.

conclusione

Come possiamo vedere, integrare l'analizzatore statico PVS-Studio nel tuo progetto TeamCity è abbastanza semplice. Per fare ciò è sufficiente scrivere solo un piccolo file di configurazione. Il controllo del codice consentirà di identificare i problemi subito dopo l'assemblaggio, il che aiuterà ad eliminarli quando la complessità e il costo delle modifiche sono ancora bassi.

PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2
Se vuoi condividere questo articolo con un pubblico di lingua inglese, utilizza il link di traduzione: Vladislav Stolyarov. PVS-Studio e integrazione continua: TeamCity. Analisi del progetto Open RollerCoaster Tycoon 2.

Fonte: habr.com

Aggiungi un commento