PVS-Studio in the Clouds: CircleCI

الصورة 2

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


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

CircleCI هي خدمة سحابية CI لإنشاء البرامج الآلية واختبارها ونشرها. وهو يدعم بناء المشاريع سواء في الحاويات أو على الأجهزة الافتراضية على Windows و Linux و macOS.

Kodi هو تطبيق مشغل وسائط مجاني ومفتوح المصدر. يسمح للمستخدمين بتشغيل وعرض معظم وسائط البث ، مثل مقاطع الفيديو والموسيقى والبودكاست ومقاطع الفيديو من الإنترنت ، وكذلك جميع ملفات الوسائط الرقمية الشائعة من وسائط التخزين المحلية وشبكات التخزين. وهو يدعم استخدام السمات والجلود وملحقات الوظائف من خلال الإضافات. Kodi متاح لأنظمة Windows و Linux و macOS و Android.

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

الإعداد


أولاً نحتاج إلى الذهاب إلى الصفحة الرئيسية لـ CircleCI والنقر فوق "تسجيل"

الصورة 1

في الصفحة التالية ، يُعرض علينا التفويض باستخدام حساب GitHub أو Bitbucket. نختار GitHub وننتقل إلى صفحة ترخيص CircleCI.

الصورة 3

بعد ترخيص التطبيق (بالنقر فوق الزر الأخضر "تفويض circleci") ، تتم إعادة توجيهنا إلى "مرحبًا بكم في CircleCI!" الصفحة:

صورة 4

هنا يمكننا أن نحدد على الفور أي المشاريع التي نريد أن تقوم الدائرة بإنشائها. نحن وضع علامة على مستودعنا وانقر فوق "متابعة".

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

الصورة 5

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

صورة 6

نذهب إلى صفحة "متغيرات البيئة". ننشئ هنا متغيرين ، PVS_USERNAME و PVS_KEY ، اللذين يحتويان على مفتاح ترخيص اسم المستخدم والمحلل.

الصورة 7

عند بدء الإنشاء ، يقرأ CircleCI تكوين المهمة من الملف المخزن في المستودع في .circleci / config.yml. دعونا إضافته.

نحتاج أولاً إلى تحديد صورة الجهاز الظاهري الذي يعمل عليه المحلل. القائمة الكاملة للصور متاحة هنا .

version: 2 jobs: build: machine: image: ubuntu-1604:201903-01 

بعد ذلك ، نضيف المستودعات اللازمة لتركيب وتثبيت تبعيات المشروع:

 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget 

إضافة مستودع PVS-Studio وتثبيت المحلل:

 - run: wget -q -O - https://files.viva64.com/etc/pubkey.txt | sudo apt-key add - && sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.viva64.com/etc/viva64.list - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" 

ثم نحن نبني التبعيات:

 - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local 

بعد ذلك ، نقوم بإنشاء Makefiles في دليل الإنشاء:

 - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. 

الخطوة التالية هي إعداد وبدء تحليل للمشروع.

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

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

--DisableLicenseExpirationCheck .

يحول الأمر النهائي ملف تقرير المحلل إلى تقرير html:

 - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log 

بمجرد انتهاء الاختبارات ، نحفظ التقارير:

 - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

إليك النص الكامل لملف .circleci / config.yml:

 version: 2.1 jobs: build: machine: image: ubuntu-1604:201903-01 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget - run: wget -q -O - https://files.viva64.com/etc/pubkey.txt | sudo apt-key add – && sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.viva64.com/etc/viva64.list - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

بمجرد تحميل هذا الملف إلى المستودع ، سيبدأ CircleCI تلقائيًا في الإنشاء.

صورة 12

بمجرد الانتهاء من المهمة ، يمكن تنزيل الملفات التي تحتوي على نتائج التحليل في علامة التبويب "القطع الأثرية".

صورة 11

نتائج التحليل


حسنًا ، دعنا الآن نلقي نظرة على بعض التحذيرات التي خرج بها المحلل.

تحذير PVS-Studio: V504 من المحتمل جدًا أن تكون الفاصلة المنقوطة '؛' مفقود بعد الكلمة الرئيسية "عودة". AdvancedSettings.cpp: 1476

 void CAdvancedSettings::SetExtraArtwork(const TiXmlElement* arttypes, std::vector<std::string>& artworkMap) { if (!arttypes) return artworkMap.clear(); const TiXmlNode* arttype = arttypes->FirstChild("arttype"); .... } 

يقترح تنسيق التعليمات البرمجية منطق التنفيذ التالي:

  • إذا كانت arttypes مؤشرًا فارغًا ، فتُرجع الطريقة ؛
  • إذا كانت arttypes عبارة عن مؤشر غير فارغ ، فسيتم مسح متجه artworkMap ثم يتم تنفيذ بعض الإجراءات.

لكن المفقودين "؛" حرف يكسر كل شيء ، ومنطق التنفيذ الفعلي هو كما يلي:

  • إذا كانت arttypes مؤشرًا فارغًا ، فسيتم مسح متجه artworkMap وإرجاع الطريقة ؛
  • إذا كانت arttypes عبارة عن مؤشر غير فارغ ، فإن البرنامج ينفذ أياً من الإجراءات التي تأتي بعد ذلك ولكن لا يتم مسح vector artapMap .

لقص قصة طويلة ، يبدو هذا الموقف خطأ. بعد كل شيء ، لا تتوقع أن يكتب أي شخص تعبيرات مثل return artworkMap.clear ()؛ :).

تحذيرات PVS-Studio:

  • V547 التعبير "lastsector" هو دائما خطأ. udf25.cpp: 636
  • V547 التعبير "lastsector" هو دائما خطأ. udf25.cpp: 644
  • الاختيار المتكرر V571 . تم التحقق بالفعل من الشرط "if (lastsector)" في السطر 636. udf25.cpp: 644

 int udf25::UDFGetAVDP( struct avdp_t *avdp) { .... uint32_t lastsector; .... lastsector = 0; // <= .... for(;;) { .... if( lastsector ) { // <= V547 lbnum = lastsector; terminate = 1; } else { //! @todo Find last sector of the disc (this is optional). if( lastsector ) // <= V547 lbnum = lastsector - 256; else return 0; } } .... } 

لاحظ النقاط التي تحمل علامة // <= . يتم تعيين متغير lastsector القيمة 0 ثم يتم استخدامه كتعبير شرطي في جملتي if . نظرًا لأن القيمة لا تتغير سواء في الحلقة أو بين التعيينات ، فلن يتم التحكم مطلقًا في الفروع الأخرى لكليهما إذا كانت العبارات.

ومع ذلك ، قد يعني ذلك أيضًا أن المطورين لم يطبقوا الوظيفة المقصودة بعد (لاحظ الملاحظة التي يجب القيام بها).

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

تحذير PVS-Studio: تعبير V547 'values.size ()! = 2' خطأ دائمًا. GUIControlSettings.cpp: 1174

 bool CGUIControlRangeSetting::OnClick() { .... std::vector<CVariant> values; SettingConstPtr listDefintion = settingList->GetDefinition(); switch (listDefintion->GetType()) { case SettingType::Integer: values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorUpper)); break; case SettingType::Number: values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorUpper)); break; default: return false; } if (values.size() != 2) return false; SetValid(CSettingUtils::SetList(settingList, values)); return IsValid(); } 

value.size ()! = 2 عملية التحقق ضرورية هنا لأن هذا التعبير الشرطي سيقيم دائمًا على خطأ . في الواقع ، إذا أدخل التنفيذ أحد فروع الحالة الخاصة ببيان التبديل ، فسيتم إضافة عنصرين إلى المتجه ، وبما أنه كان فارغًا في البداية ، فسيصبح حجمه طبيعيًا يساوي 2 ؛ وإلا (أي إذا تم تنفيذ الفرع الافتراضي ) ، فستعود الطريقة.

تحذير PVS-Studio: V547 Expression 'prio == 0x7fffffff' صحيح دائمًا. DBusReserve.cpp: 57

 bool CDBusReserve::AcquireDevice(const std::string& device) { .... int prio = INT_MAX; .... res = dbus_bus_request_name( m_conn, service.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE | (prio == INT_MAX ? 0 : DBUS_NAME_FLAG_ALLOW_REPLACEMENT), // <= error); .... } 

تتم تهيئة متغير prio إلى قيمة INT_MAX ، ثم يتم استخدامه كمعامل للمعامل الثلاثي في prio == INT_MAX مقارنة ، على الرغم من أن قيمته لا تتغير بعد التهيئة. هذا يعني أن prio == INT_MAX تعبير صحيح وأن المشغل الثلاثي سيعود دائمًا 0.

تحذيرات PVS-Studio:

  • V575 يتم تمرير المؤشر الفارغ المحتمل في وظيفة "memcpy". تفقد الحجة الأولى. خطوط التحقق: 39 ، 38. DVDOverlayImage.h: 39
  • V575 يتم تمرير المؤشر الفارغ المحتمل في وظيفة "memcpy". تفقد الحجة الأولى. خطوط التحقق: 44 ، 43. DVDOverlayImage.h: 44

 CDVDOverlayImage(const CDVDOverlayImage& src) : CDVDOverlay(src) { Data = (uint8_t*)malloc(src.linesize * src.height); memcpy(data, src.data, src.linesize * src.height); // <= if(src.palette) { palette = (uint32_t*)malloc(src.palette_colors * 4); memcpy(palette, src.palette, src.palette_colors * 4); // <= } .... } 

كلا التحذيرين لهما نفس النمط: يتم استخدام مؤشر تم إرجاعه بواسطة الدالة malloc في دالة memcpy دون التحقق من NULL أولاً.

قد يجادل البعض بأن malloc لن يُرجع مؤشرًا فارغًا أبدًا ، وإذا حدث ذلك ، فمن الأفضل أن يتعطل التطبيق. إنه موضوع مناقشة منفصلة ، ولكن بغض النظر عن رأيك ، أوصي بقراءة هذا المنشور من قِبل زميلي في الفريق: " لماذا من المهم التحقق من وظيفة malloc التي تم إرجاعها ".

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

تحذير PVS-Studio: V522 قد يكون هناك إلغاء لإشارة "إدخال" مؤشر فارغة. خطوط التحقق: 985 ، 981. emu_msvcrt.cpp: 985

 struct dirent *dll_readdir(DIR *dirp) { .... struct dirent *entry = NULL; entry = (dirent*) malloc(sizeof(*entry)); if (dirData->curr_index < dirData->items.Size() + 2) { if (dirData->curr_index == 0) strncpy(entry->d_name, ".\0", 2); .... } 

هذا المثال يشبه المثال السابق. يتم تخزين المؤشر الذي تم إرجاعه بواسطة الدالة malloc إلى متغير الإدخال ، ثم يتم استخدام هذا المتغير بدون التحقق المسبق من الصفر ( إدخال-> d_name ).

تحذير PVS-Studio: تم إنهاء نطاق الرؤية V773 لمؤشر 'progressHandler' دون تحرير الذاكرة. تسرب الذاكرة ممكن. PVRGUIChannelIconUpdater.cpp: 94

 void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const { .... CPVRGUIProgressHandler* progressHandler = new CPVRGUIProgressHandler(g_localizeStrings.Get(19286)); for (const auto& group : m_groups) { const std::vector<PVRChannelGroupMember> members = group->GetMembers(); int channelIndex = 0; for (const auto& member : members) { progressHandler->UpdateProgress(member.channel->ChannelName(), channelIndex++, members.size()); .... } progressHandler->DestroyProgress(); } 

تم إرجاع قيمة مؤشر progressHandler بواسطة المشغل الجديد . ولكن لا يوجد عامل حذف لهذا المؤشر. وهذا يعني تسرب الذاكرة.

تحذير PVS-Studio: تجاوز سعة صفيف V557 أمر ممكن. يشير مؤشر 'idx' إلى ما وراء مجموعة الصفيف. PlayerCoreFactory.cpp: 240

 std::vector<CPlayerCoreConfig *> m_vecPlayerConfigs; bool CPlayerCoreFactory::PlaysVideo(const std::string& player) const { CSingleLock lock(m_section); size_t idx = GetPlayerIndex(player); if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size()) return false; return m_vecPlayerConfigs[idx]->m_bPlaysVideo; } 

العبارة if يقيد حجم متجه m_vecPlayerConfigs إلى نطاق معين عن طريق الحصول على طريقة الإرجاع إذا كان شرط التحقق من الحجم صحيحًا. نتيجة لذلك ، عندما يصل التنفيذ إلى عبارة الإرجاع الأخيرة ، سيكون حجم متجه m_vecPlayerConfigs داخل النطاق المحدد ، [1؛ IDX]. ولكن بعد بضعة أسطر ، يقوم البرنامج بفهرسة المتجه على idx : m_vecPlayerConfigs [idx] -> m_bPlaysVideo . هذا يعني أنه إذا كانت idx مساوية لحجم المتجه ، فسنفهرسه خارج النطاق الصالح.

دعنا نختتم هذا المقال ببعض الأمثلة من كود مكتبة Platinum .

تحذير PVS-Studio: V542 خذ بعين الاعتبار فحص نوع فردي: "منطقي" إلى "char *". PltCtrlPoint.cpp: 1617

 NPT_Result PLT_CtrlPoint::ProcessSubscribeResponse(...) { .... bool subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE"); .... NPT_String prefix = NPT_String::Format(" PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for service \"%s\" (result = %d, status code = %d)", (const char*)subscription?"S":"Uns", // <= (const char*)service->GetServiceID(), res, response?response->GetStatusCode():0); .... } 

كان لدى المطورين افتراضات خاطئة حول أسبقية العمليات. ما يتم تحويله إلى const char * ليس النتيجة التي يتم إرجاعها بواسطة المشغل الثلاثي ( الاشتراك؟ "S": "Uns" ) ولكن متغير الاشتراك . هذا يبدو غريبا ، على الأقل.

تحذير PVS-Studio: V560 جزء من التعبير الشرطي خطأ دائمًا: c == '\ t'. NptUtils.cpp: 863

 NPT_Result NPT_ParseMimeParameters(....) { .... case NPT_MIME_PARAMETER_PARSER_STATE_NEED_EQUALS: if (c < ' ') return NPT_ERROR_INVALID_SYNTAX; // END or CTLs are invalid if (c == ' ' || c == '\t') continue; // ignore leading whitespace .... } 

رمز حرف المسافة هو 0x20 ، ورمز حرف علامة الجدولة هو 0x09. لذلك ، سيتم دائمًا تقييم c == '\ t' subpresspression إلى false حيث أن هذه الحالة مغطاة بالفعل بالتحقق c <'' (الذي ، إذا كان صحيحًا ، سيؤدي إلى إرجاع الوظيفة).

استنتاج


كما يوضح هذا المقال ، قمنا بإعداد تحليل بواسطة PVS-Studio بنجاح على نظام CI آخر (CircleCI). أدعوك لتنزيل المحلل وتجربته في مشروعك الخاص. إذا كانت لديك أي أسئلة حول إعداد PVS-Studio أو استخدامه ، فلا تتردد في الاتصال بنا - سنكون سعداء لتقديم المساعدة.

وبطبيعة الحال ، نتمنى لك رمز بلا أخطاء. :)

Source: https://habr.com/ru/post/ar470465/


All Articles