PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
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

Studio PVS — statyczny analizator kodu C, C++, C# i Java, zaprojektowany w celu ułatwienia zadania wyszukiwania i poprawiania różnego rodzaju błędów. Z analizatora można korzystać na systemach Windows, Linux i macOS. W tym artykule będziemy aktywnie korzystać nie tylko z samego analizatora, ale także z niektórych narzędzi z jego dystrybucji.

CLMonitor — to serwer monitorujący, który monitoruje uruchomienia kompilatora. Należy go uruchomić bezpośrednio przed rozpoczęciem tworzenia projektu. W trybie podglądania serwer przechwytuje uruchomienia wszystkich obsługiwanych kompilatorów. Warto zauważyć, że tego narzędzia można używać wyłącznie do analizy projektów C/C++.

Konwerter Plogu – narzędzie do konwersji raportów analizatora na różne formaty.

Informacje o badanym projekcie

Wypróbujmy tę funkcjonalność na praktycznym przykładzie - przeanalizujmy projekt OpenRCT2.

OtwórzRCT2 - otwarta implementacja gry RollerCoaster Tycoon 2 (RCT2), rozszerzająca ją o nowe funkcje i naprawiająca błędy. Rozgrywka koncentruje się na budowaniu i utrzymywaniu parku rozrywki zawierającego atrakcje, sklepy i obiekty. Gracz musi starać się osiągnąć zysk i utrzymać dobrą reputację parku, jednocześnie uszczęśliwiając gości. OpenRCT2 umożliwia grę zarówno w scenariuszu, jak i w trybie sandbox. Scenariusze wymagają od gracza wykonania określonego zadania w określonym czasie, natomiast Sandbox pozwala graczowi zbudować bardziej elastyczny park bez żadnych ograniczeń i finansów.

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:

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
Kliknij przycisk Utwórz projekt. Następnie wybierz opcję Ręcznie i uzupełnij pola.

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
Po naciśnięciu przycisku Stwórz, wita nas okno z ustawieniami.

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
Kliknijmy Utwórz konfigurację kompilacji.

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
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.

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
Na koniec przechodzimy do ustawień projektu.

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
Dodajmy kroki montażu, aby to zrobić kliknij: Kroki kompilacji -> Dodaj krok kompilacji.

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
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:

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
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:

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
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 artykuł:

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.

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
Teraz kliknijmy Inspekcje ogółemaby przejść do przeglądania raportu analizatora:

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
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 dokumentacja.

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

V773 [CWE-401] Zgłoszono wyjątek bez zwolnienia wskaźnika „result”. Możliwy jest wyciek pamięci. 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;
}

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

V501 Istnieją identyczne podwyrażenia „(1ULL << WIDX_MONTH_BOX)” po lewej i prawej stronie „|” 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),
  ....
};

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

V703 Dziwne jest, że pole „flagi” w klasie pochodnej „RCT12BannerElement” zastępuje pole w klasie bazowej „RCT12TileElementBase”. Sprawdź linie: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

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

V793 Dziwne, że wynik instrukcji „imageDirection / 8” jest częścią warunku. Być może należało porównać to stwierdzenie z czymś innym. libopenrct2 Wieża obserwacyjna.cpp 38

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

V587 Dziwna sekwencja przypisań tego rodzaju: A = B; B = A;. Sprawdź linie: 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;
      ....
  }
  ....
}

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

V1004 [CWE-476] Wskaźnik „player” został użyty w sposób niebezpieczny po zweryfikowaniu go pod kątem wartości nullptr. Sprawdź linie: 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);                    // <=
  }
  ....
}

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

V547 [CWE-570] Wyrażenie 'nazwa == nullptr' jest zawsze fałszywe. libopenrct2 Lista_serwerów.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));
    ....
  }
  ....
}

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

V1048 [CWE-1164] Zmiennej „ColumnHeaderPressedCurrentState” przypisano tę samą wartość. libopenrct2ui CustomListView.cpp 510

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.

PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2
Jeśli chcesz udostępnić ten artykuł anglojęzycznej publiczności, skorzystaj z linku do tłumaczenia: Vladislav Stolyarov. PVS-Studio i ciągła integracja: TeamCity. Analiza projektu Open RollerCoaster Tycoon 2.

Źródło: www.habr.com

Dodaj komentarz