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



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



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



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



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



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



معلومات عن المشروع محل التحقيق



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



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



اعداد



لتوفير الوقت ، ربما سأتخطى عملية التثبيت وأبدأ من اللحظة التي يعمل فيها خادم TeamCity على جهاز الكمبيوتر الخاص بي. نحتاج إلى الانتقال إلى: localhost: {المنفذ المحدد أثناء التثبيت} (في حالتي ، المضيف المحلي: 9090) وإدخال بيانات التفويض. بعد الدخول سنلتقي بـ:



image3.png


انقر على زر إنشاء مشروع. بعد ذلك ، حدد يدويًا ، املأ الحقول.



image5.png


بعد النقر فوق الزر " إنشاء" ، يتم استقبالنا من خلال نافذة بها إعدادات.



image7.png


انقر فوق إنشاء تكوين البناء .



image9.png


املأ الحقول ، انقر فوق إنشاء . نرى نافذة تعرض اختيار نظام التحكم في الإصدار. نظرًا لأن المصادر موجودة بالفعل محليًا ، انقر فوق تخطي .



image11.png


أخيرًا ، ننتقل إلى إعدادات المشروع.



image13.png


أضف خطوات الإنشاء بالنقر فوق: خطوات الإنشاء -> إضافة خطوة بناء .



image15.png


هنا نختار:



  • نوع العداء -> سطر الأوامر
  • تشغيل -> برنامج نصي مخصص


نظرًا لأننا سنحلل أثناء تجميع المشروع ، يجب أن يكون التجميع والتحليل خطوة واحدة ، لذا املأ حقل البرنامج النصي المخصص :



image17.png


سوف نتناول الخطوات الفردية لاحقًا. من المهم أن يستغرق تحميل المحلل وبناء المشروع وتحليله وإخراج التقرير وتنسيقه أحد عشر سطرًا فقط من التعليمات البرمجية.



آخر شيء يتعين علينا القيام به هو تعيين متغيرات البيئة ، والتي أشرت إلى بعض الطرق لتحسين إمكانية قراءتها. للقيام بذلك ، انتقل إلى: المعلمات -> إضافة معلمة جديدة وإضافة ثلاثة متغيرات:



image19.png


يبقى النقر فوق الزر " تشغيل" في الزاوية اليمنى العليا. أثناء تجميع المشروع وتحليله ، سأخبرك عن البرنامج النصي.



الكتابة مباشرة



أولاً ، نحتاج إلى تنزيل أحدث توزيعات PVS-Studio. لهذا نستخدم مدير حزمة شوكولاتة. بالنسبة لأولئك الذين يريدون معرفة المزيد عن هذا ، هناك مقالة ذات صلة :



choco install pvs-studio -y


بعد ذلك ، دعنا نطلق الأداة المساعدة لتتبع تجميع المشروع CLMonitor.



%CLmon% monitor –-attach


ثم سنقوم ببناء المشروع ، ويتم استخدام المسار إلى إصدار MSBuild الذي أحتاج إلى بنائه كمتغير بيئة MSB



%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:\temp\ptest.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:\temp\ptest.plog_TeamCity.txt"


في غضون ذلك ، تم الانتهاء من تجميع وتحليل المشروع بنجاح ، يمكننا الانتقال إلى علامة التبويب المشاريع والتحقق من ذلك.



image21.png


انقر الآن على إجمالي الاستقصاءات للذهاب لعرض تقرير المحلل:



image23.png


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



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



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



تحذير N1



V773 [CWE-401] تم طرح الاستثناء بدون تحرير مؤشر "النتيجة". من الممكن حدوث تسرب للذاكرة. 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 ، عند طرح استثناء ، لا يتم مسح الذاكرة ؛ وفقًا لذلك ، يحدث تسرب للذاكرة.



تحذير N2



V501 هناك تعبيرات فرعية متطابقة "(1ULL << WIDX_MONTH_BOX)" يسار ويمين "|" المشغل أو العامل. 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



V703 من الغريب أن يقوم حقل "الإشارات" في الفئة المشتقة "RCT12BannerElement" بالكتابة فوق الحقل في الفئة الأساسية "RCT12TileElementBase". فحص الخطوط: RCT12.h: 570 ، RCT12.h: 259. 570- محلول أقراص



struct RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};
struct rct1_peep : RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};


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



تحذير N4



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



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



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



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


تحذير N7



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


تحذير N8



V1048 [CWE-1164] تم تعيين نفس القيمة لمتغير "ColumnHeaderPressedCurrentState". libopenrct2ui CustomListView.cpp 510



void CustomListView::MouseUp(....)
{
  ....
  if (!ColumnHeaderPressedCurrentState)
  {
    ColumnHeaderPressed = std::nullopt;
    ColumnHeaderPressedCurrentState = false;
    Invalidate();
  }
}


يبدو الرمز غريبًا جدًا. أعتقد أنه كان هناك خطأ إملائي أو خاضع لـ ، أو عند إعادة تعيين قيمة متغير ColumnHeaderPressedCurrentState إلى false .



انتاج |



كما نرى ، فإن دمج محلل PVS-Studio الثابت في مشروع TeamCity الخاص بك أمر بسيط للغاية. يكفي كتابة ملف تكوين صغير واحد فقط لهذا الغرض. ستسمح لك مراجعة الكود بتحديد المشكلات فورًا بعد التجميع ، مما سيساعد في التخلص منها عندما يظل تعقيد وتكلفة عمليات التحرير قليلة.





إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Vladislav Stolyarov. PVS-Studio والتكامل المستمر: TeamCity. تحليل مشروع Open RollerCoaster Tycoon 2 .



All Articles