PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Një nga skenarët më aktualë për përdorimin e analizuesit PVS-Studio është integrimi i tij me sistemet CI. Dhe megjithëse analiza e një projekti PVS-Studio nga pothuajse çdo sistem integrimi i vazhdueshëm mund të ndërtohet në vetëm disa komanda, ne vazhdojmë ta bëjmë këtë proces edhe më të përshtatshëm. PVS-Studio tani ka mbështetje për konvertimin e daljes së analizuesit në një format për TeamCity - TeamCity Inspections Type. Le të shohim se si funksionon.

Informacion rreth softuerit të përdorur

Studio PVS — një analizues statik i kodeve C, C++, C# dhe Java, i krijuar për të lehtësuar detyrën e gjetjes dhe korrigjimit të llojeve të ndryshme të gabimeve. Analizuesi mund të përdoret në Windows, Linux dhe macOS. Në këtë artikull ne do të përdorim në mënyrë aktive jo vetëm vetë analizuesin, por edhe disa shërbime nga shpërndarja e tij.

CLMonitor — është një server monitorimi që monitoron nisjet e përpiluesit. Duhet të ekzekutohet menjëherë përpara se të filloni të ndërtoni projektin tuaj. Në modalitetin snooping, serveri do të përgjojë ekzekutimet e të gjithë përpiluesve të mbështetur. Vlen të përmendet se ky program mund të përdoret vetëm për të analizuar projektet C/C++.

PlogConverter – një mjet për konvertimin e raporteve të analizuesit në formate të ndryshme.

Informacion rreth projektit në studim

Le ta provojmë këtë funksionalitet në një shembull praktik - le të analizojmë projektin OpenRCT2.

OpenRCT2 - një zbatim i hapur i lojës RollerCoaster Tycoon 2 (RCT2), duke e zgjeruar atë me funksione të reja dhe duke rregulluar defektet. Loja sillet rreth ndërtimit dhe mirëmbajtjes së një parku zbavitës që përmban udhëtime, dyqane dhe objekte. Lojtari duhet të përpiqet të bëjë një fitim dhe të ruajë reputacionin e mirë të parkut duke i mbajtur mysafirët të lumtur. OpenRCT2 ju lejon të luani si në skenar ashtu edhe në sandbox. Skenarët kërkojnë që lojtari të përfundojë një detyrë specifike brenda një kohe të caktuar, ndërsa Sandbox i lejon lojtarit të ndërtojë një park më fleksibël pa asnjë kufizim apo financë.

rregullim

Për të kursyer kohë, ndoshta do ta kapërcej procesin e instalimit dhe do të filloj nga momenti kur serveri TeamCity funksionon në kompjuterin tim. Duhet të shkojmë te: localhost:{porti i specifikuar gjatë procesit të instalimit} (në rastin tim, localhost:9090) dhe të fusim të dhënat e autorizimit. Pas hyrjes do të na përshëndesin:

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Klikoni në butonin Krijo projekt. Më pas, zgjidhni Manual dhe plotësoni fushat.

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Pas shtypjes së butonit Krijo, na pret një dritare me cilësime.

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Le të klikojmë Krijo konfigurimin e ndërtimit.

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Plotësoni fushat dhe klikoni Krijo. Ne shohim një dritare që ju kërkon të zgjidhni një sistem të kontrollit të versionit. Meqenëse burimet janë vendosur tashmë në nivel lokal, klikoni Kalo.

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Më në fund kalojmë te cilësimet e projektit.

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Le të shtojmë hapat e montimit, për ta bërë këtë klikoni: Ndërtimi i hapave -> Shto hapin e ndërtimit.

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Këtu zgjedhim:

  • Lloji Runner -> Linja e Komandës
  • Ekzekuto -> Skript i personalizuar

Meqenëse do të kryejmë analiza gjatë përpilimit të projektit, montimi dhe analiza duhet të jenë një hap, prandaj plotësoni fushën Custom Script:

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Ne do të shqyrtojmë hapat individualë më vonë. Është e rëndësishme që ngarkimi i analizuesit, montimi i projektit, analizimi i tij, nxjerrja e raportit dhe formatimi i tij duhen vetëm njëmbëdhjetë rreshta kodi.

Gjëja e fundit që duhet të bëjmë është të vendosim variablat e mjedisit, të cilat unë kam përshkruar disa mënyra për të përmirësuar lexueshmërinë e tyre. Për ta bërë këtë, le të vazhdojmë: Parametrat -> Shto parametër të ri dhe shtoni tre variabla:

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Gjithçka që duhet të bëni është të shtypni butonin run në këndin e sipërm të djathtë. Ndërsa projekti është duke u mbledhur dhe analizuar, unë do t'ju tregoj për skenarin.

Direkt skenar

Së pari, duhet të shkarkojmë shpërndarjen më të fundit të PVS-Studio. Për këtë ne përdorim menaxherin e paketave Chocolatey. Për ata që duan të dinë më shumë për këtë, ekziston një përkatës artikull:

choco install pvs-studio -y

Më pas, le të nisim mjetin e përcjelljes së ndërtimit të projektit CLMonitor.

%CLmon% monitor –-attach

Më pas do të ndërtojmë projektin si variabël mjedisor MSB është rruga drejt versionit të MSBuild që duhet të ndërtoj

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

Le të fusim çelësin e hyrjes dhe licencës për PVS-Studio:

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

Pasi të përfundojë ndërtimi, ekzekutoni përsëri CLMonitor për të gjeneruar skedarë të parapërpunuar dhe analiza statike:

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

Pastaj do të përdorim një mjet tjetër nga shpërndarja jonë. PlogConverter konverton një raport nga një format standard në një format specifik për TeamCity. Falë kësaj, ne do të jemi në gjendje ta shikojmë atë drejtpërdrejt në dritaren e ndërtimit.

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

Hapi i fundit është shfaqja e raportit të formatuar në stdout, ku do të merret nga analizuesi TeamCity.

type "C:tempptest.plog_TeamCity.txt"

Kodi i plotë i skenarit:

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"

Ndërkohë, montimi dhe analiza e projektit ka përfunduar me sukses, ne mund të shkojmë në skedën projektet dhe sigurohuni për të.

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Tani le të klikojmë Inspektimet Totalipër të shkuar te shikimi i raportit të analizuesit:

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Paralajmërimet grupohen sipas numrave të rregullave diagnostikuese. Për të lundruar nëpër kod, duhet të klikoni në numrin e linjës me paralajmërimin. Klikimi në pikëpyetjen në këndin e sipërm djathtas do t'ju hapë një skedë të re me dokumentacion. Ju gjithashtu mund të lundroni nëpër kod duke klikuar në numrin e linjës me paralajmërimin e analizuesit. Lundrimi nga një kompjuter në distancë është i mundur kur përdorni SourceTreeRoot shënues. Kushdo që është i interesuar për këtë mënyrë funksionimi të analizuesit mund të njihet me seksionin përkatës dokumentacionin.

Shikimi i rezultateve të analizuesit

Tani që kemi mbaruar vendosjen dhe konfigurimin e ndërtimit, le të hedhim një vështrim në disa paralajmërime interesante që gjenden në projektin që po shikojmë.

Paralajmërim N1

V773 [CWE-401] Përjashtimi u hodh pa lëshuar treguesin 'rezultat'. Një rrjedhje memorie është e mundur. 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;
}

Analizuesi vuri re një gabim që pas shpërndarjes dinamike të kujtesës në KrijoObjekt, kur ndodh një përjashtim, memoria nuk pastrohet dhe ndodh një rrjedhje e kujtesës.

Paralajmërim N2

V501 Ka nën-shprehje identike '(1ULL << WIDX_MONTH_BOX)' në të majtë dhe në të djathtë të '|' operatori. Mashtrimet libopenrct2ui.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),
  ....
};

Pak njerëz përveç një analizuesi statik mund ta kalojnë këtë test të vëmendjes. Ky shembull copy-paste është i mirë pikërisht për këtë arsye.

Paralajmërimet N3

V703 Është e çuditshme që fusha 'flamuj' në klasën e prejardhur 'RCT12BannerElement' të mbishkruan fushën në klasën bazë 'RCT12TileElementBase'. Kontrolloni linjat: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

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

Natyrisht, përdorimi i një ndryshoreje me të njëjtin emër në klasën bazë dhe në pasardhësin nuk është gjithmonë një gabim. Megjithatë, vetë teknologjia e trashëgimisë supozon se të gjitha fushat e klasës prind janë të pranishme në klasën e fëmijës. Duke deklaruar fushat me të njëjtin emër në trashëgimtar, krijojmë konfuzion.

Paralajmërim N4

V793 Është e çuditshme që rezultati i deklaratës 'imageDirection / 8' të jetë pjesë e kushtit. Ndoshta, kjo deklaratë duhet të ishte krahasuar me diçka tjetër. libopenrct2 ObservationTower.cpp 38

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

Le të hedhim një vështrim më të afërt. Shprehje imageDirection/8 do të jetë false nëse Drejtimi i imazhit është në rangun nga -7 në 7. Pjesa e dytë: (imageDrection / 8) != 3 çeqe Drejtimi i imazhit për të qenë jashtë intervalit: nga -31 në -24 dhe nga 24 në 31, përkatësisht. Më duket mjaft e çuditshme të kontrolloj numrat për përfshirje në një gamë të caktuar në këtë mënyrë dhe, edhe nëse nuk ka gabim në këtë pjesë të kodit, unë do të rekomandoja rishkrimin e këtyre kushteve për të qenë më të qartë. Kjo do ta bënte jetën shumë më të lehtë për njerëzit që do të lexonin dhe ruanin këtë kod.

Paralajmërim N5

V587 Një sekuencë teke detyrash të këtij lloji: A = B; B = A;. Kontrolloni linjat: 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;
      ....
  }
  ....
}

Ky fragment kodi ka shumë të ngjarë të jetë marrë nga dekompilimi. Më pas, duke gjykuar nga komenti i lënë, një pjesë e kodit që nuk funksiononte u hoq. Megjithatë, kanë mbetur edhe disa operacione kursorId, të cilat gjithashtu nuk kanë shumë kuptim.

Paralajmërim N6

V1004 [CWE-476] Treguesi 'player' u përdor në mënyrë të pasigurt pasi u verifikua kundër nullptr. Kontrolloni linjat: 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);                    // <=
  }
  ....
}

Ky kod është mjaft i lehtë për t'u korrigjuar; thjesht duhet ta kontrolloni për herë të tretë lojtar në një tregues null, ose shtoni atë në trupin e deklaratës së kushtëzuar. Unë do të sugjeroja opsionin e dytë:

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);
    }
  }
  ....
}

Paralajmërim N7

V547 [CWE-570] Shprehja 'emër == nullptr' është gjithmonë false. libopenrct2 Lista e Serverit.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));
    ....
  }
  ....
}

Ju mund të hiqni qafe një linjë kodi të vështirë për t'u lexuar me një goditje dhe të zgjidhni problemin me kontrollimin e nullptr. Unë sugjeroj ndryshimin e kodit si më poshtë:

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);
    ....
  }
  ....
}

Paralajmërim N8

V1048 [CWE-1164] Ndryshores 'ColumnHeaderPressedCurrentState' iu caktua e njëjta vlerë. libopenrct2ui CustomListView.cpp 510

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

Kodi duket mjaft i çuditshëm. Më duket se ka pasur një gabim shtypi ose në kusht ose gjatë ricaktimit të ndryshores ColumnHeaderPressedCurrentState kuptimi i rremë.

Prodhim

Siç mund ta shohim, integrimi i analizuesit statik PVS-Studio në projektin tuaj TeamCity është mjaft i thjeshtë. Për ta bërë këtë, mjafton të shkruani vetëm një skedar të vogël konfigurimi. Kontrollimi i kodit do t'ju lejojë të identifikoni problemet menjëherë pas montimit, gjë që do të ndihmojë në eliminimin e tyre kur kompleksiteti dhe kostoja e ndryshimeve janë ende të ulëta.

PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2
Nëse dëshironi ta ndani këtë artikull me një audiencë anglishtfolëse, ju lutemi përdorni lidhjen e përkthimit: Vladislav Stolyarov. PVS-Studio dhe Integrimi i Vazhdueshëm: TeamCity. Analiza e projektit Open RollerCoaster Tycoon 2.

Burimi: www.habr.com

Shto një koment