PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Адзін з самых актуальных сцэнарыяў выкарыстання аналізатара PVS-Studio – яго інтэграцыя з CI сістэмамі. І хоць аналіз праекту PVS-Studio практычна з-пад любой continuous integration сістэмы можна ўбудаваць усяго ў некалькі каманд, мы працягваем рабіць гэты працэс яшчэ зручней. У PVS-Studio з'явілася падтрымка пераўтварэнні высновы аналізатара ў фармат для TeamCity – TeamCity Inspections Type. Давайце паглядзім, як гэта працуе.

Інфармацыя аб выкарыстоўваным ПЗ

PVS-студыя - Статычны аналізатар З, З++, C# і Java кода, прызначаны для палягчэння задачы пошуку і выпраўленні рознага роду памылак. Аналізатар можна выкарыстоўваць у Windows, Linux і macOS. У дадзеным артыкуле мы будзем актыўна выкарыстоўваць не толькі сам аналізатар, але і некаторыя ўтыліты з яго дыстрыбутыва.

CLMonitor - Уяўляе сабой сервер маніторынгу, які ажыццяўляе адсочванне запускаў кампілятараў. Яго неабходна запусціць непасрэдна перад пачаткам зборкі вашага праекту. У рэжыме адсочвання сервер будзе перахапляць запускі ўсіх падтрымліваемых кампілятараў. Варта адзначыць, што дадзеную ўтыліту можна выкарыстоўваць толькі для аналізу C/З++ праектаў.

PlogConverter – утыліта для канвертацыі справаздачы аналізатара ў розныя фарматы.

Інфармацыя аб доследным праекце

Давайце паспрабуем дадзеную функцыянальнасць на практычным прыкладзе - прааналізуем праект OpenRCT2.

OpenRCT2 - Адкрытая рэалізацыя гульні RollerCoaster Tycoon 2 (RCT2), якая пашырае яе новымі функцыямі і выпраўляе памылкі. Гульнявы ​​працэс круціцца вакол будаўніцтва і зместу парку забаў, у якім знаходзяцца атракцыёны, крамы і аб'екты. Гулец павінен паспрабаваць атрымаць прыбытак і падтрымліваць добрую рэпутацыю парка, захоўваючы пры гэтым гасцей шчаслівымі. OpenRCT2 дазваляе гуляць як у сцэнары, так і ў пясочніцы. Сцэнары патрабуюць, каб гулец выканаў пэўную задачу ва ўстаноўлены час, у той час як пясочніца дазваляе гульцу пабудаваць больш гнуткі парк без якіх-небудзь абмежаванняў ці фінансаў.

Настройка

У мэтах эканоміі часу я, мабыць, апушчу працэс усталёўкі і пачну з таго моманту, калі ў мяне на кампутары запушчаны сервер TeamCity. Нам трэба перайсці: localhost:{паказаны падчас усталёўкі порт}(у маім выпадку, localhost:9090) і ўвесці дадзеныя для аўтарызацыі. Пасля ўваходу нас сустрэне:

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Націснем кнопку Create Project. Далей выберам Manually, запоўнім палі.

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Пасля націску на кнопку Ствараць, нас сустракае акно з наладамі.

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Націснем Create build configuration.

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Запаўняем палі, націскаем Ствараць. Мы бачым акно з прапановай выбару сістэмы кантролю версій. Бо зыходнікі ўжо ляжаць лакальна, ціснем Прапускаць.

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Нарэшце, мы пераходзім да налад праекту.

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Дадамо крокі зборкі, для гэтага ціснем: Build steps -> Add build step.

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Тут выберам:

  • Runner type -> Command Line
  • Run -> Custom Script

Бо мы будзем праводзіць аналіз падчас кампіляцыі праекту, зборка і аналіз павінны быць адным крокам, таму запоўнім поле карыстацкі скрыпт:

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
На асобных кроках мы спынімся пазней. Важна, каб загрузка аналізатара, зборка праекту, яго аналіз, выснова справаздачы і яго фарматаванне заняло ўсяго адзінаццаць радкоў кода.

Апошняе, што нам трэба зрабіць, - усталяваць зменныя асяроддзі, якімі я абазначыў некаторыя шляхі для паляпшэння іх чытэльнасці. Для гэтага пяройдзем: Parameters -> Add new parameter і дадамо тры зменныя:

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Застаецца націснуць на кнопку прагон у правым верхнім куце. Пакуль ідзе зборка і аналіз праекта раскажу вам пра скрыпце.

Непасрэдна скрыпт

Для пачатку нам трэба выпампаваць свежы дыстрыбутыў PVS-Studio. Для гэтага мы выкарыстоўваем пакетны мэнэджар Сhocolatey. Для тых, хто хоча даведацца пра гэта падрабязней, ёсць адпаведная артыкул:

choco install pvs-studio -y

Далей запусцім утыліту адсочвання зборкі праекту CLMonitor.

%CLmon% monitor –-attach

Потым зробім зборку праекту, у якасці зменнай асяроддзя МСБ выступае шлях да патрэбнай мне для зборкі версіі MSBuild

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

Увядзем лагін і ключ ліцэнзіі PVS-Studio:

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

Пасля завяршэння зборкі яшчэ раз запусцім CLMonitor для генерацыі препроцессированных файлаў і статычнага аналізу:

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

Пасля скарыстаемся яшчэ адной утылітай з нашага дыстрыбутыва. PlogConverter пераўтворыць справаздачу са стандартнага ў спецыфічны для TeamCity фармат. Дзякуючы гэтаму мы зможам паглядзець яго прама ў акне зборкі.

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

Апошнім дзеяннем выведзем фарматаваную справаздачу ў выбіты, дзе яго падхопіць парсер TeamCity.

type "C:tempptest.plog_TeamCity.txt"

Поўны код скрыпту:

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"

Тым часам, зборка і аналіз праекта паспяхова завяршыліся, мы можам перайсці на ўкладку праектаў і пераканацца ў гэтым.

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Цяпер клікнем на Inspections Total, Каб перайсці да прагляду справаздачы аналізатара:

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Папярэджанні згрупаваны па нумарах дыягнастычных правілаў. Для ажыццяўлення навігацыі па кодзе трэба клікнуць на нумар радка з папярэджаннем. Націск на знак пытання ў правым верхнім куце адкрые вам новую ўкладку з дакументацыяй. Таксама можна ажыццявіць навігацыю па кодзе, націснуўшы на нумар радка з папярэджаннем аналізатара. Навігацыя з выдаленага кампутара магчымая пры ўжыванні SourceTreeRoot маркера. Той, каму цікавы дадзены рэжым працы аналізатара, можа азнаёміцца ​​з якая адпавядае часткай дакументацыі.

Прагляд вынікаў працы аналізатара

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

Папярэджанне N1

V773 [CWE-401] Exception thrown without releasing the 'result' pointer. A memory leak is possible. 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;
}

Аналізатар заўважыў памылку, якая заключаецца ў тым, што пасля дынамічнага вылучэння памяці ў CreateObject, пры ўзнікненні выключэння памяць не чысціцца, адпаведна, узнікае ўцечка памяці.

Папярэджанне N2

V501 Ёсць падобныя sub-expressions '(1ULL << WIDX_MONTH_BOX)' у лівую і правы '|' 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),
  ....
};

Мала хто, акрамя статычнага аналізатара, змог бы прайсці гэты тэст на ўважлівасць. Дадзены прыклад капіпасты добры менавіта гэтым.

Папярэджанні N3

V703 Гэта 's odd, што 'flags' field in derivated class 'RCT12BannerElement' пераўтварае field in base class 'RCT12TileElementBase'. Check lines: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

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

Вядома, выкарыстанне зменнай з адным імем у базавым класе і ў спадчынніку далёка не заўсёды з'яўляецца памылкай. Аднак тэхналогія ўспадкоўвання сама па сабе мяркуе наяўнасць усіх палёў бацькоўскага класа ў даччыным. Абвясціўшы ж у спадчынніку поля з такім жа імем, мы ўносім блытаніну.

Папярэджанне N4

V793 Гэта кропка, якая з'яўляецца вынікам 'imageDirection / 8'. Perhaps, гэты стан павінен быць цяжарна compared with something else. libopenrct2 ObservationTower.cpp 38

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

Давайце разбяромся падрабязней. Выраз imageDirection / 8 будзе false у тым выпадку, калі imageDirection знаходзіцца ў дыяпазоне ад -7 да 7. Другая частка: (imageDirection / 8)! = 3 правярае imageDirection на знаходжанне па-за дыяпазонам: ад -31 да -24 і ад 24 да 31 адпаведна. Мне здаецца даволі дзіўным правяраць лічбы на ўваходжанне ў пэўны дыяпазон такім спосабам і, нават калі ў дадзеным фрагменце кода няма памылкі, я б рэкамендаваў перапісаць дадзеныя ўмовы на больш відавочныя. Гэта істотна спрасціла б жыццё людзям, якія будуць чытаць і падтрымліваць гэты код.

Папярэджанне N5

V587 An odd sequence of assignments of this kind: A = B; B = A;. Check lines: 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;
      ....
  }
  ....
}

Дадзены фрагмент кода, хутчэй за ўсё, быў атрыманы шляхам дэкампіляцыі. Затым, мяркуючы па пакінутым каментары, была выдаленая частка непрацоўнага кода. Аднак засталася пара аперацый над cursorId, якія таксама не нясуць асаблівага сэнсу.

Папярэджанне N6

V1004 [CWE-476] The 'player' pointer быў выкарыстаны павольна пасля таго, як быў зняволены да nullptr. Check lines: 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);                    // <=
  }
  ....
}

Дадзены код паправіць даволі проста, трэба ці трэці раз правяраць гулец на нулявы паказальнік, альбо ўнесці яго ў цела ўмоўнага аператара. Я б прапанаваў другі варыянт:

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

Папярэджанне N7

V547 [CWE-570] Expression 'name == nullptr' is always false. libopenrct2 ServerList.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));
    ....
  }
  ....
}

Можна адным махам пазбавіцца ад цяжкачытаемага радка кода і вырашыць праблему з праверкай на nullptr. Прапаную змяніць код наступным чынам:

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

Папярэджанне N8

V1048 [CWE-1164] The 'ColumnHeaderPressedCurrentState' variable быў акцызаваны той жа. libopenrct2ui CustomListView.cpp 510

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

Код выглядае даволі дзіўна. Мне здаецца, мела месца быць памылка друку або ва ўмове, або пры паўторным прысваенні зменнай ColumnHeaderPressedCurrentState значэння ілжывы.

Выснова

Як мы бачым, інтэграваць статычны аналізатар PVS-Studio у свой праект на TeamCity даволі проста. Для гэтага дастаткова напісаць усяго адзін маленькі файл канфігурацыі. Праверка кода ж дазволіць выяўляць праблемы адразу пасля зборкі, што дапаможа ўстараняць іх тады, калі складанасць і кошт правак яшчэ малыя.

PVS-Studio і Continuous Integration: TeamCity. Аналіз праекта Open RollerCoaster Tycoon 2
Калі хочаце падзяліцца гэтым артыкулам з англамоўнай аўдыторыяй, то прашу выкарыстаць спасылку на пераклад: Vladislav Stolyarov. PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 праект.

Крыніца: habr.com

Дадаць каментар