PVS-Studio und kontinuierliche Integration: TeamCity. Öffnen Sie die RollerCoaster Tycoon 2-Projektanalyse



Eines der wichtigsten Szenarien für die Verwendung des PVS-Studio-Analysators ist die Integration in CI-Systeme. Obwohl die Analyse eines PVS-Studio-Projekts aus nahezu jedem kontinuierlichen Integrationssystem in nur wenigen Befehlen integriert werden kann, machen wir diesen Prozess weiterhin noch komfortabler. PVS-Studio unterstützt jetzt die Konvertierung der Analysatorausgabe in das Format für TeamCity - TeamCity Inspections Type. Mal sehen, wie es funktioniert.



Informationen zur verwendeten Software



PVS-Studio ist ein statischer Analysator für -, ++ -, C # - und Java-Code, der das Auffinden und Beheben verschiedener Arten von Fehlern erleichtert. Der Analysator kann unter Windows, Linux und MacOS verwendet werden. In diesem Artikel werden wir nicht nur den Analysator selbst, sondern auch einige Dienstprogramme aus seinem Distributionskit aktiv verwenden.



CLMonitor ist ein Überwachungsserver, der den Start des Compilers überwacht. Es muss unmittelbar vor dem Erstellen Ihres Projekts ausgeführt werden. Im Überwachungsmodus fängt der Server die Läufe aller unterstützten Compiler ab. Es ist zu beachten, dass dieses Dienstprogramm nur zur Analyse von C / C ++ - Projekten verwendet werden kann.



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



Informationen zum untersuchten Projekt



Probieren wir diese Funktionalität anhand eines praktischen Beispiels aus - analysieren wir das OpenRCT2-Projekt.



OpenRCT2 ist eine Open-Source-Implementierung von RollerCoaster Tycoon 2 (RCT2), die um neue Funktionen und Fehlerbehebungen erweitert wird. Das Gameplay dreht sich um den Bau und die Wartung eines Vergnügungsparks, der Attraktionen, Geschäfte und Einrichtungen enthält. Der Spieler sollte versuchen, Gewinn zu machen und einen guten Ruf für den Park zu bewahren, während die Gäste zufrieden sind. Mit OpenRCT2 können Sie sowohl im Szenario als auch in der Sandbox spielen. In Szenarien muss der Spieler eine bestimmte Aufgabe zu einem festgelegten Zeitpunkt erledigen, während die Sandbox es dem Spieler ermöglicht, einen flexibleren Park ohne Einschränkungen oder Finanzen zu errichten.



Einrichten



Um Zeit zu sparen, überspringe ich wahrscheinlich den Installationsvorgang und beginne ab dem Moment, an dem der TeamCity-Server auf meinem Computer ausgeführt wird. Wir müssen zu: localhost: {dem während der Installation angegebenen Port} (in meinem Fall localhost: 9090) gehen und die Autorisierungsdaten eingeben. Nach dem Betreten werden wir getroffen von:



image3.png


Klicken Sie auf die Schaltfläche Projekt erstellen. Wählen Sie anschließend Manuell und füllen Sie die Felder aus.



image5.png


Nach dem Klicken auf die Schaltfläche Erstellen werden wir von einem Fenster mit Einstellungen begrüßt.



image7.png


Klicken Sie auf Build-Konfiguration erstellen .



image9.png


Füllen Sie die Felder aus und klicken Sie auf Erstellen . Wir sehen ein Fenster, in dem Sie ein Versionskontrollsystem auswählen können. Da sich die Quellen bereits lokal befinden, klicken Sie auf Überspringen .



image11.png


Schließlich fahren wir mit den Projekteinstellungen fort.



image13.png


Fügen Sie Build-Schritte hinzu, indem Sie auf Folgendes klicken: Build-Schritte -> Build-Schritt hinzufügen .



image15.png


Hier wählen wir:



  • Läufertyp -> Befehlszeile
  • Ausführen -> Benutzerdefiniertes Skript


Da wir während der Kompilierung des Projekts analysieren werden, sollten Zusammenbau und Analyse ein Schritt sein. Füllen Sie daher das Feld Benutzerdefiniertes Skript aus :



image17.png


Wir werden später auf einzelne Schritte eingehen. Es ist wichtig, dass das Laden des Analysators, das Erstellen des Projekts, das Analysieren, das Ausgeben des Berichts und das Formatieren nur elf Codezeilen erfordern.



Als letztes müssen wir die Umgebungsvariablen festlegen, die ich in gewisser Weise angegeben habe, um ihre Lesbarkeit zu verbessern. Gehen Sie dazu zu: Parameter -> Neuen Parameter hinzufügen und drei Variablen hinzufügen:



image19.png


Es bleibt noch auf die Schaltfläche Ausführen in der oberen rechten Ecke zu klicken . Während das Projekt zusammengestellt und analysiert wird, werde ich Ihnen etwas über das Skript erzählen.



Direktes Skript



Zuerst müssen wir die neueste PVS-Studio-Distribution herunterladen. Dafür verwenden wir den ocolatehocolatey Paketmanager. Für diejenigen, die mehr darüber wissen möchten, gibt es einen verwandten Artikel :



choco install pvs-studio -y


Als Nächstes starten wir das Dienstprogramm zur Verfolgung von CLMonitor-Projektassemblierungen.



%CLmon% monitor –-attach


Dann werden wir das Projekt erstellen. Der Pfad zu der Version von MSBuild, die ich erstellen muss, wird als MSB- Umgebungsvariable verwendet



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


Geben Sie den PVS-Studio-Anmelde- und Lizenzschlüssel ein:



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


Führen Sie CLMonitor nach Abschluss des Builds erneut aus, um vorverarbeitete Dateien und statische Analysen zu generieren:



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


Dann werden wir ein weiteres Dienstprogramm aus unserem Distributionskit verwenden. PlogConverter konvertiert den Bericht vom Standard- in das TeamCity-spezifische Format. Dank dessen können wir es direkt im Montagefenster sehen.



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


Der letzte Schritt besteht darin, den formatierten Bericht an stdout auszugeben , wo er vom TeamCity-Parser abgerufen 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"


In der Zwischenzeit wurde die Montage und Analyse des Projekts erfolgreich abgeschlossen. Wir können zur Registerkarte Projekte gehen und dies überprüfen.



image21.png


Klicken Sie nun auf Inspections Total , um den Analysatorbericht anzuzeigen:



image23.png


Warnungen werden nach Diagnoseregelnummern gruppiert. Um durch den Code zu navigieren, müssen Sie auf die Zeilennummer mit der Warnung klicken. Durch Klicken auf das Fragezeichen in der oberen rechten Ecke wird eine neue Registerkarte "Dokumentation" für Sie geöffnet. Sie können auch durch den Code navigieren, indem Sie auf die Nummer der Warnzeile des Analysators klicken. Die Navigation von einem Remotecomputer aus ist mit dem SourceTreeRoot- Marker möglich. Wer sich für diesen Analysatorbetriebsmodus interessiert, kann sich mit dem entsprechenden Abschnitt der Dokumentation vertraut machen .



Anzeigen der Analyzer-Ergebnisse



Nachdem wir mit der Bereitstellung und Konfiguration des Builds fertig sind, schlage ich vor, einige interessante Warnungen zu betrachten, die im untersuchten Projekt enthalten sind.



Warnung N1



V773 [CWE-401] Die Ausnahme wurde ausgelöst, ohne den Zeiger "Ergebnis" freizugeben. Ein Speicherverlust 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 einen Fehler festgestellt, dass nach der dynamischen Speicherzuweisung in CreateObject beim Auslösen einer Ausnahme der Speicher nicht gelöscht wird und dementsprechend ein Speicherverlust auftritt.



Warnung N2



V501 Links und rechts vom '|' befinden sich identische Unterausdrücke '(1ULL << WIDX_MONTH_BOX)'. Operator. 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),
  ....
};


Mit Ausnahme eines statischen Analysegeräts konnten nur wenige Personen diesen Aufmerksamkeitstest bestehen. Dieses Beispiel für Copy-Paste ist genau dafür gut geeignet.



N3



V703 Es ist seltsam, dass das Feld 'flags' in der abgeleiteten Klasse 'RCT12BannerElement' das Feld in der Basisklasse 'RCT12TileElementBase' überschreibt. Überprüfen Sie die Zeilen: RCT12.h: 570, RCT12.h: 259. libopenrct2 RCT12.h 570



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


Die Verwendung einer gleichnamigen Variablen in der Basisklasse und im Erben ist natürlich nicht immer ein Fehler. Die Vererbungstechnologie selbst setzt jedoch das Vorhandensein aller Felder der Elternklasse im Kind voraus. Indem wir ein Feld mit demselben Namen im Erben deklarieren, führen wir zu Verwirrung.



Warnung N4



V793 Es ist merkwürdig, dass das Ergebnis der Anweisung 'imageDirection / 8' 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)
  {
    ....
  }
  ....
}


Lass uns genauer hinschauen. Der imageDirection / 8- Ausdruck ist falsch, wenn die imageDirection im Bereich von -7 bis 7 liegt. Der zweite Teil: (imageDirection / 8)! = 3 überprüft, ob die imageDirection außerhalb des Bereichs liegt: -31 bis -24 und 24 bis 31 beziehungsweise. Es erscheint mir ziemlich seltsam, Zahlen auf diese Weise auf Eingabe eines bestimmten Bereichs zu überprüfen, und selbst wenn dieses Codefragment keinen Fehler enthält, würde ich empfehlen, diese Bedingungen genauer umzuschreiben. Dies würde Menschen, die diesen Code lesen und pflegen würden, das Leben erheblich erleichtern.



Warnung N5



V587Eine ungerade Folge solcher Aufgaben: A = B; B = A; Ü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;
      ....
  }
  ....
}


Dieser Code wurde höchstwahrscheinlich durch Dekompilierung erhalten. Nach dem Kommentar zu urteilen, wurde dann ein Teil des nicht funktionierenden Codes entfernt. Es bleiben jedoch einige Operationen an cursorId übrig , die ebenfalls wenig sinnvoll sind.



N6 Warnung



V1004 [CWE-476] Der 'Spieler'-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);                    // <=
  }
  ....
}


Es ist ganz einfach, diesen Code zu reparieren. Sie müssen den Player entweder ein drittes Mal auf einen Nullzeiger überprüfen oder ihn dem Hauptteil des bedingten Operators hinzufügen. 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] Der 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 die schwer lesbare Codezeile auf einen Schlag loswerden und das Problem mit der Überprüfung auf nullptr lösen . Ich schlage vor, den Code wie folgt zu ä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 Variablen 'ColumnHeaderPressedCurrentState' wurde der gleiche Wert zugewiesen. libopenrct2ui CustomListView.cpp 510



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


Der Code sieht ziemlich komisch aus. Ich denke, es gab einen Tippfehler oder einen Fehler oder beim erneuten Zuweisen des variablen ColumnHeaderPressedCurrentState- Werts zu false .



Ausgabe



Wie wir sehen können, ist die Integration des statischen Analysators PVS-Studio in Ihr TeamCity-Projekt recht einfach. Es reicht aus, nur eine kleine Konfigurationsdatei dafür zu schreiben. Mithilfe der Codeüberprüfung können Sie Probleme unmittelbar nach dem Build identifizieren, um sie zu beseitigen, wenn die Komplexität und die Kosten der Änderungen noch gering sind.





Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Übersetzungslink: Vladislav Stolyarov. PVS-Studio und kontinuierliche Integration: TeamCity. Analyse des Open RollerCoaster Tycoon 2-Projekts .



All Articles