
Един от най-актуалните сценарии за използване на анализатора PVS-Studio е неговата интеграция с CI системи. И макар анализът на проекта PVS-Studio да може да бъде вграден в почти всяка система за непрекъсната интеграция с само няколко команди, ние продължаваме да правим този процес още по-удобен. В PVS-Studio беше добавена поддръжка за преобразуване на изхода на анализатора в формат за TeamCity — TeamCity Inspections Type. Нека видим как работи това.
Информация за използваното софтуерно осигуряване
— статичен анализатор за C, C++, C# и Java код, предназначен да улесни задачата по търсене и коригиране на различни видове грешки. Анализаторът може да се използва в Windows, Linux и macOS. В тази статия ще използваме не само самия анализатор, но и някои утилити от неговия дистрибутив.
— представлява сървър за мониторинг, който следи стартиранията на компилатори. Трябва да стартирате тази утилита непосредствено преди започване на изграждането на вашия проект. В режим на наблюдение сървърът ще прихваща стартирания на всички поддържани компилатори. Струва си да се отбележи, че тази утилита може да се използва само за анализ на C/C++ проекти.
– утилита за конвертиране на отчета на анализатора в различни формати.
Информация за изследвания проект
Нека опитаме тази функционалност на практическо ниво – да анализираме проекта OpenRCT2.
— отворена реализация на играта RollerCoaster Tycoon 2 (RCT2), която я разширява с нови функции и коригира грешки. Игровият процес се върти около изграждането и поддържането на парк за развлечения, в който има атракции, магазини и обекти. Играчът трябва да се постарае да реализира печалба и да поддържа добра репутация на парка, като същевременно държи гостите щастливи. OpenRCT2 позволява игра както в сценарий, така и в пясъчник. Сценариите изискват от играча да изпълни определена задача в зададено време, докато пясъчникът позволява на играча да построи по-гъвкав парк без ограничения или финансови ограничения.
Настройка
С цел икономия на време, вероятно ще пропусна процеса на инсталиране и ще започна от момента, когато имам сървър TeamCity стартиран на компютъра си. Трябва да отидем на: localhost:{указаният в процеса на инсталиране порт}(в моя случай, localhost:9090) и да въведем данни за удостоверяване. След влизането ще бъдем посрещнати от:

Ще натиснем бутона Create Project. Следващата стъпка е да изберем Manually и да попълним полетата.

След натискане на бутона Create, посреща ни прозорец с настройки.

Натиснете Създаване на конфигурация за изграждане.

Попълваме полетата, натискаме Create. Ние виждаме прозорец с предложение за избор на система за контрол на версиите. Тъй като изходният код вече е локално, натискаме Пропусни.

Накрая преминаваме към настройките на проекта.

Добавяме стъпки за изграждане, за целта натискаме: Build steps -> Add build step.

Тук ще изберем:
- Runner type -> Command Line
- Run -> Custom Script
Тъй като ще провеждаме анализ по време на компилацията на проекта, изграждането и анализът трябва да бъдат една стъпка, затова попълваме полето Custom Script:

На отделни стъпки ще се спрем по-късно. Важно е зареждането на анализатора, изграждането на проекта, неговият анализ, генерирането на отчета и форматирането му да отнеме само единадесет реда код.
Последното, което ни остава да направим, е да зададем променливи на средата, за които обозначих някои пътища за подобряване на четимостта им. За целта преминаваме на: Parameters -> Add new parameter и добавяме три променливи:

Остава да натиснем бутона Run в горния десен ъгъл. Докато протича изграждането и анализът на проекта, ще ви разкажа за скрипта.
Самият скрипт
За начало трябва да изтеглим последната версия на PVS-Studio. За целта използваме пакетния мениджър Сhocolatey. За онези, които искат да научат повече за това, има съответна :
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"Последното действие е да изведем форматирания отчет в stdout, където парсерът 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"Междувременно, сборката и анализ проекта успешно завършени, можем да преминем към раздела Проекти и да се уверим в това.

Сега кликнете на Общо проверки, за да преминете към прегледа на доклада на анализатора:

Предупрежденията са групирани по номера на диагностичните правила. За навигация в кода, кликнете върху номера на реда с предупреждение. Натискането на знака въпрос в горния десен ъгъл ще отвори нов раздел с документация. Можете също да навигирате в кода, като кликнете на номера на реда с предупреждението на анализатора. Навигацията от дистанционен компютър е възможна при използване на SourceTreeRoot маркер. Този, на когото му интересува този режим на работа на анализатора, може да се запознае с съответния раздел .
Преглед на резултатите от анализатора
След като завършихме с разгръщането и настройката на сборката, предлагам да разгледаме някои интересни предупреждения, открити в разглеждания проект.
Предупреждение N1
[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
Има идентични подизрази '(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
Странно е, че полето 'flags' в производния клас '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
Странно е, че резултатът от израза 'imageDirection / 8' е част от условието. Може би това изречение е трябвало да бъде сравнено с нещо друго. 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
Странна последователност от присвоявания от този вид: 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
[CWE-476] Указателят 'player' беше използван небезопасно след проверка срещу nullptr. Проверете редове: 2085, 2094. libopenrct2 Network.cpp 2094
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Добавяне на нов играч.
player = AddPlayer("", "");
if (player) // Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}Този код е доста лесен за поправка, нужно е или за трети път да проверим player за нулев указател, или да го внесем в тялото на условния оператор. Предлагам втория вариант:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Добавяне на нов играч.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}Предупреждение N7
[CWE-570] Изразът 'name == nullptr' винаги е неверен. 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));
....
}
....
}Може да се избавите от трудночитаемата линия от код с едно движение и да разрешите проблема с проверката на nullptr. Предлагам да промените кода по следния начин:
std::optional ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = "";
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}Предупреждение N8
[CWE-1164] Променливата 'ColumnHeaderPressedCurrentState' беше присвоена на същата стойност. libopenrct2ui CustomListView.cpp 510
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}Кодът изглежда доста странно. Считам, че е ставало дума за печатна грешка, или в условието, или при повторното присвояване на променливата ColumnHeaderPressedCurrentState стойности неверно.
Извод
Както виждаме, интегрирането на статичния анализатор PVS-Studio в проекта си в TeamCity е доста просто. За целта е достатъчно да напишете само един малък конфигурационен файл. Кодът ще бъде проверен, което ще позволи откриването на проблеми веднага след компилацията, помагайки да се отстранят те, докато сложността и цената на поправките все още са малки.
Ако искате да споделите тази статия с англоговорящата аудитория, моля, използвайте връзката към превода: Владислав Столяров. .
Източник: habr.com
