OpenCV هي مكتبة مفتوحة المصدر تحتوي على خوارزميات رؤية ومعالجة الصور وخوارزميات رقمية للأغراض العامة. المكتبة معروفة بين مطوري C ++. إلى جانب C ++ ، هناك أيضًا إصدارات لـ Python و Java و Ruby و Matlab و Lua ولغات أخرى. نظرًا لأن C # ، وهي اللغة التي أتخصص فيها ، ليست مدرجة في تلك القائمة ، فقد اخترت OpenCvSharp ، وهو ملف C # من OpenCV ، للتحقق من ذلك باستخدام PVS-Studio. وتناقش نتائج هذا الاختيار في هذه المقالة.
مقدمة
قبل أن أصبح جزءًا من فريق PVS-Studio ، شاركت في صنع روبوتات لتقديمها في المعارض. تضمنت واجباتي أهم أعمال الإصلاح (تم معالجة حالات الفشل الكبرى من قبل شخص آخر) بالإضافة إلى تطوير البرامج والأدوات المساعدة من كل نوع.
أنا متعب وجديد على المدينة ، مع روبوت KIKI غير المعبأ حديثًا.بالمناسبة ، كان الجزء التطوير مضحك جدا. في كل مرة كان لدى أحدنا فكرة عن طريقة جديدة لمفاجأة زوار المعرض ، طرحناها للمناقشة ، وإذا أعجب الجميع بها ، فسنبدأ العمل. بمجرد حدوث ذلك ، قمنا بعمل روبوت يمكنه التعرف على الوجه الإنساني والرد بكلمة ترحيبية.
لقد غوغل لبعض المكتبات لاحتياجاتي وتعثرت على OpenCV ، مكتبة خوارزميات رؤية الكمبيوتر. لكنني شعرت بخيبة أمل في أقرب وقت لأنني اكتشفت أن OpenCV تم تنفيذه في C ++. من الواضح أن معرفتي بـ C ++ ، التي درستها في الكلية ، لم تكن كافية. لذا غوغل أكثر قليلاً ووجدت OpenCvSharp ، وهي مجموعة من مكتبة C # ، وهي اللغة التي أتخصص فيها. لقد مر حوالي نصف عام منذ ذلك الحين ، وهو البرنامج المكتوب والمستخدم منذ فترة طويلة ، والآن قررت أخيرًا إلقاء نظرة خاطفة "تحت غطاء" برنامج OpenCvSharp ومسح كود مصدره باستخدام محلل ثابت PVS-Studio.
المشروع قيد التحليل
OpenCvSharp عبارة عن غلاف لـ OpenCV للاستخدام في مشاريع C #. بالمناسبة ،
لقد فحصنا بالفعل OpenCV في الماضي. تتمثل النقاط القوية في OpenCvSharp في المجموعة الكبيرة من نماذج التعليمات البرمجية والدعم عبر الأنظمة الأساسية (يتم تشغيله على أي نظام أساسي مدعوم من مونو) وسهولة التركيب.
المجمع عبارة عن مشروع صغير حول 112200 سطر من رمز C # طويلة. 1.2 ٪ من هذه التعليقات ، والتي ، يجب أن أقول ، هي قليلة بشكل مثير للريبة. من ناحية أخرى ، هناك عدد غير قليل من الأخطاء لمشروع صغير. لقد اخترت أكثر من 20 مثالًا لهذه المقالة ، لكن المحلل وجد بالفعل الكثير منها ، وهي ليست مثيرة للاهتمام أو واضحة.
PVS استوديو
يعد PVS-Studio أداة للكشف عن الأخطاء ونقاط الضعف المحتملة في التعليمات البرمجية المصدر للبرامج المكتوبة بلغات C و C ++ و C # و Java. إنه يعمل على Windows و Linux و macOS. بالإضافة إلى الشفرة التي يتعذر الوصول إليها وأخطاء البرمجة والأخطاء المطبعية ، فإن PVS-Studio ، كما ذكر سابقًا ، قادر على اكتشاف مشكلات الأمان المحتملة. لذلك ، يمكن عرضه كأداة اختبار أمان تطبيق ثابت (SAST).
التحذيرات الأكثر إثارة للاهتمام
ما يجعل أسلوب
WriteableBitmapConverter خاصًا هو أنه أثار أربعة تحذيرات من نفس النوع مرة واحدة:
- V3005 يتم تخصيص المتغير "optimumChannels [PixelFormats.Indexed1]" لنفسه. WriteableBitmapConverter.cs 22
- V3005 يتم تخصيص المتغير "optimumChannels [PixelFormats.Indexed8]" لنفسه. WriteableBitmapConverter.cs 23
- V3005 يتم تعيين متغير "optimumTypes [PixelFormats.Indexed1]" لنفسه. WriteableBitmapConverter.cs 50
- V3005 يتم تعيين متغير "optimumTypes [PixelFormats.Indexed8]" لنفسه. WriteableBitmapConverter.cs 51
static WriteableBitmapConverter() { optimumChannels = new Dictionary <PixelFormat, int>(); optimumChannels[PixelFormats.Indexed1] =
يتم تعريف فئة
PixelFormats في مساحة اسم
System.Windows.Media وهي عبارة عن مجموعة من تنسيقات البكسل المختلفة. يشير المحلل إلى أن عناصر
optimumChannels [PixelFormats.Indexed1] و
optimumChannels [PixelFormats.Indexed8] يتم تعيين قيم للمرة الثانية في طريقة
WriteableBitmapConverter ، والتي لا معنى لها. من غير الواضح ما إذا كان هذا مجرد خطأ مطبعي أم أن المبرمج يعني شيئًا آخر. بالمناسبة ، يعد هذا المقتطف مثالًا حيًا على الطريقة التي يمكن بها للمحللين الاستاتيكيين: النظر إلى مجموعة من الخطوط المتشابهة يجعلك أقل تركيزًا - فلا تخطئ الأخطاء المطبعية على الرغم من مراجعة الكود. على الرغم من ذلك ، لا تواجه المحللون الثابتون مشكلة في الحفاظ على الانتباه ولا يحتاجون إلى الراحة ، حتى يتمكنوا من التقاط الأخطاء من هذا القبيل دون أي جهد.
تشعر بقوة التحليل الثابت.رسالة تشخيص PVS-Studio :
V3021 هناك
بيانان "if" مع تعبيرات شرطية متطابقة. تحتوي العبارة "if" الأولى على طريقة إرجاع. هذا يعني أن العبارة 'if' الثانية لا معنى لها InputArray.cs 394
private static MatType EstimateType(Type t) { .... if (t == typeof(Vec2b)) return MatType.CV_8UC2; if (t == typeof(Vec3b)) return MatType.CV_8UC3; if (t == typeof(Vec4b)) return MatType.CV_8UC4; if (t == typeof(Vec6b)) return MatType.CV_8UC(6); if (t == typeof(Vec2s))
هذا الخطأ يشبه إلى حد ما سابقتها. المطور يتحقق من نفس الحالة مرتين. ليس من المنطقي هنا أن يكون فرعًا من "المكررة"
إذا لم يتم تنفيذ العبارة بسبب:
- إذا كان الشرط الأول صحيحاً ، فستعود الطريقة ؛
- إذا كان الشرط الأول خاطئًا ، فسيكون الخطأ خاطئًا أيضًا لأن المتغير الجاري فحصه ، لا يتغير بين الشيكين.
هذا الكود يحتاج إلى مراجعة. من المحتمل جدًا أن النسخة الثانية من
Vec2s كانت تعني في الواقع بعض المتغيرات الأخرى.
رسالة تشخيص PVS-Studio :
V3010 يجب استخدام قيمة الإرجاع للدالة 'ToString'. ImgProcTest.cs 80
public static RectanglesIntersectTypes RotatedRectangleIntersection(RotatedRect rect1, RotatedRect rect2, out Point2f[] intersectingRegion) { using (var intersectingRegionVec = new VectorOfPoint2f()) { int ret = NativeMethods .imgproc_rotatedRectangleIntersection_vector( rect1, rect2, intersectingRegionVec.CvPtr); intersectingRegion = intersectingRegionVec.ToArray(); return (RectanglesIntersectTypes) ret; } } public void RotatedRectangleIntersectionVector() { var rr1 = new RotatedRect(new Point2f(100, 100), new Size2f(100, 100), 45); var rr2 = new RotatedRect(new Point2f(130, 100), new Size2f(100, 100), 0); Cv2.RotatedRectangleIntersection(rr1, rr2, out var intersectingRegion); .... intersectingRegion.ToString(); }
يتم الوصول إلى طريقة
RotatedRectangleIntersection من خلال المعلمة
intersectingRegion وإرجاع مجموعة من عناصر النوع
Point2f . بمجرد ملء
intersectingRegion بالقيم ، يتم استدعاء الأسلوب
ToString () على الصفيف. لا يؤثر هذا على عناصر الصفيف بأي شكل من الأشكال ولا يتم تنفيذ أي عمل مفيد في السطر الأخير ، لذلك سيكون من العدل افتراض أن المطور قد نسي ببساطة إزالة هذه القطعة.
رسائل تشخيص PVS-Studio:- V3021 هناك بيانان "if" مع تعبيرات شرطية متطابقة. تحتوي العبارة "if" الأولى على طريقة إرجاع. هذا يعني أن العبارة "if" الثانية لا معنى لها Cv2_calib3d.cs 1370
- تعبير V3022 'objectPoints == null' غير صحيح دائمًا. Cv2_calib3d.cs 1372
public static double CalibrateCamera(....) { if (objectPoints == null) throw new ArgumentNullException(nameof(objectPoints)); if (objectPoints == null) throw new ArgumentNullException(nameof(objectPoints)); .... }
لقد قمنا باستنساخ الكود هنا ، وبالتالي التحذيران. الأول يقول أنه في
حالة تحقق البيانات من نفس الحالة. إذا كان هذا الشرط صحيحًا ، فستُرجع الطريقة في الجزء السفلي من العلامة الأولى. وبالتالي ، فإن الشرط الثاني سيكون دائمًا خاطئًا ، وهو ما يقوله لنا التحذير الثاني. يبدو أن المستنسخة استنسخت تلك القطعة باستخدام نسخة لصق ولكن نسيت تغييرها.
لطيف نسخ لصق.تحذيرات أخرى من هذا النوع:
- V3021 هناك بيانان "if" مع تعبيرات شرطية متطابقة. تحتوي العبارة "if" الأولى على طريقة إرجاع. هذا يعني أن العبارة "if" الثانية لا معنى لها Cv2_calib3d.cs 1444
- تعبير V3022 'objectPoints == null' غير صحيح دائمًا. Cv2_calib3d.cs 1446
رسالة تشخيص PVS-Studio: تعبير
V3022 'label == MarkerValue' غير صحيح دائمًا. Labeller.cs 135
internal static class Labeller { .... private const int MarkerValue = -1; public static int Perform(Mat img, CvBlobs blobs) { .... int label = 0; int lastLabel = 0; CvBlob lastBlob = null; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { if (imgIn[x + y * step] == 0) continue; bool labeled = labels[y, x] != 0; if (....) { labeled = true;
يتم إنشاء
تسمية باسم متغير وتهيئتها إلى 0. إذا كان هناك شرط معين صحيح ، فستتزايد بواحد. والأكثر من ذلك ، أن هذا المتغير لن يتراجع أبدًا في هذا المقتطف. لذلك ، التحقق من الثبات -1 ، كما في السطر الذي أشار إليه المحلل ، ليس له أي معنى.
رسالة تشخيص PVS-Studio: V3038 تم تمرير الوسيطة إلى الطريقة عدة مرات. من الممكن أن يتم تمرير وسيطة أخرى بدلاً من ذلك. Cv2_photo.cs 124
public static void FastNlMeansDenoisingMulti(....) { .... NativeMethods.photo_fastNlMeansDenoisingMulti( srcImgPtrs, srcImgPtrs.Length, dst.CvPtr, imgToDenoiseIndex, templateWindowSize, h, templateWindowSize, searchWindowSize); .... }
لفهم ما يقوله المحلل ، دعونا نلقي نظرة على
معلمات طريقة
photo_fastNlMeansDenoisingMulti :
public static extern void photo_fastNlMeansDenoisingMulti( IntPtr[] srcImgs, int srcImgsLength, IntPtr dst, int imgToDenoiseIndex, int temporalWindowSize, float h, int templateWindowSize, int searchWindowSize)
دعونا تبسيط الأمر أكثر لجعلها واضحة تماما. قارن هذه الخطوط:
NativeMethods.photo_fastNlMeansDenoisingMulti( .... templateWindowSize, .... templateWindowSize, ....); public static extern void photo_fastNlMeansDenoisingMulti( .... int temporalWindowSize, .... int templateWindowSize, ....)
يتم الإعلان عن متغير
templateWindowSize مرتين ، ولكن يجب أن تكون أول مرة يتم ذكرها بالفعل إعلان
temporalWindowSize . شيء آخر لم يعجبه المحلل هو عدم استخدام قيمة
temporalWindowSize في طريقة
photo_fastNlMeansDenoisingMulti على الإطلاق. قد يكون هذا قرارًا واعًا ، لكنني ألقيت نظرة فاحصة على هذا الرمز إذا كنت المؤلف.
تحذيرات أخرى من هذا النوع:
- V3038 تم تمرير الوسيطة إلى الطريقة عدة مرات. من الممكن أن يتم تمرير وسيطة أخرى بدلاً من ذلك. Cv2_photo.cs 149
- V3038 تم تمرير الوسيطة إلى الطريقة عدة مرات. من الممكن أن يتم تمرير وسيطة أخرى بدلاً من ذلك. Cv2_photo.cs 180
- V3038 تم تمرير الوسيطة إلى الطريقة عدة مرات. من الممكن أن يتم تمرير وسيطة أخرى بدلاً من ذلك. Cv2_photo.cs 205
المثال التالي يشبه إلى حد ما المثال السابق.
رسالة تشخيص PVS-Studio: V3066 تم تمرير الترتيب غير الصحيح المحتمل للوسيطات إلى طريقة 'calib3d_Rodrigues_MatToVec': 'matrixM.CvPtr' و 'vectorM.CvPtr'. Cv2_calib3d.cs 86
public static void Rodrigues(double[,] matrix, out double[] vector, out double[,] jacobian) { .... using (var jacobianM = new Mat<double>()) { NativeMethods.calib3d_Rodrigues_MatToVec (matrixM.CvPtr, vectorM.CvPtr, jacobianM.CvPtr); .... } }
لنلقِ نظرة على
معلمات طريقة
calib3d_Rodrigues_MatToVec :
public static extern void calib3d_Rodrigues_MatToVec( IntPtr vector, IntPtr matrix, IntPtr jacobian)
يبدو أن طريقة
calib3d_Rodrigues_MatToVec تسمى مع الوسيطتين
matrixM.CvPtr و
vectorM.CvPtr تم التبديل بينهما بطريق الخطأ. يجب على المؤلفين التحقق من هذا المقتطف: قد يكون هناك خطأ يعوق الحسابات الصحيحة.
رسالة تشخيص PVS-Studio: V3063 جزء من التعبير الشرطي خطأ دائمًا إذا تم تقييمه: data == null. Mat.cs 3539
private void CheckArgumentsForConvert(....) { .... if (data == null) throw new ArgumentNullException(nameof(data)); MatType t = Type(); if (data == null || (data.Length * dataDimension)
يشير المحلل إلى أن
بيانات الفحص الثانية
== فارغة لن تكون
صحيحة أبدًا لأنه إذا كانت
البيانات مساوية
للقيمة الخالية في الشرط الأول ، فسيتم رفع استثناء ولن يصل التنفيذ إلى الفحص الثاني أبدًا.
أعلم أنك متعب ، لكننا انتهينا تقريبًا.رسالة تشخيص PVS-Studio: V3127 تم العثور على شظايا رمز مشابه. ربما ، هذا خطأ مطبعي ويجب استخدام متغير "window" بدلاً من "src2" Cv2_imgproc.cs 1547
public static Point2d PhaseCorrelateRes(....) { if (src1 == null) throw new ArgumentNullException(nameof(src1)); if (src2 == null) throw new ArgumentNullException(nameof(src2)); if (window == null) throw new ArgumentNullException(nameof(src2));
رصد المحلل خطأ مطبعي في هذا المقتطف. يتم التحقق من المتغيرات
خالية ، وإذا كان هذا صحيحًا ، يلقي كل تحقق استثناء. ومع ذلك ، لا يعمل بشكل صحيح لمتغير
النافذة . إذا كانت قيمتها تساوي قيمة
فارغة ، فسيتم طرح استثناء مطابق أيضًا ولكن مع النص الخاطئ. لن يكون ذكر
نافذة . سيكون
src2 بدلا من ذلك. يجب مراجعة الشرط على النحو التالي:
if (window == null) throw new ArgumentNullException(nameof(window));
رسالة تشخيص PVS-Studio: تم اكتشاف رمز غير
قابل للوصول
V3142 . من الممكن وجود خطأ. MatOfT.cs 873
الآن ، فقط من أجل التغيير ، دعونا نلقي نظرة على الحالة التي يكون فيها المحلل صحيحًا تقنيًا بشأن الشفرة التي يتعذر الوصول إليها ، ولكن في الواقع لا يوجد خطأ. إنه تحذير يمكن أن يطلق عليه كلاً من الصواب والخطأ في نفس الوقت.
public new Mat<TElem> SubMat(params Range[] ranges) { Mat result = base.SubMat(ranges); return Wrap(result); }
يخبرنا المحلل أن بيان
الإرجاع غير قابل للوصول. دعنا ننظر إلى
نص أسلوب
SubMat لمعرفة ما إذا كان المحلل يقول الحقيقة.
public Mat SubMat(params Range[] ranges) { throw new NotImplementedException(); }
كما ترى ، فإن الوظيفة غير مكتملة حاليًا وستظل دائمًا استثناء. المحلل هو الصحيح تماما مشيرا إلى رمز غير قابلة للوصول - لكنها ليست علة حقيقية.
العيوب الثلاثة التالية من نفس النوع ، لكنها باردة جدًا ولم أتمكن من مساعدتي بما في ذلك العيوب الثلاثة.
رسالة تشخيص PVS-Studio: تعبير
V3022 'String.IsNullOrEmpty ("winName")' خطأ دائمًا. Cv2_highgui.cs 46
public static void DestroyWindow(string winName) { if (String.IsNullOrEmpty("winName")) .... }
رسالة تشخيص PVS-Studio: تعبير
V3022 'string.IsNullOrEmpty ("fileName")' خطأ دائمًا. FrameSource.cs 37
public static FrameSource CreateFrameSource_Video(string fileName) { if (string.IsNullOrEmpty("fileName")) .... }
رسالة تشخيص PVS-Studio: تعبير
V3022 'string.IsNullOrEmpty ("fileName")' خطأ دائمًا. FrameSource.cs 53
public static FrameSource CreateFrameSource_Video_CUDA(string fileName) { if (string.IsNullOrEmpty("fileName")) .... }
في بعض الأحيان ،
تشير تحذيرات
V3022 (حول تعبيرات صحيحة / خاطئة دائمًا) إلى أخطاء غريبة أو مضحكة حقًا. جميع الأمثلة الثلاثة المذكورة أعلاه لها نفس الخطأ في نفوسهم. تحتوي الطريقة على معلمة من
سلسلة type التي يجب التحقق من قيمتها. ما يتم تحديده بدلاً من ذلك ، هو عبارة عن سلسلة حرفية يكون نصها هو اسم المتغير ، أي اسم المتغير المحاط بعلامات اقتباس.
يجب أن يكون المبرمج قد كتب كتلة خاطئة من التعليمات البرمجية مرة واحدة ثم قام باستنساخها من خلال النسخ واللصق.
استنتاج
لقد قام مطورو OpenCvSharp بعمل كبير ومهم ، وبصفتي مستخدمًا لمكتبتهم ، أنا ممتن جدًا لذلك. شكرا يا شباب!
ولكن الآن بعد أن أصبحت جزءًا من فريق PVS-Studio وشاهدت رمز المكتبة ، يجب أن أقول إن جانب الجودة لم يحظى بالاهتمام المناسب. لا يبدو المشروع وكأنه يتم فحصه بانتظام بواسطة أجهزة التحليل الثابتة ، ويبدو أن العديد من الأخطاء تم إصلاحها باستخدام تقنيات أكثر تكلفة (مثل الاختبار أو ملاحظات المستخدم) ، وبعض الكائنات لا تزال تعيش داخل الكود وهو أننا اللحاق مع محلل لدينا. تمت مناقشة هذا الموضوع بمزيد من التفصيل في
هذا المنشور الصغير حول فلسفة التحليل الثابت.
نظرًا لأن OpenCvSharp مفتوح المصدر ومتاح مجانًا على GitHub ، يمكن لمؤلفيها استخدام أحد
خيارات الترخيص المجانية لبرنامج PVS-Studio لبدء استخدامه على أساس منتظم.
شكرا للقراءة. لا تتردد في
تنزيل نسخة تجريبية من PVS-Studio للتحقق من المشاريع الخاصة بك.