أحد السيناريوهات الأكثر حداثة لاستخدام محلل PVS-Studio هو تكامله مع أنظمة CI. وعلى الرغم من أن تحليل مشروع PVS-Studio من أي نظام تكامل مستمر تقريبًا يمكن دمجه في عدد قليل من الأوامر، إلا أننا نواصل جعل هذه العملية أكثر ملاءمة. يتمتع PVS-Studio الآن بدعم لتحويل مخرجات المحلل إلى تنسيق TeamCity - نوع عمليات التفتيش TeamCity. دعونا نرى كيف يعمل.
معلومات عن البرامج المستخدمة
معلومات عن المشروع قيد الدراسة
لنجرب هذه الوظيفة بمثال عملي - دعنا نحلل مشروع OpenRCT2.
تعديل
لتوفير الوقت، ربما سأتخطى عملية التثبيت وأبدأ من اللحظة التي يكون فيها خادم TeamCity قيد التشغيل على جهاز الكمبيوتر الخاص بي. نحتاج إلى الانتقال إلى: localhost:{port المحدد أثناء عملية التثبيت} (في حالتي، 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 علامة. يمكن لأي شخص مهتم بهذا الوضع من تشغيل المحلل التعرف على القسم المقابل
عرض نتائج المحلل
الآن وبعد أن انتهينا من نشر البنية وتكوينها، فلنلقي نظرة على بعض التحذيرات المثيرة للاهتمام الموجودة في المشروع الذي ننظر إليه.
تحذير رقم 1
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، عند حدوث استثناء، لا يتم مسح الذاكرة، ويحدث تسرب للذاكرة.
تحذير رقم 2
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),
....
};
قليل من الأشخاص، باستثناء المحلل الثابت، يمكنهم اجتياز اختبار الانتباه هذا. يعد مثال النسخ واللصق هذا جيدًا لهذا السبب بالتحديد.
تحذيرات رقم 3
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};
بالطبع، استخدام متغير يحمل نفس الاسم في الفئة الأساسية وفي الفئة التابعة لا يعد خطأً دائمًا. ومع ذلك، تفترض تقنية الميراث نفسها أن جميع حقول الفئة الأصلية موجودة في الفئة الفرعية. من خلال الإعلان عن الحقول التي تحمل نفس الاسم في الوريث، فإننا نخلق ارتباكًا.
تحذير رقم 4
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}
دعونا نلقي نظرة فاحصة. تعبير اتجاه الصورة/8 سيكون كاذبا إذا imageDirection يقع في المدى من -7 إلى 7. الجزء الثاني: (إتجاه الصورة / 8) != 3 الفحوصات imageDirection لكونها خارج النطاق: من -31 إلى -24 ومن 24 إلى 31 على التوالي. يبدو غريبًا جدًا بالنسبة لي التحقق من الأرقام لتضمينها في نطاق معين بهذه الطريقة، وحتى إذا لم يكن هناك خطأ في هذا الجزء من التعليمات البرمجية، فإنني أوصي بإعادة كتابة هذه الشروط لتكون أكثر وضوحًا. وهذا من شأنه أن يجعل الحياة أسهل بكثير للأشخاص الذين يقرؤون هذا الرمز ويحافظون عليه.
تحذير رقم 5
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;
....
}
....
}
على الأرجح تم الحصول على جزء التعليمات البرمجية هذا عن طريق إلغاء الترجمة. ثم، انطلاقا من التعليق المتبقي، تمت إزالة جزء من التعليمات البرمجية غير العاملة. ومع ذلك، لا تزال هناك بضع عمليات متبقية معرف المؤشر، وهو أيضًا ليس له معنى كبير.
تحذير رقم 6
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);
}
}
....
}
تحذير رقم 7
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);
....
}
....
}
تحذير رقم 8
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}
يبدو الرمز غريبًا جدًا. يبدو لي أنه كان هناك خطأ مطبعي سواء في الشرط أو عند إعادة تعيين المتغير ColumnHeaderPressedCurrentState معنى زائف.
إنتاج
كما نرى، يعد دمج محلل PVS-Studio الثابت في مشروع TeamCity الخاص بك أمرًا بسيطًا للغاية. للقيام بذلك، يكفي كتابة ملف تكوين صغير واحد فقط. سيسمح لك التحقق من الكود بتحديد المشكلات فورًا بعد التجميع، مما سيساعد في القضاء عليها عندما لا يزال التعقيد وتكلفة التغييرات منخفضة.
إذا كنت ترغب في مشاركة هذه المقالة مع جمهور ناطق باللغة الإنجليزية، يرجى استخدام رابط الترجمة: فلاديسلاف ستولياروف.
المصدر: www.habr.com