
Eines der relevantesten Anwendungsszenarien für den PVS-Studio-Analysator ist dessen Integration in CI-Systeme. Obwohl die Analyse eines PVS-Studio-Projekts in nahezu jedes Continuous Integration-System mit nur wenigen Befehlen implementiert werden kann, arbeiten wir daran, diesen Prozess noch benutzerfreundlicher zu gestalten. PVS-Studio unterstützt jetzt die Umwandlung der Ausgaben des Analysators in ein Format für TeamCity — den TeamCity Inspections Type. Lassen Sie uns sehen, wie das funktioniert.
Informationen zur verwendeten Software
— ein statischer Analysator für C, C++, C# und Java-Code, der entwickelt wurde, um die Suche und Behebung verschiedener Arten von Fehlern zu erleichtern. Der Analysator kann unter Windows, Linux und macOS eingesetzt werden. In diesem Artikel werden wir nicht nur den Analysator selbst aktiv nutzen, sondern auch einige Dienstprogramme aus seiner Distribution.
— ist ein Überwachungsserver, der die Ausführungen von Compilern verfolgt. Er muss direkt vor Beginn des Builds Ihres Projekts gestartet werden. Im Überwachungsmodus erfasst der Server die Ausführungen aller unterstützten Compiler. Es ist zu beachten, dass dieses Tool nur zur Analyse von C/C++-Projekten verwendet werden kann.
– ist ein Tool zur Konvertierung von Analyseberichten in verschiedene Formate.
Informationen zum analysierten Projekt
Lassen Sie uns diese Funktionalität anhand eines praktischen Beispiels ausprobieren – wir analysieren das Projekt OpenRCT2.
— eine offene Umsetzung des Spiels RollerCoaster Tycoon 2 (RCT2), die es um neue Funktionen erweitert und Fehler behebt. Das Gameplay dreht sich um den Bau und die Verwaltung eines Freizeitparks mit Fahrgeschäften, Geschäften und Einrichtungen. Der Spieler soll versuchen, Gewinn zu erzielen und den guten Ruf des Parks aufrechtzuerhalten, während er die Gäste glücklich hält. OpenRCT2 ermöglicht das Spielen sowohl in Szenarien als auch im Sandbox-Modus. Szenarien erfordern, dass der Spieler innerhalb einer festgelegten Zeit eine bestimmte Aufgabe erfüllt, während der Sandbox-Modus es dem Spieler erlaubt, einen flexibleren Park ohne Einschränkungen oder finanzielle Vorgaben zu bauen.
Konfiguration
Um Zeit zu sparen, lasse ich den Installationsprozess weg und beginne mit dem Moment, in dem der TeamCity-Server auf meinem Computer läuft. Wir müssen zu: localhost:{im Installationsprozess angegebenen Port} (in meinem Fall localhost:9090) und die Anmeldedaten eingeben. Nach dem Login werden wir begrüßt von:

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

Nach dem Klicken auf die Schaltfläche Erstellen, es öffnet sich ein Fenster mit den Einstellungen.

Klicken wir Build-Konfiguration erstellen.

Füllen wir die Felder aus, klicken wir auf Erstellen. Wir sehen das Fenster mit der Auswahl der Versionskontrollsysteme. Da die Quellcodes bereits lokal vorhanden sind, klicken wir auf Überspringen.

Schließlich kommen wir zu den Projekteinstellungen.

Fügen wir die Build-Schritte hinzu. Dazu klicken wir auf: Build-Schritte -> Neuen Build-Schritt hinzufügen.

Hier wählen wir aus:
- Runner-Typ -> Befehlszeile
- Ausführen -> Benutzerdefiniertes Skript
Da wir die Analyse während der Projektkompilierung durchführen werden, sollten Bau und Analyse ein Schritt sein. Daher füllen wir das Feld Benutzerdefiniertes Skript:

Über die einzelnen Schritte werden wir später sprechen. Wichtig ist, dass das Laden des Analysators, das Kompilieren des Projekts, die Analyse, die Berichtserstellung und die Formatierung insgesamt nur elf Codezeilen in Anspruch nehmen.
Das letzte, was wir tun müssen, ist, die Umgebungsvariablen festzulegen, für die ich einige Pfade zur besseren Lesbarkeit definiert habe. Dafür gehen wir zu: Parameter -> Neuen Parameter hinzufügen und fügen drei Variablen hinzu:

Es bleibt nur noch die Schaltfläche Ausführen in der oberen rechten Ecke zu drücken. Während das Projekt gebaut und analysiert wird, erzähle ich Ihnen etwas über das Skript.
Das Skript selbst
Zunächst müssen wir die neueste Version von PVS-Studio herunterladen. Dazu verwenden wir den Paketmanager Chocolatey. Für diejenigen, die mehr darüber erfahren möchten, gibt es entsprechende :
choco install pvs-studio -yLassen Sie uns nun das Tool CLMonitor zur Überwachung des Projektaufbaus starten.
%CLmon% monitor –-attachAnschließend werden wir das Projekt bauen, wobei wir folgende Umgebungsvariable verwenden: MSB verweist auf den Pfad zur benötigten Version von MSBuild für den Aufbau.
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortableGeben Sie den Benutzernamen und den Lizenzschlüssel von PVS-Studio ein:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%Nach Abschluss des Aufbaus starten wir CLMonitor erneut, um die vorverarbeiteten Dateien und die statische Analyse zu generieren:
%CLmon% analyze -l "c:ptest.plog"Anschließend nutzen wir ein weiteres Tool aus unserem Distribution. PlogConverter wandelt den Bericht vom Standard- in ein TeamCity-spezifisches Format um. Dadurch können wir ihn direkt im Build-Fenster anzeigen.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"Als letzten Schritt geben wir den formatierten Bericht aus stdout, wo er vom TeamCity-Parser erfasst wird.
type "C:tempptest.plog_TeamCity.txt"Der vollständige Skriptcode lautet:
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 wurden der Aufbau und die Analyse des Projekts erfolgreich abgeschlossen, wir können zum Tab wechseln Projects und dies überprüfen.

Klicken wir jetzt auf Inspections Total, um den Bericht des Analysewerkzeugs anzusehen:

Warnungen sind nach Nummern der Diagnoseregeln gruppiert. Um durch den Code zu navigieren, klicken Sie auf die Zeilennummer mit der Warnung. Ein Klick auf das Fragezeichen in der oberen rechten Ecke öffnet Ihnen ein neues Tab mit der Dokumentation. Sie können auch durch den Code navigieren, indem Sie auf die Zeilennummer mit der Warnung des Analysewerkzeugs klicken. Die Navigation von einem Remote-Computer ist möglich, wenn Sie SourceTreeRoot Marker verwenden. Interessierte können sich mit dem entsprechenden Abschnitt vertrautmachen .
Ergebnisse der Analyse anzeigen
Nachdem wir mit der Bereitstellung und Konfiguration der Build abgeschlossen haben, möchte ich einige interessante Warnungen zeigen, die im untersuchten Projekt festgestellt wurden.
Warnung Nr. 1
[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("Object 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, weil nach der dynamischen Speicherzuweisung in CreateObject, im Falle einer Ausnahme der Speicher nicht freigegeben wird, wodurch ein Speicherleck entsteht.
Warnung N2
Es gibt identische Unterausdrücke '(1ULL << WIDX_MONTH_BOX)' links und rechts des '|' Operators. 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),
....
};Kaum jemand außer dem statischen Analyzer könnte diesen Aufmerksamkeits-Test bestehen. Dieses Beispiel für Copy-Paste ist gerade deshalb gut.
Warnungen N3
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;
....
};Natürlich ist die Verwendung einer Variablen mit demselben Namen in der Basisklasse und in der abgeleiteten Klasse nicht immer ein Fehler. Die Technik der Vererbung selbst setzt jedoch voraus, 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' Teil der Bedingung ist. Vielleicht hätte diese Anweisung mit etwas anderem verglichen werden sollen. libopenrct2 ObservationTower.cpp 38
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}Lassen Sie uns genauer darauf eingehen. Der Ausdruck imageDirection / 8 wird false sein, wenn imageDirection im Bereich von -7 bis 7 liegt. Der zweite Teil: (imageDirection / 8) != 3 prüft imageDirection das Vorhandensein außerhalb des Bereichs: von -31 bis -24 und von 24 bis 31. Ich finde es ziemlich seltsam, Zahlen auf diese Weise zu überprüfen, und selbst wenn in diesem Codesegment kein Fehler vorliegt, würde ich empfehlen, diese Bedingungen in klarere umzuschreiben. Das würde den Menschen, die diesen Code lesen und warten, erheblich das Leben erleichtern.
Warnung N5
Eine merkwürdige Zuordnung von 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; // <=
// Window-Event WE_UNKNOWN_0E wurde hier aufgerufen,
// aber keine Fenster haben tatsächlich einen Handler implementiert und
// es ist nicht bekannt, wofür es war
cursorId = edi; // <=
if ((ebx & 0xFF) != 0)
{
set_cursor(cursorId);
return;
}
break;
....
}
....
}Dieser Codeausschnitt wurde wahrscheinlich durch Dekomplilierung erhalten. Anscheinend wurde, gemäß dem hinterlassenen Kommentar, ein Teil nicht funktionierender Code entfernt. Es sind jedoch ein paar Operationen geblieben, cursorId, die ebenfalls keinen besonderen Sinn ergeben.
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) // <=
{
*player = pendingPlayer;
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 jedoch ein drittes Mal überprüfen player Entweder auf den Nullzeiger verweisen oder ihn in den Körper der bedingten Anweisung 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] Der 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 auf einen Schlag die schwer lesbare Codezeile loswerden und das Problem mit der Überprüfung lösen. 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 Wert zugewiesen. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Der Code sieht ziemlich seltsam aus. Ich habe das Gefühl, dass es einen Schreibfehler entweder in der Bedingung oder bei der erneuten Zuweisung der Variablen gegeben hat. ColumnHeaderPressedCurrentState zwischen den Werten false.
Fazit
Wie wir sehen, ist es relativ einfach, den statischen Analyse-Tool PVS-Studio in ein Projekt auf TeamCity zu integrieren. Dazu genügt es, eine kleine Konfigurationsdatei zu schreiben. Die Codeüberprüfung ermöglicht es, Probleme sofort nach dem Build zu identifizieren, was hilft, sie zu beheben, solange die Komplexität und die Kosten für Korrekturen noch gering sind.
Falls Sie diesen Artikel mit einem englischsprachigen Publikum teilen möchten, verwenden Sie bitte den Link zur Übersetzung: Vladislav Stolyarov. .
Quelle: habr.com
