PVS-Studio und kontinuierliche Integration:TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2

PVS-Studio und kontinuierliche Integration:TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2

Eines der relevantesten Einsatzszenarien für den PVS-Studio Analyzer ist die Integration in CI-Systeme. Auch wenn sich eine Projektanalyse von PVS-Studio bereits mit wenigen Befehlen in nahezu jedes Continuous-Integration-System einbetten lässt, machen wir diesen Prozess noch komfortabler. PVS-Studio unterstützt jetzt die Konvertierung der Analyseausgabe in das TeamCity-Format – TeamCity-Inspektionstyp. Mal sehen, wie es funktioniert.

Über die verwendete Software

PVS-Studio ist ein statischer Analysator für C-, C++-, C#- und Java-Code, der entwickelt wurde, um die Aufgabe zu erleichtern, verschiedene Arten von Fehlern zu finden und zu korrigieren. Der Analysator kann unter Windows, Linux und macOS verwendet werden. In diesem Artikel werden wir nicht nur den Analysator selbst aktiv verwenden, sondern auch einige Dienstprogramme aus seiner Distribution.

CLMonitor ist ein Überwachungsserver, der die Überwachung von Compilerläufen durchführt. Es muss unmittelbar vor dem Erstellen eines Projekts ausgeführt werden. Im Überwachungsmodus fängt der Server Ausführungen aller unterstützten Compiler ab. Es ist erwähnenswert, dass dieses Dienstprogramm nur zum Analysieren von C/C++-Projekten verwendet werden kann.

PlogConverter ist ein Dienstprogramm zum Konvertieren des Analyseberichts in verschiedene Formate.

Über das geprüfte Projekt

Lassen Sie uns diese Funktion an einem praktischen Beispiel ausprobieren, indem wir das OpenRCT2-Projekt analysieren.

OpenRCT2 ist eine offene Implementierung des Spiels RollerCoaster Tycoon 2 (RCT2) und erweitert es um neue Funktionen und behobene Fehler. Das Gameplay dreht sich um den Bau und die Instandhaltung eines Vergnügungsparks, der Fahrgeschäfte, Geschäfte und Einrichtungen beherbergt. Der Spieler muss versuchen, Profit zu machen und den guten Ruf des Parks aufrechtzuerhalten, während er die Gäste glücklich macht. Mit OpenRCT2 können Sie sowohl nach dem Skript als auch in der Sandbox spielen. Szenarien erfordern, dass ein Spieler eine bestimmte Aufgabe in einer festgelegten Zeit erledigt, während die Sandbox es einem Spieler ermöglicht, einen flexibleren Park ohne Einschränkungen oder Finanzen zu bauen.

Konfiguration

Um Zeit zu sparen, überspringe ich wahrscheinlich den Installationsvorgang und beginne an dem Punkt, an dem der TeamCity-Server auf meinem Computer läuft. Wir müssen zu:localhost:{der während der Installation angegebene Port}(in meinem Fall localhost:9090) gehen und die Autorisierungsdaten eingeben. Nach der Eingabe erhalten wir:

Klicken Sie auf Projekt erstellen. Wählen Sie als Nächstes Manuell aus und füllen Sie die Felder aus.

Nachdem Sie auf Erstellen geklickt haben , sehen wir das Fenster mit den Einstellungen.

Klicken Sie auf Build-Konfiguration erstellen.

Füllen Sie die Felder aus und klicken Sie auf Erstellen . Wir sehen das Fenster, das vorschlägt, ein Versionskontrollsystem auszuwählen. Da sich die Quellen bereits lokal befinden, klicken Sie auf Überspringen .

Abschließend gehen wir zu den Projekteinstellungen.

Wir fügen die Build-Schritte hinzu. Klicken Sie dazu auf:Build-Schritte -> Build-Schritt hinzufügen .

Hier wählen wir:

  • Runnertyp -> Befehlszeile
  • Ausführen -> Benutzerdefiniertes Skript

Da wir während der Projektkompilierung eine Analyse durchführen, müssen Build und Analyse ein Schritt sein, daher füllen wir das Benutzerdefinierte Skript aus Feld:

Auf einzelne Schritte gehen wir später ein. Es ist wichtig, dass das Laden des Analysators, das Erstellen des Projekts, das Analysieren, die Berichtsausgabe und das Formatieren nur elf Zeilen Code benötigen.

Das letzte, was wir tun müssen, ist, Umgebungsvariablen zu setzen, die in meinem Fall einige Möglichkeiten skizzieren, um ihre Lesbarkeit zu verbessern. Gehen Sie dazu zu:Parameter -> Neuen Parameter hinzufügen und fügen Sie drei Variablen hinzu:

Klicken Sie einfach auf Ausführen in der oberen rechten Ecke. Während das Projekt erstellt und analysiert wird, erzähle ich Ihnen etwas über das Skript.

Das Skript selbst

Zuerst müssen wir die neueste PVS-Studio-Distribution herunterladen. Dazu verwenden wir den Chocolatey-Paketmanager. Für diejenigen, die mehr darüber erfahren möchten, gibt es einen speziellen Artikel:

choco install pvs-studio -y

Führen Sie als Nächstes das Dienstprogramm CLMonitor zur Projekterstellungsüberwachung aus.

%CLmon% monitor –-attach

Dann bauen wir das Projekt. Das MSB Die Umgebungsvariable stellt den Pfad zu der MSBuild-Version dar, die ich erstellen muss.

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

Geben Sie den Benutzernamen und den Lizenzschlüssel für PVS-Studio ein:

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

Nachdem der Build abgeschlossen ist, führen wir CLMonitor erneut aus, um vorverarbeitete Dateien zu generieren und eine statische Analyse durchzuführen:

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

Danach verwenden wir ein anderes Dienstprogramm aus unserer Distribution. PlogConverter konvertiert einen Bericht vom Standardformat in ein TeamCity-spezifisches Format. Dadurch können wir es direkt im Build-Fenster anzeigen.

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

Die letzte Aktion besteht darin, den formatierten Bericht an stdout, auszugeben wo es vom TeamCity-Parser abgeholt wird.

type "C:\temp\ptest.plog_TeamCity.txt"

Vollständiger Skriptcode:

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:\temp\ptest.plog_TeamCity.txt"

Inzwischen ist der Aufbau und die Analyse des Projekts erfolgreich abgeschlossen, sodass wir zu den Projekten wechseln können Tab und vergewissern Sie sich.

Klicken Sie nun auf Inspektionen gesamt So zeigen Sie den Analysebericht an:

Warnungen werden nach Diagnoseregelnummern gruppiert. Um entlang des Codes zu navigieren, klicken Sie auf die Zeilennummer mit der Warnung. Ein Klick auf das Fragezeichen in der oberen rechten Ecke öffnet eine neue Registerkarte mit Dokumentation. Sie können auch entlang des Codes navigieren, indem Sie auf die Zeilennummer mit der Warnung des Analysators klicken. Die Navigation von einem entfernten Computer ist möglich, wenn der SourceTreeRoot verwendet wird Marker. Diejenigen, die an dieser Betriebsart des Analysators interessiert sind, können den zugehörigen Dokumentationsabschnitt lesen.

Ansehen der Analyseergebnisse

Nachdem wir mit der Bereitstellung und Konfiguration des Builds fertig sind, schlage ich vor, einen Blick auf einige interessante Warnungen zu werfen, die in dem überprüften Projekt gefunden wurden.

Warnung N1

V773 [CWE-401] Die Ausnahme wurde ausgelöst, ohne dass der „Ergebnis“-Zeiger freigegeben wurde. Ein Speicherleck ist möglich. 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;
}

Der Analysator hat den Fehler bemerkt, dass nach der dynamischen Speicherzuordnung in CreateObject , wenn eine Ausnahme auftritt, wird der Speicher nicht gelöscht und folglich tritt ein Speicherleck auf.

Warnung N2

V501 Es gibt identische Unterausdrücke '(1ULL <

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

Nur wenige, aber ein statischer Codeanalysator würde diesen Test bestehen, um Aufmerksamkeit zu erlangen. Dieses Beispiel für Copy-Paste-Prüfungen ist Sorgfalt.

Warnungen N3

V703 Es ist seltsam, dass das Feld „Flags“ in der abgeleiteten Klasse „RCT12BannerElement“ das Feld in der Basisklasse „RCT12TileElementBase“ überschreibt. Zeilen überprüfen:RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

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

Natürlich ist es nicht immer ein Fehler, dieselbe Namensvariable sowohl in der Basis- als auch in der abgeleiteten Klasse zu verwenden. Die Vererbungstechnologie selbst geht jedoch davon aus, dass alle Felder der Elternklasse in der Kindklasse vorhanden sind. Indem wir ein Feld mit demselben Namen in der abgeleiteten Klasse deklarieren, stiften wir Verwirrung.

Warnung N4

V793 Es ist merkwürdig, dass das Ergebnis der 'imageDirection / 8'-Anweisung Teil der Bedingung ist. Vielleicht hätte diese Aussage mit etwas anderem verglichen werden sollen. libopenrct2 ObservationTower.cpp 38

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

Schauen wir es uns genauer an. Die imageDirection / 8 Ausdruck ist falsch, wenn imageDirection liegt im Bereich von -7 bis 7. Zweiter Teil:(imageDirection / 8) !=3 überprüft imageDirection für außerhalb des Bereichs:von -31 bis -24 bzw. von 24 bis 31. Es scheint ziemlich seltsam, Zahlen auf diese Weise darauf zu überprüfen, ob sie in einen bestimmten Bereich fallen, und selbst wenn es keinen Fehler in diesem Codefragment gibt, würde ich empfehlen, diese Bedingungen in explizitere umzuschreiben. Dies würde das Leben der Menschen, die diesen Code später lesen und pflegen, erheblich vereinfachen.

Warnung N5

V587 Eine ungerade Folge von Zuweisungen dieser Art:A =B; B =EIN;. Überprüfen Sie die Zeilen: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;
      ....
  }
  ....
}

Dieses Codefragment wurde höchstwahrscheinlich durch Dekompilierung erhalten. Dann wurde, nach dem hinterlassenen Kommentar zu urteilen, ein Teil des nicht funktionierenden Codes gelöscht. Es gibt jedoch immer noch ein paar Operationen auf cursorId das macht auch nicht viel Sinn.

Warnung N6

V1004 [CWE-476] Der „Player“-Zeiger wurde unsicher verwendet, nachdem er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: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);                    // <=
  }
  ....
}

Dieser Code ist recht einfach zu korrigieren - man muss entweder player überprüfen für einen Nullzeiger zum dritten Mal, oder fügen Sie ihn dem Hauptteil des Bedingungsoperators hinzu. Ich würde die zweite Option vorschlagen:

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

Warnung N7

V547 [CWE-570] Ausdruck 'name ==nullptr' ist immer falsch. 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));
    ....
  }
  ....
}

Sie können eine schwer lesbare Codezeile auf einen Schlag loswerden und das Problem lösen, indem Sie nach nullptr suchen . Ich würde den Code wie folgt ändern:

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

Warnung N8

V1048 [CWE-1164] Der Variable „ColumnHeaderPressedCurrentState“ wurde derselbe Wert zugewiesen. libopenrct2ui CustomListView.cpp 510

void CustomListView::MouseUp(....)
{
  ....
  if (!ColumnHeaderPressedCurrentState)
  {
    ColumnHeaderPressed = std::nullopt;
    ColumnHeaderPressedCurrentState = false;
    Invalidate();
  }
}

Der Code sieht ziemlich seltsam aus. Ich denke, es gab einen Tippfehler entweder in der Bedingung oder bei der Neuzuweisung von false -Wert zum ColumnHeaderPressedCurrentState Variable.

Schlussfolgerung

Wie wir sehen können, ist es recht einfach, den statischen Analysator PVS-Studio in Ihr TeamCity-Projekt zu integrieren. Dazu müssen Sie nur eine kleine Konfigurationsdatei schreiben. Die Überprüfung des Codes wiederum ermöglicht es Ihnen, Probleme unmittelbar nach dem Build zu erkennen, was Ihnen hilft, sie zu beheben, wenn die Komplexität und die Kosten der Änderungen noch gering sind.