یکی از جدیدترین سناریوها برای استفاده از آنالایزر PVS-Studio، ادغام آن با سیستم های CI است. و اگرچه تجزیه و تحلیل یک پروژه PVS-Studio از تقریباً هر سیستم یکپارچه سازی پیوسته را می توان تنها در چند دستور ایجاد کرد، ما همچنان این فرآیند را راحت تر می کنیم. PVS-Studio اکنون برای تبدیل خروجی تحلیلگر به قالبی برای TeamCity - TeamCity Inspections Type پشتیبانی می کند. بیایید ببینیم چگونه کار می کند.
اطلاعاتی در مورد نرم افزار مورد استفاده
اطلاعات پروژه در دست مطالعه
بیایید این قابلیت را در یک مثال عملی امتحان کنیم - بیایید پروژه OpenRCT2 را تجزیه و تحلیل کنیم.
تنظیم
به منظور صرفه جویی در زمان، احتمالاً مراحل نصب را نادیده می گیرم و از لحظه ای که سرور TeamCity را روی رایانه خود اجرا می کنم، شروع می کنم. باید به آدرس: localhost:{port specified in the install process} (در مورد من localhost:9090) برویم و داده های مجوز را وارد کنیم. پس از ورود از ما استقبال می شود:
روی دکمه Create Project کلیک کنید. بعد دستی را انتخاب کنید و فیلدها را پر کنید.
پس از فشار دادن دکمه ساختن، با پنجره ای با تنظیمات روبرو می شویم.
بیایید کلیک کنیم پیکربندی ساخت را ایجاد کنید.
فیلدها را پر کرده و کلیک کنید ساختن. پنجره ای را می بینیم که از شما می خواهد یک سیستم کنترل نسخه را انتخاب کنید. از آنجایی که منابع از قبل به صورت محلی قرار دارند، کلیک کنید پرش.
در نهایت به تنظیمات پروژه می رویم.
بیایید مراحل اسمبلی را اضافه کنیم، برای انجام این کار کلیک کنید: مراحل ساخت -> مرحله ساخت را اضافه کنید.
در اینجا ما انتخاب می کنیم:
- نوع Runner -> Command Line
- اجرا -> اسکریپت سفارشی
از آنجایی که در حین تدوین پروژه تحلیل انجام خواهیم داد، مونتاژ و آنالیز باید یک مرحله باشد، پس فیلد را پر کنید اسکریپت سفارشی:
بعداً مراحل جداگانه را بررسی خواهیم کرد. مهم است که بارگذاری تحلیلگر، مونتاژ پروژه، تجزیه و تحلیل آن، خروجی گزارش و قالب بندی آن تنها به یازده خط کد نیاز دارد.
آخرین کاری که باید انجام دهیم این است که متغیرهای محیطی را تنظیم کنیم، که من روش هایی را برای بهبود خوانایی آنها بیان کرده ام. برای انجام این کار، به ادامه مطلب می پردازیم: پارامترها -> افزودن پارامتر جدید و سه متغیر اضافه کنید:
تنها کاری که باید انجام دهید این است که دکمه را فشار دهید دویدن در گوشه سمت راست بالا در حالی که پروژه در حال مونتاژ و تجزیه و تحلیل است، من در مورد فیلمنامه به شما خواهم گفت.
اسکریپت مستقیم
ابتدا باید آخرین توزیع PVS-Studio را دانلود کنیم. برای این کار از Chocolatey package manager استفاده می کنیم. برای کسانی که می خواهند در این مورد بیشتر بدانند، یک متناظر وجود دارد
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"
آخرین مرحله نمایش گزارش فرمت شده است 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
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
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;
....
};
البته استفاده از متغیری با همین نام در کلاس پایه و در decendant همیشه خطا نیست. با این حال، خود فناوری وراثت فرض میکند که تمام فیلدهای کلاس والد در کلاس فرزند وجود دارد. با اعلام فیلدهایی با همین نام در وارث، سردرگمی ایجاد می کنیم.
هشدار N4
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}
بیایید نگاه دقیق تری بیندازیم. اصطلاح imageDirection/8 دروغ خواهد بود اگر جهت تصویر در محدوده 7- تا 7 قرار دارد. بخش دوم: (imageDirection / 8) != 3 چک ها جهت تصویر برای خارج از محدوده: به ترتیب از 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;
....
}
....
}
این قطعه کد به احتمال زیاد با دیکامپایل به دست آمده است. سپس با قضاوت بر اساس کامنت گذاشته شده، بخشی از کد ناکارآمد حذف شد. با این حال، هنوز چند عملیات باقی مانده است نشانگر مکان نما، که همچنین چندان منطقی نیست.
هشدار 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 بسیار ساده است. برای این کار کافی است فقط یک فایل پیکربندی کوچک بنویسید. بررسی کد به شما این امکان را می دهد که مشکلات را بلافاصله پس از مونتاژ شناسایی کنید، که به رفع آنها در زمانی که پیچیدگی و هزینه تغییرات هنوز کم است کمک می کند.
اگر می خواهید این مقاله را با مخاطبان انگلیسی زبان به اشتراک بگذارید، لطفاً از پیوند ترجمه استفاده کنید: Vladislav Stolyarov.
منبع: www.habr.com