Jednym z najbardziej aktualnych scenariuszy wykorzystania analizatora PVS-Studio jest jego integracja z systemami CI. I chociaż analizę projektu PVS-Studio z niemal dowolnego systemu ciągłej integracji można wbudować w zaledwie kilka poleceń, nadal czynimy ten proces jeszcze wygodniejszym. PVS-Studio obsługuje teraz konwersję danych wyjściowych analizatora do formatu TeamCity - Typ kontroli TeamCity. Zobaczmy jak to działa.
Informacje o używanym oprogramowaniu
Informacje o badanym projekcie
Wypróbujmy tę funkcjonalność na praktycznym przykładzie - przeanalizujmy projekt OpenRCT2.
regulacja
Aby zaoszczędzić czas, prawdopodobnie pominę proces instalacji i zacznę od momentu, gdy na moim komputerze będzie działał serwer TeamCity. Musimy wejść na: localhost:{port określony podczas instalacji} (w moim przypadku localhost:9090) i wprowadzić dane autoryzacyjne. Po wejściu przywitają nas:
Kliknij przycisk Utwórz projekt. Następnie wybierz opcję Ręcznie i uzupełnij pola.
Po naciśnięciu przycisku Stwórz, wita nas okno z ustawieniami.
Kliknijmy Utwórz konfigurację kompilacji.
Wypełnij pola i kliknij Stwórz. Widzimy okno z prośbą o wybranie systemu kontroli wersji. Ponieważ źródła znajdują się już lokalnie, kliknij Skip.
Na koniec przechodzimy do ustawień projektu.
Dodajmy kroki montażu, aby to zrobić kliknij: Kroki kompilacji -> Dodaj krok kompilacji.
Tutaj wybieramy:
- Typ biegacza -> Wiersz poleceń
- Uruchom -> Skrypt niestandardowy
Ponieważ analizę będziemy przeprowadzać podczas kompilacji projektu, montaż i analiza powinny stanowić jeden krok, dlatego należy wypełnić pole Skrypt niestandardowy:
Poszczególnym krokom przyjrzymy się później. Ważne jest, aby załadowanie analizatora, złożenie projektu, przeanalizowanie go, wygenerowanie raportu i jego sformatowanie zajęło tylko jedenaście linii kodu.
Ostatnią rzeczą, którą musimy zrobić, to ustawić zmienne środowiskowe, w których opisałem kilka sposobów na poprawę ich czytelności. Aby to zrobić, przejdźmy dalej: Parametry -> Dodaj nowy parametr i dodaj trzy zmienne:
Wszystko, co musisz zrobić, to nacisnąć przycisk run w prawym górnym rogu. W trakcie składania i analizowania projektu opowiem Wam o scenariuszu.
Bezpośrednio skrypt
Najpierw musimy pobrać najnowszą dystrybucję PVS-Studio. W tym celu używamy menedżera pakietów Chocolatey. Dla tych, którzy chcą wiedzieć więcej na ten temat, istnieje odpowiedni
choco install pvs-studio -y
Następnie uruchommy narzędzie do śledzenia kompilacji projektu CLMonitor.
%CLmon% monitor –-attach
Następnie zbudujemy projekt jako zmienną środowiskową MSB to ścieżka do wersji programu MSBuild, którą muszę zbudować
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable
Wprowadźmy login i klucz licencyjny dla PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%
Po zakończeniu kompilacji uruchom ponownie CLMonitor, aby wygenerować wstępnie przetworzone pliki i analizę statyczną:
%CLmon% analyze -l "c:ptest.plog"
Następnie skorzystamy z innego narzędzia z naszej dystrybucji. PlogConverter konwertuje raport ze standardowego formatu do formatu specyficznego dla TeamCity. Dzięki temu będziemy mogli obejrzeć go bezpośrednio w oknie kompilacji.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"
Ostatnim krokiem jest wyświetlenie sformatowanego raportu w formacie stdout, gdzie zostanie on odebrany przez parser TeamCity.
type "C:tempptest.plog_TeamCity.txt"
Pełny kod skryptu:
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"
W międzyczasie montaż i analiza projektu zakończyła się pomyślnie, możemy przejść do zakładki Projekty i upewnij się o tym.
Teraz kliknijmy Inspekcje ogółemaby przejść do przeglądania raportu analizatora:
Ostrzeżenia są pogrupowane według numerów reguł diagnostycznych. Aby poruszać się po kodzie, należy kliknąć numer linii z ostrzeżeniem. Kliknięcie znaku zapytania w prawym górnym rogu spowoduje otwarcie nowej zakładki z dokumentacją. Możesz także poruszać się po kodzie, klikając numer linii z ostrzeżeniem analizatora. Podczas korzystania możliwa jest nawigacja z komputera zdalnego SourceTreeRoot znacznik. Każdy, kto jest zainteresowany tym sposobem działania analizatora, może zapoznać się z odpowiednim rozdziałem
Przeglądanie wyników analizatora
Teraz, gdy już zakończyliśmy wdrażanie i konfigurowanie kompilacji, przyjrzyjmy się kilku interesującym ostrzeżeniom znalezionym w projekcie, któremu się przyglądamy.
Ostrzeżenie N1
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;
}
Analizator zauważył błąd, który po dynamicznym przydzieleniu pamięci w Utwórz obiekt, gdy wystąpi wyjątek, pamięć nie zostanie wyczyszczona i nastąpi wyciek pamięci.
Ostrzeżenie N2
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),
....
};
Niewiele osób poza analizatorem statycznym mogłoby przejść ten test uwagi. Ten przykład kopiowania i wklejania jest dobry właśnie z tego powodu.
Ostrzeżenia N3
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};
Oczywiście użycie zmiennej o tej samej nazwie w klasie bazowej i potomku nie zawsze jest błędem. Jednak sama technologia dziedziczenia zakłada, że wszystkie pola klasy nadrzędnej są obecne w klasie podrzędnej. Deklarując w spadkobiercy pola o tej samej nazwie, wprowadzamy zamieszanie.
Ostrzeżenie N4
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}
Przyjrzyjmy się bliżej. Wyrażenie obrazKierunek/8 będzie fałszywe, jeśli Kierunek obrazu mieści się w przedziale od -7 do 7. Część druga: (kierunek obrazu / 8) != 3 czeki Kierunek obrazu za bycie poza zakresem: odpowiednio od -31 do -24 i od 24 do 31. Sprawdzanie w ten sposób liczb pod kątem uwzględnienia ich w określonym zakresie wydaje mi się dość dziwne i nawet jeśli w tym fragmencie kodu nie ma błędu, zalecałbym przepisanie tych warunków, aby były bardziej jednoznaczne. Ułatwiłoby to życie osobom, które będą czytać i utrzymywać ten kod.
Ostrzeżenie N5
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;
....
}
....
}
Ten fragment kodu został najprawdopodobniej uzyskany w wyniku dekompilacji. Następnie, sądząc po pozostawionym komentarzu, część niedziałającego kodu została usunięta. Jednak pozostało jeszcze kilka operacji identyfikator kursora, co również nie ma większego sensu.
Ostrzeżenie N6
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); // <=
}
....
}
Kod ten jest dość łatwy do poprawienia, wystarczy sprawdzić go trzeci raz gracz do wskaźnika zerowego lub dodaj go do treści instrukcji warunkowej. Sugerowałbym drugą opcję:
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);
}
}
....
}
Ostrzeżenie N7
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));
....
}
....
}
Możesz za jednym zamachem pozbyć się trudnej do odczytania linii kodu i rozwiązać problem ze sprawdzaniem nullptr. Sugeruję zmianę kodu w następujący sposób:
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);
....
}
....
}
Ostrzeżenie N8
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}
Kod wygląda dość dziwnie. Wydaje mi się, że wystąpiła literówka w warunku lub podczas ponownego przypisania zmiennej Nagłówek kolumnyNaciśniętyCurrentState wartości fałszywy.
Wniosek
Jak widzimy, integracja analizatora statycznego PVS-Studio z projektem TeamCity jest dość prosta. Aby to zrobić, wystarczy napisać tylko jeden mały plik konfiguracyjny. Sprawdzenie kodu pozwoli na identyfikację problemów zaraz po montażu, co pomoże je wyeliminować, gdy stopień skomplikowania i koszt zmian będą jeszcze niskie.
Jeśli chcesz udostępnić ten artykuł anglojęzycznej publiczności, skorzystaj z linku do tłumaczenia: Vladislav Stolyarov.
Źródło: www.habr.com