التحقق من مشروع LibrePCB باستخدام PVS-Studio داخل حاوية Docker

PVS-Studio و Docker Container

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

LibrePCB


LibrePCB هو برنامج مجاني لتصميم الدوائر الإلكترونية ولوحات الدوائر المطبوعة. رمز البرنامج مكتوب بلغة C ++ ، ويتم استخدام Qt5 لبناء الواجهة الرسومية. في الآونة الأخيرة ، تم إطلاق أول إصدار رسمي من هذا التطبيق ، والذي تميز بتثبيت تنسيق الملف الخاص به (* .lp ، * .lplib). الحزم الثنائية المعدة لنظام Linux و macOS و Windows.

LibrePCB


LibrePCB هو مشروع صغير يحتوي فقط على حوالي 300000 سطر غير فارغ من الكود في C و C ++. في الوقت نفسه ، 25 ٪ من الخطوط غير الفارغة هي التعليقات. بالمناسبة ، هذه نسبة كبيرة للتعليقات. على الأرجح ، يرجع هذا إلى حقيقة أن المشروع يحتوي على العديد من الملفات الصغيرة ، التي يشغل جزء كبير منها عن طريق توجيه تعليقات من معلومات المشروع والترخيص. رمز موقع جيثب: LibrePCB .

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

عامل الميناء


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

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

أول تجربة لاختبار مشروع مفتوح المصدر في Docker كانت مع شركة Azure Service Fabric . هناك ، قام المطورون بتثبيت دليل الملف المصدر على الحاوية وكان تكامل المحلل مقصورًا على تحرير أحد البرامج النصية التي تعمل في الحاوية:

diff --git a/src/build.sh b/src/build.sh index 290c57d..2a286dc 100755 --- a/src/build.sh +++ b/src/build.sh @@ -193,6 +193,9 @@ BuildDir() cd ${ProjBinRoot}/build.${DirName} + pvs-studio-analyzer analyze --cfg /src/PVS-Studio.cfg \ + -o ./service-fabric-pvs.log -j4 + if [ "false" = ${SkipBuild} ]; then if (( $NumProc <= 0 )); then NumProc=$(($(getconf _NPROCESSORS_ONLN)+0)) 

الفرق بين مشروع LibrePCB هو أنهم قدموا على الفور Dockerfile لبناء الصورة والمشروع فيها. اتضح أن يكون أكثر ملاءمة بالنسبة لنا. فيما يلي جزء من ملف Docker الذي نهتم به:

 FROM ubuntu:14.04 # install packages RUN DEBIAN_FRONTEND=noninteractive \ apt-get -q update \ && apt-get -qy upgrade \ && apt-get -qy install git g++ qt5-default qttools5-dev-tools qt5-doc \ qtcreator libglu1-mesa-dev dia \ && apt-get clean # checkout librepcb RUN git clone --recursive https://..../LibrePCB.git /opt/LibrePCB \ && cd /opt/LibrePCB .... # build and install librepcb RUN /opt/LibrePCB/dev/docker/make_librepcb.sh .... 

لن نقوم بتجميع وتثبيت المشروع عند تجميع الصورة. وبالتالي ، قمنا بجمع صورة يضمن فيها مؤلف المشروع التجميع الناجح للمشروع.

بعد بدء تشغيل الحاوية ، تم تثبيت أداة التحليل وتنفيذ الأوامر التالية لإنشاء وتحليل المشروع:

 cd /opt/LibrePCB mkdir build && cd build qmake -r ../librepcb.pro pvs-studio-analyzer trace -- make -j2 pvs-studio-analyzer analyze -l /mnt/Share/PVS-Studio.lic -r /opt/LibrePCB \ -o /opt/LibrePCB/LibrePCB.log -v -j4 cp -R -L -a /opt/LibrePCB /mnt/Share 

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

البق وجدت


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

  1. يتضمن PVS-Studio دعمًا لسلسلة أدوات GNU Arm Embedded Toolchain ؛
  2. PVS-Studio: دعم معايير الترميز MISRA C و MISRA C ++ .

الأخطاء المطبعية


 SymbolPreviewGraphicsItem::SymbolPreviewGraphicsItem( const IF_GraphicsLayerProvider& layerProvider, const QStringList& localeOrder, const Symbol& symbol, const Component* cmp, const tl::optional<Uuid>& symbVarUuid, const tl::optional<Uuid>& symbVarItemUuid) noexcept { if (mComponent && symbVarUuid && symbVarItemUuid) .... if (mComponent && symbVarItemUuid && symbVarItemUuid) // <= .... } 

تحذير PVS-Studio: V501 CWE-571 هناك تعابير فرعية مماثلة "symbVarItemUuid" إلى اليسار وإلى يمين المشغل "&&". symbolpreviewgraphicsitem.cpp 74

خطأ مطبعي كلاسيكي : يتم فحص symbVarItemUuid المتغير مرتين على التوالي. هناك فحص مماثل أعلاه ، وعند النظر إليه ، يصبح من الواضح أن المتغير الثاني الذي سيتم فحصه يجب أن يكون symbVarUuid .

مقتطف الشفرة التالي:

 void Clipper::DoMaxima(TEdge *e) { .... if (e->OutIdx >= 0) { AddOutPt(e, e->Top); e->OutIdx = Unassigned; } DeleteFromAEL(e); if (eMaxPair->OutIdx >= 0) { AddOutPt(eMaxPair, e->Top); // <= eMaxPair->OutIdx = Unassigned; } DeleteFromAEL(eMaxPair); .... } 

PVS-Studio Warning: V778 CWE-682 تم العثور على شظايا رمز مماثلة. ربما ، هذا خطأ مطبعي ويجب استخدام متغير "eMaxPair" بدلاً من "e". clipper.cpp 2999

على الأرجح ، تمت كتابة التعليمات البرمجية باستخدام Copy-Paste. نظرًا لوجود رقابة في المجموعة الثانية من النص ، فقد نسوا استبدال e-> Top بـ eMaxPair-> Top .

الشيكات الإضافية


 static int rndr_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, "<em>"); if (content) hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, "</em>"); return 1; } 

تحذير PVS-Studio: V547 CWE-571 التعبير "محتوى" صحيح دائمًا. html.c 162

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

وضع مماثل:

 void Clipper::DoMaxima(TEdge *e) { .... else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) { if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); DeleteFromAEL(e); DeleteFromAEL(eMaxPair); } .... } 

تحذير PVS-Studio: V547 CWE-571 Expression 'e-> OutIdx> = 0' صحيح دائمًا. clipper.cpp 2983

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

وحالة أخرى:

 QString SExpression::toString(int indent) const { .... if (child.isLineBreak() && nextChildIsLineBreak) { if (child.isLineBreak() && (i > 0) && mChildren.at(i - 1).isLineBreak()) { // too many line breaks ;) } else { str += '\n'; } } .... } 

PVS-Studio Warning: V571 CWE-571 الاختيار المتكرر. تم التحقق من حالة "child.isLineBreak ()" بالفعل في السطر 208. sexpression.cpp 209

خطأ في المنطق


 void FootprintPreviewGraphicsItem::paint(....) noexcept { .... for (const Circle& circle : mFootprint.getCircles()) { layer = mLayerProvider.getLayer(*circle.getLayerName()); if (!layer) continue; // <= if (layer) { // <= pen = QPen(....); painter->setPen(pen); } else painter->setPen(Qt::NoPen); .... } .... } 

تحذير PVS-Studio: طبقة "التعبير" V547 CWE-571 صحيحة دائمًا. footprintpreviewgraphicsitem.cpp 177

نظرًا لأن الشرط في الحالة الثانية إذا كان العبارة صحيحًا دائمًا ، فإن الفرع الآخر غير راض أبدًا.

فحص مؤشر المنسية


 extern int ZEXPORT unzGetGlobalComment ( unzFile file, char * szComment, uLong uSizeBuf) { .... if (uReadThis>0) { *szComment='\0'; if (ZREAD64(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) return UNZ_ERRNO; } if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) *(szComment+s->gi.size_comment)='\0'; .... } 

تحذير PVS-Studio: V595 CWE-476 تم استخدام مؤشر 'szComment' قبل أن يتم التحقق منه ضد nullptr. خطوط الفحص: 2068 ، 2073. unzip.c 2068

إذا كان uReadThis> 0 ، فسيتم إلغاء مؤشر szComment . هذا خطير لأن هذا المؤشر قد يكون لاغيا. يقوم المحلل بإجراء مثل هذا الاستنتاج استنادًا إلى حقيقة أن هذا المؤشر يتم التحقق منه لتحقيق المساواة NULL .

عضو صف غير مهيأ


 template <class T> class Edge { public: using VertexType = Vector2<T>; Edge(const VertexType &p1, const VertexType &p2, T w=-1) : p1(p1), p2(p2), weight(w) {}; // <= Edge(const Edge &e) : p1(e.p1), p2(e.p2), weight(e.weight), isBad(false) {}; Edge() : p1(0,0), p2(0,0), weight(0), isBad(false) {} VertexType p1; VertexType p2; T weight=0; bool isBad; }; 

تحذير PVS-Studio: V730 CWE-457 لم تتم تهيئة جميع أعضاء الفصل داخل المنشئ. النظر في التفتيش: isBad. الحافة 14

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

هناك 11 مشغلات تشخيص V730 أخرى . ومع ذلك ، نظرًا لأننا لسنا على دراية بالكود ، فمن الصعب تحديد ما إذا كانت هذه التحذيرات تشير إلى مشاكل حقيقية. أعتقد أنه من الأفضل دراسة هذه التحذيرات من قبل مؤلفي المشروع.

تسرب الذاكرة


 template <typename ElementType> void ProjectLibrary::loadElements(....) { .... ElementType* element = new ElementType(elementDir, false); // can throw if (elementList.contains(element->getUuid())) { throw RuntimeError( __FILE__, __LINE__, QString(tr("There are multiple library elements with the same " "UUID in the directory \"%1\"")) .arg(subdirPath.toNative())); } .... } 

تحذير PVS-Studio: V773 CWE-401 تم طرح الاستثناء دون تحرير مؤشر "العنصر". تسرب الذاكرة ممكن. projectlibrary.cpp 245

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

نوع استثناء غير صالح


 bool CmdRemoveSelectedSchematicItems::performExecute() { .... throw new LogicError(__FILE__, __LINE__); .... } 

تحذير PVS-Studio: V1022 CWE-755 تم طرح استثناء بواسطة المؤشر. فكر في رميها بالقيمة بدلاً من ذلك. cmdremoveselectedschematicitems.cpp 143

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

بشكل عام ، يتم كتابة المشغل الجديد هنا عن طريق الصدفة ويجب حذفه. أن هذا خطأ تؤكده حقيقة أنه في جميع الأماكن الأخرى تقول:

 throw LogicError(__FILE__, __LINE__); 

استخدام خطير للديناميكية


 void GraphicsView::handleMouseWheelEvent( QGraphicsSceneWheelEvent* event) noexcept { if (event->modifiers().testFlag(Qt::ShiftModifier)) .... } bool GraphicsView::eventFilter(QObject* obj, QEvent* event) { .... handleMouseWheelEvent(dynamic_cast<QGraphicsSceneWheelEvent*>(event)); .... } 

تحذير PVS-Studio: V522 CWE-628 قد يتم إلغاء تنظيم "حدث" المؤشر الخالي. يتم تمرير المؤشر الفارغ المحتمل في وظيفة "handleMouseWheelEvent". تفقد الحجة الأولى. خطوط التحقق: 143 ، 252. graphicsview.cpp 143

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

هذا أمر خطير حيث أن مشغل dynamic_cast قد يعود nullptr . اتضح أن هذا الرمز ليس أفضل من مجرد استخدام static_cast الأسرع.

يجب إضافة تدقيق مؤشر واضح إلى هذا الرمز قبل الاستخدام.

أيضًا ، يعد هذا الرمز شائعًا جدًا:

 bool GraphicsView::eventFilter(QObject* obj, QEvent* event) { .... QGraphicsSceneMouseEvent* e = dynamic_cast<QGraphicsSceneMouseEvent*>(event); Q_ASSERT(e); if (e->button() == Qt::MiddleButton) .... } 

تحذير PVS-Studio: V522 CWE-690 قد يكون هناك إلغاء مرجعية لمؤشر خالي محتمل 'e'. graphicsview.cpp 206

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

Q_ASSERT () مفيد لاختبار قبل وبعد الشروط أثناء التطوير. لا يفعل شيئًا إذا تم تعريف QT_NO_DEBUG أثناء التحويل البرمجي.

Q_ASSERT طريقة سيئة للتحقق من المؤشرات قبل الاستخدام. كقاعدة عامة ، في إصدار الإصدار لم يتم تعريف QT_NO_DEBUG . لا أعرف كيف تسير الأمور في مشروع LibrePCB. ومع ذلك ، إذا تم تعريف QT_NO_DEBUG في الإصدار ، فهذا يعد حلًا غريبًا وغير قياسي.

إذا تم توسيع الماكرو إلى الفراغ ، اتضح أنه لا يوجد التحقق. ومن ثم ، ليس من الواضح سبب استخدام dynamic_cast على الإطلاق. لماذا لا تستخدم static_cast بعد ذلك ؟

بشكل عام ، هذا الكود عديم الرائحة ويستحق مراجعة جميع أجزاء الكود المشابهة. وهناك ، بالمناسبة ، الكثير منهم. لقد عدت 82 حالة مماثلة!

الخاتمة


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



إذا كنت ترغب في مشاركة هذا المقال مع جمهور ناطق باللغة الإنجليزية ، فيرجى استخدام الرابط الخاص بالترجمة: Andrey Karpov ، Svyatoslav Razmyslov. التحقق من LibrePCB باستخدام PVS-Studio داخل حاوية الإرساء .

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


All Articles