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

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Eines der relevantesten Einsatzszenarien des PVS-Studio-Analyzers ist seine Integration mit CI-Systemen. Obwohl die Analyse eines Projekts mit PVS-Studio praktisch unter jedem Continuous Integration-System mit nur wenigen Befehlen integriert werden kann, arbeiten wir weiterhin daran, diesen Prozess noch benutzerfreundlicher zu gestalten. PVS-Studio unterstützt jetzt die Umwandlung der Ausgaben des Analyzers in ein Format für TeamCity – TeamCity Inspections Type. Schauen wir uns an, wie das funktioniert.

Informationen zur verwendeten Software

PVS-Studio – ein statischer Analyzer für C, C++, C# und Java-Code, der dazu dient, die Suche und Behebung verschiedener Arten von Fehlern zu erleichtern. Der Analyzer kann unter Windows, Linux und macOS verwendet werden. In diesem Artikel werden wir nicht nur den Analyzer selbst, sondern auch einige Werkzeuge aus dessen Distribution aktiv nutzen.

CLMonitor – ist ein Überwachungsserver, der die Ausführungen von Compilern verfolgt. Er muss direkt vor dem Beginn der Erstellung Ihres Projekts gestartet werden. Im Überwachungsmodus wird der Server die Ausführungen aller unterstützten Compiler abfangen. Es ist erwähnenswert, dass dieses Tool nur zur Analyse von C/C++-Projekten verwendet werden kann.

PlogConverter – ein Tool zur Konvertierung des Analyzerberichts in verschiedene Formate.

Informationen zum untersuchten Projekt

Lassen Sie uns diese Funktionalität an einem praktischen Beispiel ausprobieren – wir analysieren das Projekt OpenRCT2.

OpenRCT2 – ist eine offene Implementierung des Spiels RollerCoaster Tycoon 2 (RCT2), die das Spiel um neue Funktionen erweitert und Fehler behebt. Das Gameplay dreht sich um den Bau und die Pflege eines Freizeitparks, in dem Attraktionen, Geschäfte und Objekte vorhanden sind. Der Spieler muss versuchen, Gewinn zu erzielen und den guten Ruf des Parks zu bewahren, während er gleichzeitig die Gäste glücklich hält. OpenRCT2 ermöglicht das Spielen sowohl nach Szenarien als auch im Sandbox-Modus. Szenarien erfordern, dass der Spieler innerhalb eines festgelegten Zeitrahmens eine bestimmte Aufgabe erfüllt, während der Sandbox-Modus dem Spieler erlaubt, einen flexibleren Park ohne Einschränkungen oder finanzielle Begrenzungen zu bauen.

Einstellungen

Um Zeit zu sparen, werde ich den Installationsprozess überspringen und mit dem Moment fortfahren, in dem der TeamCity-Server auf meinem Computer läuft. Wir müssen zu: localhost:{im Installationsprozess angegebenen Port}(in meinem Fall localhost:9090) gehen und die Anmeldedaten eingeben. Nach dem Login werden wir begrüßt:

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Klicken wir auf die Schaltfläche Projekt erstellen. Dann wählen wir Manuell aus und füllen die Felder aus.

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Nach dem Klicken auf die Schaltfläche Erstellen, begegnet uns ein Fenster mit den Einstellungen.

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Klicken wir Build-Konfiguration erstellen.

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Füllen wir die Felder aus, klicken wir auf Erstellen. Wir sehen ein Fenster mit der Auswahl der Versionskontrollsysteme. Da die Quellcodes bereits lokal gespeichert sind, klicken wir auf Überspringen.

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Schließlich kommen wir zu den Projekteinstellungen.

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Fügen wir Schritte zur Erstellung hinzu, dazu klicken wir auf: Build-Schritte -> Neuen Build-Schritt hinzufügen.

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Hier wählen wir:

  • Runner-Typ -> Befehlszeile
  • Ausführen -> Benutzerdefiniertes Skript

Da wir während der Kompilierung des Projekts eine Analyse durchführen werden, müssen Erstellung und Analyse einen Schritt bilden, deshalb füllen wir das Feld aus Benutzerdefiniertes Skript:

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
In separaten Schritten werden wir später darauf eingehen. Wichtig ist, dass das Laden des Analyseprogramms, die Erstellung des Projekts, dessen Analyse, das Ausgeben des Berichts und dessen Formatierung insgesamt elf Zeilen Code in Anspruch nehmen.

Das Letzte, was wir tun müssen, ist, Umgebungsvariablen zu setzen, für die ich einige Pfade zur besseren Lesbarkeit bezeichnet habe. Dafür gehen wir zu: Parameter -> Neuen Parameter hinzufügen und fügen drei Variablen hinzu:

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Es bleibt nur noch, auf die Schaltfläche zu klicken Ausführen oben rechts. Während die Erstellung und Analyse des Projekts läuft, erzähle ich Ihnen von dem Skript.

Das Skript selbst

Zunächst müssen wir das aktuelle PVS-Studio-Distributionspaket herunterladen. Dazu verwenden wir den Paketmanager Chocoaltey. Für diejenigen, die mehr darüber erfahren möchten, gibt es entsprechend. Artikel:

choco install pvs-studio -y

Dann starten wir das Tool zur Überwachung der Projekt-Builds, CLMonitor.

%CLmon% monitor –-attach

Danach führen wir die Erstellung des Projekts durch, wobei der Umgebungsvariable MSB der Pfad zur für mich zu verwendenden Version von MSBuild entspricht.

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

Geben wir den Benutzernamen und den Lizenzschlüssel von PVS-Studio ein:

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

Nach Abschluss der Erstellung starten wir CLMonitor erneut, um die vorverarbeiteten Dateien und die statische Analyse zu generieren:

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

Anschließend verwenden wir ein weiteres Utility aus unserem Distributionspaket. PlogConverter wandelt den Bericht von einem Standardformat in ein TeamCity-spezifisches Format um. Dadurch können wir ihn direkt im Build-Fenster ansehen.

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

Als letzte Aktion geben wir den formatieren Bericht aus stdout, wo er vom TeamCity-Parser aufgenommen wird.

type "C:tempptest.plog_TeamCity.txt"

Der vollständige Code des Skripts:

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"

In der Zwischenzeit wurde die Erstellung und Analyse des Projekts erfolgreich abgeschlossen, wir können zum Tab wechseln Projects und dies überprüfen.

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Jetzt klicken wir auf Gesamtinspektionen, um den Bericht des Analysators anzuzeigen:

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Warnungen sind nach den Nummern der Diagnose-Regeln gruppiert. Um durch den Code zu navigieren, müssen Sie auf die Zeilennummer mit der Warnung klicken. Ein Klick auf das Fragezeichen in der oberen rechten Ecke öffnet Ihnen einen neuen Tab mit der Dokumentation. Man kann auch durch den Code navigieren, indem man auf die Zeilennummer mit der Warnung des Analysators klickt. Die Navigation von einem entfernten Computer ist möglich, wenn man einen SourceTreeRoot Marker verwendet. Wer an diesem Arbeitsmodus des Analysators interessiert ist, kann sich mit dem entsprechenden Abschnitt vertrautmachen. Dokumentation.

Ergebnisse der Analyse ansehen

Nachdem wir mit der Bereitstellung und Konfiguration der Erstellung abgeschlossen sind, schlage ich vor, einige interessante Warnungen anzusehen, die im untersuchten Projekt gefunden wurden.

Warnung N1

V773 [CWE-401] Die Ausnahme wurde geworfen, ohne den 'result'-Zeiger freizugeben. Ein Speicherleck ist möglich. libopenrct2 ObjectFactory.cpp 443

Object* CreateObjectFromJson(....)
{
  Object* result = nullptr;
  ....
  result = CreateObject(entry);
  ....
  if (readContext.WasError())
  {
    throw std::runtime_error("Objekt hat Fehler");
  }
  ....
}

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("Ungültiger Objekttyp");
  }
  return result;
}

Der Analysator hat einen Fehler festgestellt, da nach der dynamischen Speicherzuweisung in CreateObject, bei Auftreten einer Ausnahme der Speicher nicht freigegeben wird, was zu einem Speicherleck führt.

Warnung N2

V501 Es gibt identische Teil-Ausdrücke '(1ULL << WIDX_MONTH_BOX)' links und rechts des '|' Operators. libopenrct2ui Cheats.cpp 487

static uint64_t fenster_cheats_seite_aktive_widgets[] = 
{
  HAUPTCHEAT_AKTIVE_WIDGETS |
  (1ULL << WIDX_KEIN_GELD) |
  (1ULL << WIDX_HINZUFUEGEN_SET_GELD_GRUPPE) |
  (1ULL << WIDX_GELD_SPINNER) |
  (1ULL << WIDX_GELD_SPINNER_INKREMENT) |
  (1ULL << WIDX_GELD_SPINNER_DEKREMENT) |
  (1ULL << WIDX_HINZUFUEGEN_GELD) |
  (1ULL << WIDX_SET_GELD) |
  (1ULL << WIDX_LOAN_CLEAR) |
  (1ULL << WIDX_DATUM_SETZEN) |
  (1ULL << WIDX_MONAT_BOX) |  // <=
  (1ULL << WIDX_MONAT_UP) |
  (1ULL << WIDX_MONAT_DOWN) |
  (1ULL << WIDX_JAHR_BOX) |
  (1ULL << WIDX_JAHR_UP) |
  (1ULL << WIDX_JAHR_DOWN) |
  (1ULL << WIDX_TAG_BOX) |
  (1ULL << WIDX_TAG_UP) |
  (1ULL << WIDX_TAG_DOWN) |
  (1ULL << WIDX_MONAT_BOX) |  // <=
  (1ULL << WIDX_DATUM_GRUPPE) |
  (1ULL << WIDX_DATUM_ZURUECKSETZEN),
  ....
};

Wenigstens einer statischer Analyzer könnte diesen Konzentrationstest bestehen. Dieses Beispiel von Copy-Paste ist gerade deshalb besonders.

Warnungen N3

V703 Es ist seltsam, dass das 'flags'-Feld in der abgeleiteten Klasse 'RCT12BannerElement' das Feld in der Basis-Klasse '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;
  ....
};

Natürlich ist die Verwendung einer Variable mit demselben Namen in der Basis-Klasse und in der abgeleiteten Klasse nicht immer ein Fehler. Allerdings impliziert die Vererbung selbst, dass alle Felder der Elternklasse in der Kindklasse vorhanden sind. Wenn wir in der abgeleiteten Klasse Felder mit demselben Namen deklarieren, führen wir zu Verwirrung.

Warnung N4

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

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

Lass uns das näher betrachten. Der Ausdruck imageDirection / 8 wird false sein, wenn imageDirection im Bereich von -7 bis 7 ist. Der zweite Teil: (imageDirection / 8) != 3 überprüft imageDirection ob es außerhalb des Bereichs liegt: von -31 bis -24 und von 24 bis 31. Ich finde es ziemlich seltsam, Zahlen auf diese Weise auf Zugehörigkeit zu einem bestimmten Bereich zu überprüfen. Selbst wenn in diesem Codeabschnitt kein Fehler vorliegt, würde ich empfehlen, diese Bedingungen klarer zu formulieren. Das würde das Leben der Leute erheblich erleichtern, die diesen Code lesen und warten.

Warnung N5

V587 Eine seltsame Abfolge von Zuweisungen dieser Art: 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;                                 // <=
      // Fensterereignis WE_UNKNOWN_0E wurde hier aufgerufen,
      // aber es implementierte niemand tatsächlich einen Handler und
      // es ist nicht bekannt, was es bezweckte
      cursorId = edi;                                 // <=
      if ((ebx & 0xFF) != 0)
      {
        set_cursor(cursorId);
        return;
      }
      break;
      ....
  }
  ....
}

Dieser Codeabschnitt wurde wahrscheinlich durch Dekompilierung erhalten. Dann, wie aus dem hinterlassenen Kommentar hervorgeht, wurde ein Teil des nicht funktionierenden Codes entfernt. Allerdings sind einige Operationen über cursorId, die ebenfalls keinen besonderen Sinn tragen.

Warnung N6

V1004 [CWE-476] Der 'player'-Zeiger wurde unsicher verwendet, nachdem er gegen nullptr überprüft wurde. Überprüfen Sie die Zeilen: 2085, 2094. libopenrct2 Network.cpp 2094

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Neuen Spieler hinzufügen.
    player = AddPlayer("", "");
    if (player)                                          // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
      {
        _serverConnection->Player = player;
      }
    }
    newPlayers.push_back(player->Id);                    // <=
  }
  ....
}

Dieser Code lässt sich recht einfach korrigieren, man muss entweder ein drittes Mal prüfen player auf einen Nullzeiger oder ihn in den Körper des bedingten Operators einfügen. Ich würde die zweite Variante vorschlagen:

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Neuen Spieler hinzufügen.
    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::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    ....
  }
  else
  {
    ....
    entry.name = (name == nullptr ? "" : json_string_value(name));
    ....
  }
  ....
}

Man kann die schwer lesbare Codezeile auf einmal loswerden und das Problem mit der Überprüfung auf nullptr. Ich schlage vor, den Code wie folgt zu ändern:

std::optional 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] Die Variable 'ColumnHeaderPressedCurrentState' wurde derselben Wertzuweisung unterzogen. libopenrct2ui CustomListView.cpp 510

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

Der Code wirkt ziemlich seltsam. Ich habe das Gefühl, dass es einen Tippfehler entweder in der Bedingung oder bei der erneuten Zuweisung der Variablen gegeben hat ColumnHeaderPressedCurrentState Werte false.

Ausgabe

Wie wir sehen, ist es recht einfach, den statischen Analysator PVS-Studio in ein Projekt auf TeamCity zu integrieren. Dazu genügt es, eine kleine Konfigurationsdatei zu erstellen. Die Codeüberprüfung ermöglicht es, Probleme sofort nach dem Build zu erkennen, was hilft, sie zu beheben, solange die Komplexität und die Kosten für Korrekturen noch gering sind.

PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2
Wenn Sie diesen Artikel mit einem englischsprachigen Publikum teilen möchten, verwenden Sie bitte den Link zur Übersetzung: Vladislav Stolyarov. PVS-Studio und Continuous Integration: TeamCity. Analyse des Projekts Open RollerCoaster Tycoon 2.

Quelle: habr.com

60GB SSD 8Gb DDR4