Един от най-актуалните сценарии за използване на анализатора PVS-Studio е неговата интеграция с CI системи. И въпреки че анализът на проект на PVS-Studio от почти всяка система за непрекъсната интеграция може да бъде вграден само в няколко команди, ние продължаваме да правим този процес още по-удобен. PVS-Studio вече има поддръжка за конвертиране на изхода на анализатора във формат за TeamCity - TeamCity Inspections Type. Нека да видим как работи.
Информация за използвания софтуер
Информация за проучвания проект
Нека изпробваме тази функционалност на практически пример - нека анализираме проекта OpenRCT2.
регулиране
За да спестя време, вероятно ще пропусна инсталационния процес и ще започна от момента, в който сървърът на TeamCity работи на моя компютър. Трябва да отидем на: localhost:{порт, посочен по време на инсталационния процес} (в моя случай, localhost:9090) и да въведем данни за оторизация. След влизане ще бъдем посрещнати от:
Кликнете върху бутона Създаване на проект. След това изберете Ръчно и попълнете полетата.
След натискане на бутона Създаване на, ни посреща прозорец с настройки.
Да щракнем Създайте конфигурация за изграждане.
Попълнете полетата и щракнете Създаване на. Виждаме прозорец с молба да изберете система за контрол на версиите. Тъй като източниците вече са разположени локално, щракнете Пропусни.
Накрая преминаваме към настройките на проекта.
Нека добавим стъпки за сглобяване, за да направите това, щракнете върху: Стъпки на изграждане -> Добавяне на стъпка на изграждане.
Тук избираме:
- Тип бегач -> Команден ред
- Изпълнение -> Персонализиран скрипт
Тъй като ще извършим анализ по време на компилирането на проекта, сглобяването и анализът трябва да са една стъпка, така че попълнете полето Персонализиран скрипт:
По-късно ще разгледаме отделните стъпки. Важно е, че зареждането на анализатора, асемблирането на проекта, анализирането му, извеждането на отчета и форматирането му отнема само единадесет реда код.
Последното нещо, което трябва да направим, е да зададем променливите на средата, за които съм посочил някои начини за подобряване на тяхната четливост. За да направите това, нека продължим: Параметри -> Добавяне на нов параметър и добавете три променливи:
Всичко, което трябва да направите, е да натиснете бутона бягане в горния десен ъгъл. Докато проектът се сглобява и анализира, ще ви разкажа за сценария.
Директен скрипт
Първо, трябва да изтеглим най-новата дистрибуция на 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"
Междувременно сглобяването и анализът на проекта са завършени успешно, можем да отидем в раздела Проекти и убедиться в това.
Сега нека щракнем върху Общо инспекцииза да отидете на преглед на отчета на анализатора:
Предупрежденията са групирани по номера на диагностични правила. За да навигирате в кода, трябва да кликнете върху номера на реда с предупреждението. Щракването върху въпросителния знак в горния десен ъгъл ще ви отвори нов раздел с документация. Можете също да навигирате в кода, като щракнете върху номера на реда с предупреждението на анализатора. При използване е възможна навигация от отдалечен компютър SourceTreeRoot маркер. Всеки, който се интересува от този режим на работа на анализатора, може да се запознае със съответния раздел
Преглед на резултатите от анализатора
Сега, след като приключихме с разполагането и конфигурирането на компилацията, нека да разгледаме някои интересни предупреждения, открити в проекта, който разглеждаме.
Предупреждение N1
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
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
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};
Разбира се, използването на променлива с едно и също име в базовия клас и в наследника не винаги е грешка. Самата технология за наследяване обаче предполага, че всички полета на родителския клас присъстват в дъщерния клас. Като декларираме полета с едно и също име в наследника, създаваме объркване.
Предупреждение N4
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
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
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
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
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}
Кодът изглежда доста странен. Струва ми се, че има правописна грешка или в условието, или при повторното присвояване на променливата ColumnHeaderPressedCurrentState значение фалшив.
Продукция
Както виждаме, интегрирането на статичния анализатор на PVS-Studio във вашия проект TeamCity е доста просто. За да направите това, достатъчно е да напишете само един малък конфигурационен файл. Проверката на кода ще ви позволи да идентифицирате проблеми веднага след сглобяването, което ще помогне за отстраняването им, когато сложността и цената на промените са все още ниски.
Ако искате да споделите тази статия с англоезична аудитория, моля, използвайте връзката за превод: Владислав Столяров.
Източник: www.habr.com