PVS-Studio والتكامل المستمر: TeamCity. تحليل مشروع Open RollerCoaster Tycoon 2

PVS-Studio والتكامل المستمر: TeamCity. تحليل مشروع Open RollerCoaster Tycoon 2
أحد السيناريوهات الأكثر حداثة لاستخدام محلل PVS-Studio هو تكامله مع أنظمة CI. وعلى الرغم من أن تحليل مشروع PVS-Studio من أي نظام تكامل مستمر تقريبًا يمكن دمجه في عدد قليل من الأوامر، إلا أننا نواصل جعل هذه العملية أكثر ملاءمة. يتمتع PVS-Studio الآن بدعم لتحويل مخرجات المحلل إلى تنسيق TeamCity - نوع عمليات التفتيش TeamCity. دعونا نرى كيف يعمل.

معلومات عن البرامج المستخدمة

استوديو PVS - محلل ثابت لرموز C وC++ وC# وJava، مصمم لتسهيل مهمة البحث عن أنواع مختلفة من الأخطاء وتصحيحها. يمكن استخدام المحلل على أنظمة التشغيل Windows وLinux وmacOS. في هذه المقالة، سوف نستخدم بنشاط ليس فقط المحلل نفسه، ولكن أيضًا بعض الأدوات المساعدة من توزيعه.

CLMonitor - هو خادم مراقبة يراقب عمليات تشغيل المترجم. ويجب تشغيله فورًا قبل البدء في بناء مشروعك. في وضع التطفل، سيعترض الخادم عمليات تشغيل جميع المترجمين المعتمدين. تجدر الإشارة إلى أنه لا يمكن استخدام هذه الأداة إلا لتحليل مشاريع C/C++.

com.PlogConverter – أداة لتحويل تقارير المحلل إلى صيغ مختلفة.

معلومات عن المشروع قيد الدراسة

لنجرب هذه الوظيفة بمثال عملي - دعنا نحلل مشروع OpenRCT2.

برنامج OpenRCT2 - تطبيق مفتوح للعبة RollerCoaster Tycoon 2 (RCT2)، وتوسيعها بوظائف جديدة وإصلاح الأخطاء. تدور طريقة اللعب حول بناء وصيانة متنزه يحتوي على ألعاب ومتاجر ومرافق. يجب على اللاعب أن يحاول تحقيق الربح والحفاظ على السمعة الطيبة للحديقة مع إبقاء الضيوف سعداء. يتيح لك OpenRCT2 اللعب في كل من السيناريو ووضع الحماية. تتطلب السيناريوهات من اللاعب إكمال مهمة محددة خلال فترة زمنية محددة، بينما يسمح Sandbox للاعب ببناء حديقة أكثر مرونة دون أي قيود أو موارد مالية.

تعديل

لتوفير الوقت، ربما سأتخطى عملية التثبيت وأبدأ من اللحظة التي يكون فيها خادم TeamCity قيد التشغيل على جهاز الكمبيوتر الخاص بي. نحتاج إلى الانتقال إلى: localhost:{port المحدد أثناء عملية التثبيت} (في حالتي، 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 علامة. يمكن لأي شخص مهتم بهذا الوضع من تشغيل المحلل التعرف على القسم المقابل توثيق.

عرض نتائج المحلل

الآن وبعد أن انتهينا من نشر البنية وتكوينها، فلنلقي نظرة على بعض التحذيرات المثيرة للاهتمام الموجودة في المشروع الذي ننظر إليه.

تحذير رقم 1

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;
}

لاحظ المحلل خطأ أنه بعد تخصيص الذاكرة ديناميكيًا CREATEOBJECT، عند حدوث استثناء، لا يتم مسح الذاكرة، ويحدث تسرب للذاكرة.

تحذير رقم 2

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),
  ....
};

قليل من الأشخاص، باستثناء المحلل الثابت، يمكنهم اجتياز اختبار الانتباه هذا. يعد مثال النسخ واللصق هذا جيدًا لهذا السبب بالتحديد.

تحذيرات رقم 3

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;
  ....
};

بالطبع، استخدام متغير يحمل نفس الاسم في الفئة الأساسية وفي الفئة التابعة لا يعد خطأً دائمًا. ومع ذلك، تفترض تقنية الميراث نفسها أن جميع حقول الفئة الأصلية موجودة في الفئة الفرعية. من خلال الإعلان عن الحقول التي تحمل نفس الاسم في الوريث، فإننا نخلق ارتباكًا.

تحذير رقم 4

V793 ومن الغريب أن تكون نتيجة عبارة "imageDirection / 8" جزءًا من الشرط. ربما كان ينبغي مقارنة هذا البيان بشيء آخر. libopenrct2 برج المراقبة.cpp 38

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

V587 تسلسل فردي للمهام من هذا النوع: A = B؛ ب = أ؛. خطوط الاختيار: 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;
      ....
  }
  ....
}

على الأرجح تم الحصول على جزء التعليمات البرمجية هذا عن طريق إلغاء الترجمة. ثم، انطلاقا من التعليق المتبقي، تمت إزالة جزء من التعليمات البرمجية غير العاملة. ومع ذلك، لا تزال هناك بضع عمليات متبقية معرف المؤشر، وهو أيضًا ليس له معنى كبير.

تحذير رقم 6

V1004 [CWE-476] تم استخدام مؤشر "اللاعب" بشكل غير آمن بعد أن تم التحقق منه ضد 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);
    }
  }
  ....
}

تحذير رقم 7

V547 [CWE-570] التعبير 'name == nullptr' غير صحيح دائمًا. 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);
    ....
  }
  ....
}

تحذير رقم 8

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

إضافة تعليق