PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Ena iz najbolj relevantnih scenarijev uporabe analizerja PVS-Studio je njegova integracija s CI sistemi. Čeprav je analizo projekta PVS-Studio mogoče integrirati v katerikoli continuous integration sistem z le nekaj ukazi, ta proces še naprej olajšujemo. V PVS-Studio je dodana podpora za pretvorbo izhoda analizerja v format za TeamCity — TeamCity Inspections Type. Oglejmo si, kako to deluje.

Informacije o uporabljeni programski opremi

PVS-Studio — statični analizer C, C++, C# in Java kode, namenjen olajšanju iskanja in odpravljanja različnih napak. Analizer se lahko uporablja v Windows, Linux in macOS. V tem članku bomo aktivno uporabljali ne samo sam analizer, temveč tudi nekatere pripomočke iz njegove distribucije.

CLMonitor — predstavlja strežnik za monitoring, ki spremlja zagonih prevajalnikov. Zagnati ga je treba neposredno pred začetkom sestavljanja vašega projekta. V načinu spremljanja bo strežnik zajemal zagone vseh podprtih prevajalnikov. Omeniti velja, da se tega pripomočka lahko uporablja samo za analizo C/C++ projektov.

PlogConverter – pripomoček za pretvorbo poročila analizerja v različne formate.

Informacije o raziskovanem projektu

Poglejmo to funkcionalnost na praktičnem primeru – analizirajmo projekt OpenRCT2.

OpenRCT2 — odprta implementacija igre RollerCoaster Tycoon 2 (RCT2), ki razširja igro z novimi funkcijami in odpravlja napake. Igralni postopek se vrti okoli gradnje in vzdrževanja zabaviščnega parka, v katerem so atrakcije, trgovine in objekti. Igralec mora poskrbeti, da bo park uspešen in ohranjati dobro reputacijo, hkrati pa ohranjati goste srečne. OpenRCT2 omogoča igranje tako v scenarijih kot v peskovniku. Scenariji zahtevajo, da igralec opravi določeno nalogo v določenem času, medtem ko peskovnik omogoča igralcu, da gradi bolj prilagodljiv park brez kakršnih koli omejitev ali financ.

Setup

Da bi prihranil čas, bom verjetno preskočil postopek namestitve in začel s trenutkom, ko imam na računalniku zagnan strežnik TeamCity. Moramo preiti na: localhost:{naveden v postopku namestitve port}(v mojem primeru, localhost:9090) in vpisati prijavne podatke. Po prijavi nas bo sprejel:

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Pritisnite gumb Create Project. Zatem izberite Manually in izpolnite polja.

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Po pritisku na gumb Stworzyć, nas pozdravi okno z nastavitvami.

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Pritisnite Ustvari konfiguracijo gradnje.

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Izpolnimo polja, pritisnemo Stworzyć. Vidimo okno z možnostjo izbire sistema za nadzor različic. Ker so izvorne datoteke že lokalno shranjene, pritisnemo Skip.

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Nazadnje, preidemo na nastavitve projekta.

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Dodajmo korake gradnje, za to pritisnemo: Koraki gradnje -> Dodaj korak gradnje.

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Tukaj izberemo:

  • Tip izvajalca -> Ukazna vrstica
  • Izvedi -> Po meri skript

Ker bomo izvajali analizo med kompilacijo projekta, morata biti gradnja in analiza en korak, zato izpolnimo polje Po meri skript:

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
O ločenih korakih se bomo ustavili kasneje. Pomembno je, da nalaganje analizatorja, gradnja projekta, njegova analiza, izhod poročila in njegovo oblikovanje omogoča le enajst vrstic kode.

Nazadnje, kar moramo narediti, je nastaviti okoljske spremenljivke, s katerimi sem opredelil nekatere poti za izboljšanje njihove berljivosti. Zato pojdimo na: Parametri -> Dodaj novo spremenljivko in dodajmo tri spremenljivke:

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Ostane še pritisniti gumb Zaženi v zgornjem desnem kotu. Medtem ko poteka gradnja in analiza projekta, vam bom predstavil skript.

Skript

Najprej moramo prenesti svežo različico PVS-Studio. Za to uporabimo upravljalnik paketov Chocolatey. Tisti, ki bi želeli izvedeti več o tem, imajo ustrezno članek:

choco install pvs-studio -y

Nato zaženemo orodje za sledenje gradnji projekta CLMonitor.

%CLmon% monitor –-attach

Nato bomo izvedli gradnjo projekta, kot spremenljivka okolja MSB predstavlja pot do različice MSBuild, ki jo potrebujem za gradnjo

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

Vnesimo uporabniško ime in licenčni ključ PVS-Studio:

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

Po končanju gradnje ponovno zaženemo CLMonitor za generiranje predobdelanih datotek in izvedbo statične analize:

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

Nato bomo uporabili še en pripomoček iz našega distribucijskega paketa. PlogConverter pretvori poročilo iz standardnega v obliko, specifično za TeamCity. Tako bomo lahko pogledali neposredno v oknu gradnje.

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

Kot zadnji korak bomo izpisali oblikovano poročilo v stdout, kjer ga bo pobral parser TeamCity.

type "C:tempptest.plog_TeamCity.txt"

Celoten kod skripta:

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"

В то же время, сборка и анализ проекта успешно завершились, мы можем перейти на вкладку Projekti и убедиться в этом.

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Теперь кликнем на Итоговые инспекции, чтобы перейти к просмотру отчета анализатора:

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Предупреждения сгруппированы по номерам диагностических правил. Для навигации по коду нужно кликнуть на номер строки с предупреждением. Нажатие на знак вопроса в правом верхнем углу откроет новую вкладку с документацией. Также можно осуществить навигацию по коду, кликнув на номер строки с предупреждением анализатора. Навигация с удаленного компьютера возможна при использовании SourceTreeRoot маркера. Тот, кого интересует данный режим работы анализатора, может ознакомиться с соответствующим разделом dokumenti.

Просмотр результатов работы анализатора

После того, как мы закончили с развертыванием и настройкой сборки, предлагаю посмотреть на некоторые интересные предупреждения, обнаруженные в исследуемом проекте.

Предупреждение N1

V773 [CWE-401] Исключение было выброшено без освобождения указателя 'result'. Возможна утечка памяти. libopenrct2 ObjectFactory.cpp 443

Object* CreateObjectFromJson(....)
{
  Object* result = nullptr;
  ....
  result = CreateObject(entry);
  ....
  if (readContext.WasError())
  {
    throw std::runtime_error("Объект содержит ошибки");
  }
  ....
}

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("Недопустимый тип объекта");
  }
  return result;
}

Анализатор заметил ошибку, заключающуюся в том, что после динамического выделения памяти в CreateObject, при возникновении исключения память не очищается, соответственно, возникает утечка памяти.

Предупреждение N2

V501 Есть идентичные подвыражения '(1ULL << WIDX_MONTH_BOX)' слева и справа от оператора '|'. 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),
  ....
};

Malo kdo, razen statičnega analizatorja, bi opravil ta test pozornosti. Ta primer kopipejsta je dober prav zaradi tega.

Opozorila N3

V703 Nenavadno je, da polje 'flags' v podrazredu 'RCT12BannerElement' prepisuje polje v osnovnem razredu 'RCT12TileElementBase'. Preverite vrstice: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

struct RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};
struct rct1_peep : RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};

Seveda, uporaba spremenljivke z istim imenom v osnovnem razredu in v podrazredu ni nujno napaka. Vendar pa sama tehnologija dedovanja predpostavlja prisotnost vseh polj osnovnega razreda v podrazredu. Če v podrazredu razglasimo polja z enakim imenom, vnašamo zmedo.

Opozorilo N4

V793 Nenavadno je, da je rezultat izjave 'imageDirection / 8' del pogoja. Morda bi bilo to izjavo treba primerjati z nečim drugim. libopenrct2 ObservationTower.cpp 38

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

Poglejmo malce podrobneje. Izraz imageDirection / 8 bo false v primeru, če imageDirection je v razponu od -7 do 7. Drugi del: (imageDirection / 8) != 3 preverja imageDirection da je zunaj razpona: od -31 do -24 in od 24 do 31. Zdi se mi precej nenavadno preverjati števila na vstop v določen razpon na ta način in, tudi če v tem delu kode ni napake, bi priporočal, da te pogoje prepišete na bolj jasne. To bi bistveno olajšalo življenje ljudem, ki bodo brali in vzdrževali to kodo.

Opozorilo N5

V587 Nenavadna zaporedja dodelitev te vrste: A = B; B = A;. Preverite vrstice: 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;
      ....
  }
  ....
}

Ta koda, ki jo smo dobili, temelji na dekompilaciji. Glede na preostali komentar je bil odstranjen del nefunkcionalne kode. Vendar pa je ostalo nekaj operacij nad cursorId, ki prav tako nimajo posebnega smisla.

Opozorilo N6

V1004 [CWE-476] Kazalec 'player' je bil po preverjanju proti nullptr uporabljen na nevaren način. Preverite vrstice: 2085, 2094. libopenrct2 Network.cpp 2094

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

Ta koda je precej enostavna za popraviti, potrebno je ali tretjič preveriti player za ničelni kazalec, ali ga vključiti v telo pogojnega stavka. Predlagam drugo možnost:

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Dodaj novega igralca.
    player = AddPlayer("", "");
    if (player)
    {
      *player = pendingPlayer;
      if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
      {
        _serverConnection->Player = player;
      }
      newPlayers.push_back(player->Id);
    }
  }
  ....
}

Opozorilo N7

V547 [CWE-570] Izraz 'name == nullptr' je vedno napačen. 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));
    ....
  }
  ....
}

Z enim potezom lahko odpravimo nerazumljivo vrstico kode in rešimo težavo s preverjanjem na nullptr. Predlagam, da spremenimo kodo na naslednji način:

std::optional ServerListEntry::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    name = "";
    ....
  }
  else
  {
    ....
    entry.name = json_string_value(name);
    ....
  }
  ....
}

Opozorilo N8

V1048 [CWE-1164] Spremenljivka 'ColumnHeaderPressedCurrentState' je bila dodeljena isti vrednosti. libopenrct2ui CustomListView.cpp 510

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

Koda se zdi precej nenavadna. Zdi se mi, da je prišlo do tipkarskega napaka bodisi v pogoju bodisi pri ponovnem dodeljevanju spremenljivke ColumnHeaderPressedCurrentState pomeni false.

Izlaz

Kot vidimo, je integracija statičnega analizatorja PVS-Studio v projekt na TeamCity precej preprosta. Potrebno je le napisati en majhen konfiguracijski datoteko. Pregled kode omogoča odkrivanje težav takoj po gradnji, kar pomaga pri njihovem odpravljanju, ko so težave še majhne in stroški popravila nizki.

PVS-Studio in Continuous Integration: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2
Če želite to besedilo deliti z angleško govorečo publiko, vas prosim, da uporabite povezavo na prevod: Vladislav Stolyarov. PVS-Studio in neprekinjena integracija: TeamCity. Analiza projekta Open RollerCoaster Tycoon 2.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster