
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
– 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.
– 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.
– 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.
– 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:

Klicken wir auf die Schaltfläche Projekt erstellen. Dann wählen wir Manuell aus und füllen die Felder aus.

Nach dem Klicken auf die Schaltfläche Erstellen, begegnet uns ein Fenster mit den Einstellungen.

Klicken wir Build-Konfiguration erstellen.

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.

Schließlich kommen wir zu den Projekteinstellungen.

Fügen wir Schritte zur Erstellung hinzu, dazu klicken wir auf: Build-Schritte -> Neuen Build-Schritt hinzufügen.

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:

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:

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. :
choco install pvs-studio -yDann starten wir das Tool zur Überwachung der Projekt-Builds, CLMonitor.
%CLmon% monitor –-attachDanach 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:PublishPortableGeben 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.

Jetzt klicken wir auf Gesamtinspektionen, um den Bericht des Analysators anzuzeigen:

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. .
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
[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
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
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
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
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
[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
[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
[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.
Wenn Sie diesen Artikel mit einem englischsprachigen Publikum teilen möchten, verwenden Sie bitte den Link zur Übersetzung: Vladislav Stolyarov. .
Quelle: habr.com
