PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Един от най-актуалните сценарии за използване на анализатора PVS-Studio е неговата интеграция с CI системи. И въпреки че анализът на проект на PVS-Studio от почти всяка система за непрекъсната интеграция може да бъде вграден само в няколко команди, ние продължаваме да правим този процес още по-удобен. PVS-Studio вече има поддръжка за конвертиране на изхода на анализатора във формат за TeamCity - TeamCity Inspections Type. Нека да видим как работи.

Информация за използвания софтуер

PVS-Студио — статичен анализатор на C, C++, C# и Java код, предназначен да улесни задачата за намиране и коригиране на различни видове грешки. Анализаторът може да се използва на Windows, Linux и macOS. В тази статия ще използваме активно не само самия анализатор, но и някои помощни програми от неговото разпространение.

CLMonitor — е сървър за наблюдение, който следи стартиранията на компилатора. Трябва да се стартира непосредствено преди да започнете да изграждате вашия проект. В режим на наблюдение сървърът ще прихване изпълнението на всички поддържани компилатори. Струва си да се отбележи, че тази помощна програма може да се използва само за анализ на C/C++ проекти.

PlogConverter – помощна програма за конвертиране на отчетите на анализатора в различни формати.

Информация за проучвания проект

Нека изпробваме тази функционалност на практически пример - нека анализираме проекта OpenRCT2.

OpenRCT2 - отворена реализация на играта RollerCoaster Tycoon 2 (RCT2), която я разширява с нови функции и коригира грешки. Играта се върти около изграждането и поддържането на увеселителен парк, включващ атракциони, магазини и съоръжения. Играчът трябва да се опита да спечели печалба и да поддържа добрата репутация на парка, като същевременно поддържа гостите доволни. OpenRCT2 ви позволява да играете както в сценарий, така и в пясъчник. Сценариите изискват от играча да изпълни конкретна задача в рамките на определено време, докато Sandbox позволява на играча да изгради по-гъвкав парк без никакви ограничения или финанси.

регулиране

За да спестя време, вероятно ще пропусна инсталационния процес и ще започна от момента, в който сървърът на TeamCity работи на моя компютър. Трябва да отидем на: localhost:{порт, посочен по време на инсталационния процес} (в моя случай, localhost:9090) и да въведем данни за оторизация. След влизане ще бъдем посрещнати от:

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Кликнете върху бутона Създаване на проект. След това изберете Ръчно и попълнете полетата.

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
След натискане на бутона Създаване на, ни посреща прозорец с настройки.

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Да щракнем Създайте конфигурация за изграждане.

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Попълнете полетата и щракнете Създаване на. Виждаме прозорец с молба да изберете система за контрол на версиите. Тъй като източниците вече са разположени локално, щракнете Пропусни.

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Накрая преминаваме към настройките на проекта.

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Нека добавим стъпки за сглобяване, за да направите това, щракнете върху: Стъпки на изграждане -> Добавяне на стъпка на изграждане.

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Тук избираме:

  • Тип бегач -> Команден ред
  • Изпълнение -> Персонализиран скрипт

Тъй като ще извършим анализ по време на компилирането на проекта, сглобяването и анализът трябва да са една стъпка, така че попълнете полето Персонализиран скрипт:

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
По-късно ще разгледаме отделните стъпки. Важно е, че зареждането на анализатора, асемблирането на проекта, анализирането му, извеждането на отчета и форматирането му отнема само единадесет реда код.

Последното нещо, което трябва да направим, е да зададем променливите на средата, за които съм посочил някои начини за подобряване на тяхната четливост. За да направите това, нека продължим: Параметри -> Добавяне на нов параметър и добавете три променливи:

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Всичко, което трябва да направите, е да натиснете бутона бягане в горния десен ъгъл. Докато проектът се сглобява и анализира, ще ви разкажа за сценария.

Директен скрипт

Първо, трябва да изтеглим най-новата дистрибуция на PVS-Studio. За целта използваме мениджъра на пакети Chocolatey. За тези, които искат да знаят повече за това, има съответния статия:

choco install pvs-studio -y

След това нека стартираме помощната програма за проследяване на изграждане на проект CLMonitor.

%CLmon% monitor –-attach

След това ще изградим проекта като променлива на средата MSB е пътят до версията на 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 и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Сега нека щракнем върху Общо инспекцииза да отидете на преглед на отчета на анализатора:

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Предупрежденията са групирани по номера на диагностични правила. За да навигирате в кода, трябва да кликнете върху номера на реда с предупреждението. Щракването върху въпросителния знак в горния десен ъгъл ще ви отвори нов раздел с документация. Можете също да навигирате в кода, като щракнете върху номера на реда с предупреждението на анализатора. При използване е възможна навигация от отдалечен компютър SourceTreeRoot маркер. Всеки, който се интересува от този режим на работа на анализатора, може да се запознае със съответния раздел документация.

Преглед на резултатите от анализатора

Сега, след като приключихме с разполагането и конфигурирането на компилацията, нека да разгледаме някои интересни предупреждения, открити в проекта, който разглеждаме.

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

V773 [CWE-401] Изключението беше хвърлено без освобождаване на указателя „резултат“. Възможно е изтичане на памет. 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;
}

Анализаторът забеляза грешка, която след динамично разпределяне на памет в Създаване на обект, когато възникне изключение, паметта не се изчиства и възниква изтичане на памет.

Предупреждение 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),
  ....
};

Малко хора, различни от статичен анализатор, биха могли да преминат този тест за внимание. Този пример за копиране и поставяне е добър точно поради тази причина.

Предупреждения N3

V703 Странно е, че полето „флагове“ в производния клас „RCT12BannerElement“ презаписва полето в базовия клас „RCT12TileElementBase“. Проверете редове: 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' е част от условието. Може би това твърдение трябваше да се сравни с нещо друго. libopenrct2 ObservationTower.cpp 38

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

Нека да разгледаме по-отблизо. Изразяване imageDirection/8 ще бъде невярно, ако imageDirection е в диапазона от -7 до 7. Втора част: (imageDirection / 8) != 3 проверки imageDirection за излизане извън диапазона: съответно от -31 до -24 и от 24 до 31. Изглежда ми доста странно да проверявам числата за включване в определен диапазон по този начин и дори ако няма грешка в тази част от кода, бих препоръчал пренаписване на тези условия, за да бъдат по-ясни. Това би направило живота много по-лесен за хората, които ще четат и поддържат този код.

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

V587 Нечетна последователност от присвоявания от този вид: A = B; B = A;. Проверете редове: 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] Указателят „player“ е използван небезопасно, след като е проверен срещу nullptr. Проверете редове: 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] Изразът „име == nullptr“ винаги е 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] На променливата „ColumnHeaderPressedCurrentState“ е присвоена същата стойност. libopenrct2ui CustomListView.cpp 510

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

Кодът изглежда доста странен. Струва ми се, че има правописна грешка или в условието, или при повторното присвояване на променливата ColumnHeaderPressedCurrentState значение фалшив.

Продукция

Както виждаме, интегрирането на статичния анализатор на PVS-Studio във вашия проект TeamCity е доста просто. За да направите това, достатъчно е да напишете само един малък конфигурационен файл. Проверката на кода ще ви позволи да идентифицирате проблеми веднага след сглобяването, което ще помогне за отстраняването им, когато сложността и цената на промените са все още ниски.

PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2
Ако искате да споделите тази статия с англоезична аудитория, моля, използвайте връзката за превод: Владислав Столяров. PVS-Studio и непрекъсната интеграция: TeamCity. Анализ на проекта Open RollerCoaster Tycoon 2.

Източник: www.habr.com

Добавяне на нов коментар