استخدام التعلم الآلي في التحليل الثابت لكود مصدر البرنامج

استخدام التعلم الآلي في التحليل الثابت

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

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

مناهج جديدة


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

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


النظر في بعض الأمثلة المعروفة:

  1. DeepCode
  2. يستنتج ، سابينز ، سابفيكس
  3. Embold
  4. المصدر {د}
  5. ذكي ارتكب ، مساعد التزام
  6. CodeGuru

DeepCode


Deep Code هو أداة للبحث عن الثغرات الأمنية في شفرة البرامج المكتوبة بلغات Java و JavaScript و TypeScript و Python ، حيث يتعلم التعلم الآلي كمكون. وفقا لبوريس باسكاليف ، أكثر من 250 ألف قواعد تعمل بالفعل. يتم تدريب هذه الأداة على التغييرات التي أجراها المطورون على الكود المصدري للمشروعات المفتوحة (مليون مستودع). تقول الشركة نفسها أن مشروعهم هو Grammarly للمطورين.



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

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





يتم أيضًا نشر مجموعة من المنشورات حول الطرق التي يعتمد عليها المحلل على الموقع.

استنتج


يحاول Facebook على نطاق واسع إدخال أساليب جديدة في منتجاته. لم يتجاهلوا انتباههم والتعلم الآلي. في عام 2013 ، اشتروا شركة ناشئة تقوم بتطوير محلل ثابت قائم على الآلة. وفي عام 2015 ، أصبحت شفرة المصدر للمشروع مفتوحة .

Infer هو محلل ثابت للمشاريع المكتوبة بلغات Java و C و C ++ و Objective-C ، التي طورها Facebook. وفقًا للموقع ، يتم استخدامه أيضًا في Amazon Web Services و Oculus و Uber وغيرها من المشاريع الشائعة.

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

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

  • الخروج من مجموعة ؛
  • استثناءات typecast.
  • تسرب البيانات التي لم يتم التحقق منها ؛
  • حالة سباق العرق.

SapFix


SapFix هو أداة التحرير الآلي. يتلقى معلومات من Sapienz ، أداة أتمتة الاختبار ، ومحلل ثابت Infer ، واستناداً إلى أحدث التغييرات والرسائل ، يختار Infer إحدى الاستراتيجيات العديدة لإصلاح الأخطاء.



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

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

Embold


Embold عبارة عن منصة بدء تشغيل للتحليل الثابت للكود المصدري للبرامج ، والتي كانت تسمى قبل Gamma. يتم إجراء التحليل الثابت على أساس التشخيصات الخاصة بنا ، وكذلك على أساس التحليلات المدمجة مثل Cppheck و SpotBugs و SQL Check وغيرها.



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



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



باستخدام البرمجة اللغوية العصبية (NLP) ، يقوم Embold بتقسيم الشفرة إلى أجزاء ويبحث عن الترابط والتبعيات بين الوظائف والأساليب بينهما ، مما يوفر وقت إعادة التجهيز.



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

المصدر {د}


المصدر {d} هو الأكثر انفتاحًا من حيث كيفية تنفيذه من المحللين الذين فحصناهم. إنه أيضًا حل مفتوح المصدر . على موقعه على الويب ، يمكنك (في مقابل عنوان بريدك الإلكتروني) الحصول على كتيب مع وصف للتقنيات التي يستخدمونها. بالإضافة إلى ذلك ، يحتوي على رابط إلى قاعدة المنشورات التي جمعوها فيما يتعلق باستخدام التعلم الآلي في تحليل الشفرة ، بالإضافة إلى مستودع مع مجموعة بيانات للتدريب على التعليمات البرمجية. المنتج نفسه عبارة عن منصة كاملة لتحليل الكود المصدري ومنتج البرنامج ، ويركز ، بدلاً من ذلك ، ليس على المطورين ، ولكن على رابط المديرين. من بين قدراتها ، هناك وظيفة وظيفية لتحديد حجم الديون الفنية ، والاختناقات في عملية التنمية وغيرها من الإحصاءات العالمية بشأن المشروع.



يعتمدون في مقاربتهم لتحليل الكود بمساعدة الآلة على الفرضية الطبيعية ، التي صيغت في مقالة " حول طبيعية البرمجيات ".

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

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

لتحليل الشفرة ، يستخدم المصدر {d} خدمة Babelfish ، والتي يمكنها تحليل ملف التعليمات البرمجية في أي من اللغات المتاحة ، والحصول على شجرة بناء جملة مجردة وتحويلها إلى شجرة بناء جملة عالمية.



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





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



يمكنك قراءة المزيد حول هذا الأمر في المنشور الخاص بهم: " STYLE-ANALYZER: إصلاح التعارضات في نمط الكود مع خوارزميات غير خاضعة للرقابة يمكن تفسيرها ".

بشكل عام ، يعد المصدر {d} منصة واسعة لجمع مجموعة متنوعة من الإحصاءات حول الكود المصدري وعملية تطوير المشروع ، بدءًا من حساب فعالية المطورين ووصولاً إلى تحديد الوقت المستغرق في مراجعة الكود.

ارتكاب ذكي


Clever-Commit عبارة عن محلل أنشأته Mozilla بالتعاون مع Ubisoft. وهو يستند إلى دراسة CLEVER (الجمع بين مستويات أساليب منع الأخطاء وحلها) في Ubisoft ، ومساعد Commit القائم على المنتج ، والذي يحدد الالتزامات المشبوهة التي من المحتمل أن تحتوي على خطأ. نظرًا لحقيقة أن CLEVER يعتمد على مقارنة الكود ، فإنه لا يشير فقط إلى رمز خطير ، ولكنه يقدم أيضًا اقتراحات حول التصحيحات المحتملة. وفقًا للوصف ، في 60-70٪ من الحالات ، تجد Clever-Commit مناطق للمشاكل وبنفس التردد يقدم تصحيحات صحيحة لهم. بشكل عام ، هناك القليل من المعلومات حول هذا المشروع وعن الأخطاء التي يمكنه العثور عليها.

CodeGuru


ومؤخراً ، تم تجديد قائمة المحللين الذين يستخدمون التعلم الآلي بمنتج من Amazon يسمى CodeGuru. تعتمد هذه الخدمة على التعلم الآلي ، والذي يتيح لك العثور على أخطاء في الكود ، بالإضافة إلى تحديد الأقسام المكلفة فيها. حتى الآن ، التحليل مخصص لرمز Java فقط ، لكنهم يكتبون عن دعم للغات أخرى في المستقبل. على الرغم من الإعلان عن ذلك مؤخرًا ، يقول أندي جاسي ، الرئيس التنفيذي لـ AWS (خدمات الويب من أمازون) ، إنه استخدمه لفترة طويلة في أمازون نفسها.

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

في الواقع ، يتم تقسيم الخدمة إلى قسمين: CodeGuru المعلقين ، المدربين من خلال البحث عن القواعد المرتبطة والبحث عن الأخطاء في التعليمات البرمجية ، و CodeGuru Profiler ، الذي يراقب أداء التطبيق.



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

يُقال أيضًا أن توصيات التعليمات البرمجية المخصصة تتحسن بعد تلقي الملاحظات على التوصيات.

قائمة الأخطاء التي يستجيب عليها المراجع غير واضحة إلى حد ما ، حيث لم يتم نشر وثائق محددة للأخطاء:
  • أفضل الممارسات AWS
  • تواز
  • تسرب الموارد
  • تسرب المعلومات السرية
  • "أفضل الممارسات" الشائعة للترميز

شكوكنا


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

  1. تدريب محلل ثابت يدويًا للبحث عن مشاكل متعددة باستخدام أمثلة الرموز الاصطناعية والحقيقية ؛
  2. تدريب الخوارزميات على عدد كبير من شفرة المصدر المفتوح (GitHub) وتغيير التاريخ ، وبعد ذلك سيبدأ المحلل نفسه في اكتشاف الأخطاء وحتى اقتراح تصحيحات.

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

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

دليل ثابت محلل التدريب


افترض أننا نريد استخدام ML بحيث يبدأ المحلل في البحث عن الحالات الشاذة من النموذج التالي في التعليمات البرمجية:

if (A == A) 

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

لذلك ، هل من الممكن البحث عن مثل هذه العيوب في الكود باستخدام خوارزميات التعلم الآلي؟ يمكنك ذلك. لكن ليس من الواضح سبب القيام بذلك!

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

بعد كل شيء ، لا نريد البحث فقط عن الحالات (A == A) ، ولكن أيضًا عن:

  • إذا (X && A == A)
  • إذا (A + 1 == A + 1)
  • إذا (A [i] == A [i])
  • إذا ((A) == (A))
  • و هكذا.



الآن دعونا نرى كيف سيتم تطبيق مثل هذا التشخيص البسيط في PVS-Studio:

 void RulePrototype_V501(VivaWalker &walker, const Ptree *left, const Ptree *right, const Ptree *operation) { if (SafeEq(operation, "==") && SafeEqual(left, right)) { walker.AddError(" , !", left, 501, Level_1, "CWE-571"); } } 

وهذا كل شيء. لا قاعدة تدريب عينة حاجة!

في المستقبل ، يجب تعليم التشخيصات أن تأخذ في الاعتبار عددًا من الاستثناءات وأن تفهم أنك بحاجة إلى أقسم (A [0] == A [1-1]). ومع ذلك ، كل هذا سهل جدا للبرمجة. لكن مع وجود قاعدة للتدريب ، سيكون كل شيء سيئًا.

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

دعونا ننظر الآن في بعض القواعد الأخرى. على سبيل المثال ، يجب استخدام نتيجة بعض الوظائف. ليس من المنطقي أن ندعو لهم دون استخدام النتيجة. فيما يلي بعض هذه الميزات:
  • malloc
  • memcmp
  • سلسلة :: فارغة

بشكل عام ، هذا ما تقوم به تشخيصات V530 المطبقة في PVS-Studio.

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

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

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

 uint32_t* BnNew() { uint32_t* result = new uint32_t[kBigIntSize]; memset(result, 0, kBigIntSize * sizeof(uint32_t)); return result; } std::string AndroidRSAPublicKey(crypto::RSAPrivateKey* key) { .... uint32_t* n = BnNew(); .... RSAPublicKey pkey; pkey.len = kRSANumWords; pkey.exponent = 65537; // Fixed public exponent pkey.n0inv = 0 - ModInverse(n0, 0x100000000LL); if (pkey.n0inv == 0) return kDummyRSAPublicKey; // <= .... } 

مثال مأخوذ من مقالة " Chromium: تسرب الذاكرة ". إذا تم استيفاء الشرط (pkey.n0inv == 0) ، فستخرج الوظيفة دون تحرير المخزن المؤقت ، ويتم تخزين المؤشر في المتغير n .

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

تعمل خوارزمية تتبع القيمة العامة. لا يهم كيف يتم كتابة التعليمات البرمجية. لا يهم ما هو الآخر في الوظيفة غير المرتبطة بالعمل مع المؤشرات. الخوارزمية عالمية ويجد تشخيص V773 الكثير من الأخطاء في مشاريع مختلفة. نرى كيف مختلفة شظايا رمز حيث يتم اكتشاف الأخطاء!

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

هناك شك في أن الكثير من الأمثلة ستكون مطلوبة للتدريب بحيث تصبح المهمة شاقة. نحن لا نقول أنه غير ممكن التحقيق. نشك في أن تكاليف إنشاء محلل ستؤتي ثمارها.

هذا التشبيه. يشبه القياس آلة حاسبة ، حيث بدلاً من التشخيص ، من الضروري برمجة العمليات الحسابية. نحن على يقين من أنه يمكنك تعليم آلة حاسبة تعتمد على ML لإضافة أرقام بشكل جيد من خلال تقديم قاعدة معرفة حول نتيجة العمليات 1 + 1 = 2 ، 1 + 2 = 3 ، 2 + 1 = 3 ، 100 + 200 = 300 ، وهكذا. كما تعلمون ، فإن استصواب تطوير مثل هذه الآلة الحاسبة هو سؤال كبير (إذا لم يتم تخصيص منحة لذلك :). يمكن كتابة آلة حاسبة أبسط وأسرع وأكثر دقة وموثوقية باستخدام العملية العادية "+" في الكود.

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

التعلم من الكثير من المصادر المفتوحة


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

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

أول فارق بسيط. مصدر البيانات.

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

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

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

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

لذلك ، لم نصل بعد إلى التدريب ، ولكن هناك بالفعل فروق دقيقة :).

فارق بسيط الثاني. تأخر في التنمية.

سوف يخضع المحللون الذين سيتم تدريبهم على قواعد البيانات مثل GitHub دائمًا لمتلازمة مثل "التخلف العقلي". وذلك لأن لغات البرمجة تتغير مع مرور الوقت.

قدمت C # 8.0 أنواع Nullable Reference للمساعدة في التعامل مع استثناءات Null Reference (NREs). JDK 12 يقدم بيان تبديل جديد ( JEP 325 ). في C ++ 17 ، أصبح من الممكن تنفيذ الإنشاءات الشرطية في مرحلة التجميع ( constexpr if ). و هكذا.

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

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

دعنا ننظر إلى هذه المشكلة مع مثال محدد. ظهرت مجموعة تستند إلى حلقة في C ++ 11. ويمكنك كتابة الكود التالي ، مع تكرار جميع العناصر الموجودة في الحاوية:

 std::vector<int> numbers; .... for (int num : numbers) foo(num); 

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

خذ بعين الاعتبار الكود غير الصحيح التالي:

 for (int num : numbers) { numbers.push_back(num * 2); } 

سيقوم المحول البرمجي بتحويله إلى شيء مثل هذا:

 for (auto __begin = begin(numbers), __end = end(numbers); __begin != __end; ++__begin) { int num = *__begin; numbers.push_back(num * 2); } 

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

لذلك ، منذ فترة طويلة نمط الخطأ معروفة ووصفها في الأدب. يقوم محلل PVS-Studio بتشخيصه باستخدام تشخيصات V789 ووجد بالفعل أخطاء حقيقية في المشروعات المفتوحة.

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

كم سنة يجب أن تمر؟ خمسة؟ عشرة؟

عشرة أشياء كثيرة ، وهل نحن متشائمون؟ لا على الإطلاق. لقد مرت ثماني سنوات من الوقت الذي كُتب فيه هذا المقال ، حيث ظهر على أساس النطاق للحلقة في C ++ 11. لكن حتى الآن ، تم كتابة ثلاث حالات فقط من هذا الخطأ في قاعدة البيانات الخاصة بنا. الأخطاء الثلاثة ليست كثيرة وليست صغيرة. لا ينبغي استخلاص أي رقم من عددهم. الشيء الرئيسي هو أنه يمكنك تأكيد أن نمط الخطأ هذا حقيقي ومن المنطقي اكتشافه.

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

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

على الأرجح ، لن يحدث هذا إلا بعد 10-15 سنة من لحظة ظهور C ++ 11. والآن السؤال الفلسفي. إذا كنت تعرف نمط الخطأ بالفعل ، فسننتظر لسنوات عديدة حتى تتراكم العديد من الأخطاء في المشروعات المفتوحة؟

إذا كانت الإجابة "نعم" ، فمن الممكن التشخيص المعقول لجميع المحللين على أساس ML تشخيص "التخلف العقلي".

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

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

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

فارق بسيط الثالث. الوثائق.

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

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

بالطبع ، في بعض الحالات سيكون كل شيء واضحًا. افترض أن نقاط محلل لهذا الرمز:

 char *p = (char *)malloc(strlen(src + 1)); strcpy(p, src); 

وسوف تقدم ليحل محله:

 char *p = (char *)malloc(strlen(src) + 1); strcpy(p, src); 

من الواضح على الفور أن المبرمج أغلق وأضيف 1 إلى المكان الخطأ. نتيجة لذلك ، سيتم تخصيص ذاكرة أقل.

هنا ، بدون وثائق ، كل شيء واضح. ومع ذلك ، لن يكون هذا هو الحال دائمًا.

تخيل أن المحلل يشير بصمت إلى هذا الرمز:

 char check(const uint8 *hash_stage2) { .... return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE); } 

ويقترح تغيير نوع القيمة المرجعة من char إلى int:

 int check(const uint8 *hash_stage2) { .... return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE); } 

لا توجد وثائق للتحذير. وعلى ما يبدو ، فإن نص التحذير نفسه ، كما نفهمه ، لن يكون كذلك ، إذا كنا نتحدث عن محلل مستقل تمامًا.

ما يجب القيام به ما هو الفرق؟ هل يجب أن أقوم بمثل هذا الاستبدال؟

من حيث المبدأ ، هنا يمكنك أن تأخذ فرصة وتوافق على إصلاح الكود. على الرغم من قبول التعديلات دون فهمها ، إلا أنها تدار بشكل جيد ... :) يمكنك إلقاء نظرة على وصف وظيفة memcmp وقراءة أن الدالة تقوم بإرجاع قيم int : 0 أكبر من الصفر وأقل من الصفر. لكن رغم كل ذلك ، قد لا يكون من الواضح سبب إجراء التغييرات إذا كانت الشفرة تعمل بالفعل بنجاح.

الآن ، إذا كنت لا تعرف الهدف من هذا التعديل ، فاقرأ وصف تشخيصات V642 . يصبح من الواضح على الفور أن هذا خطأ حقيقي. علاوة على ذلك ، يمكن أن يسبب الضعف.

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

 ObjectOutputStream out = new ObjectOutputStream(....); SerializedObject obj = new SerializedObject(); obj.state = 100; out.writeObject(obj); obj.state = 200; out.writeObject(obj); out.close(); 

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

 ObjectOutputStream out = new ObjectOutputStream(....); SerializedObject obj = new SerializedObject(); obj.state = 100; out.writeObject(obj); obj = new SerializedObject(); //    obj.state = 200; out.writeObject(obj); out.close(); 

بدلاً من تغيير الكائن وإعادة تسجيله ، يتم إنشاء كائن جديد وتسلسله بالفعل.

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

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

إذا كانت هناك وثائق ، يصبح كل شيء شفافًا. فئة java.io.ObjectOuputStream ، والتي تُستخدم للتسلسل ، تخزن الكائنات القابلة للكتابة. هذا يعني أنه لن يتم إجراء تسلسل لنفس الكائن مرتين. بمجرد تسلسل الفئة للكائن ، وفي المرة الثانية يكتب ببساطة رابطًا لنفس الكائن الأول في الدفق. اقرأ المزيد: V6076 - سيستخدم التسلسل المتكرر حالة الكائن المخزنة مؤقتًا من التسلسل الأول.

نأمل أن نتمكن من شرح أهمية الحصول على الوثائق. والآن السؤال. كيف ستظهر وثائق محلل قائم على ML؟

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

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

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

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

فارق بسيط الرابع. لغات متخصصة للغاية.

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

النظر في هذا مع مثال محدد. للبدء ، انتقل إلى GitHub وابحث عن مستودعات للغة Java الشائعة.

النتيجة: اللغة: "Java": 3،128،884 نتائج مستودع التخزين المتاحة

الآن لنأخذ اللغة المتخصصة "1C Enterprise" المستخدمة في التطبيقات المحاسبية الصادرة عن الشركة الروسية 1C .

النتيجة: اللغة: "1C Enterprise": 551 نتائج تخزين متاحة

ربما ليست هناك حاجة المحللين لهذه اللغة؟ هناك حاجة. هناك حاجة عملية لتحليل مثل هذه البرامج ، والمحللات المقابلة موجودة بالفعل. على سبيل المثال ، هناك SonarQube 1C (BSL) البرنامج المساعد الذي تم تصنيعه بواسطة Silver Bullet .

أعتقد أنه لا يلزم تقديم تفسير خاص لماذا سيكون أسلوب التعلم الآلي صعباً بالنسبة للغات المتخصصة.

فارق بسيط الخامس. C ، C ++ ، # تشمل .

تنجذب المقالات المخصصة للبحث في مجال التحليل الثابت للرمز المستند إلى ML إلى لغات مثل Java و JavaScript و Python. ويفسر هذا من خلال شعبيتها القصوى. لكن اللغات C و C ++ تجاوزت إلى حد ما ، على الرغم من أنها بالتأكيد لا يمكن أن يطلق عليها شعبية.

لدينا فرضية أن هذه ليست مسألة شعبية / وعد ، ولكن هناك مشاكل مع لغات C و C ++. والآن سنقوم "بسحب" مشكلة واحدة غير مريحة للمراجعة.

يمكن أن يكون من الصعب للغاية ترجمة ملف c / cpp المستخلص. على الأقل ، لن يعمل لتنزيل المشروع من GitHub ، وتحديد بعض ملفات cpp وتجميعها فقط. الآن سنشرح كيف يرتبط كل هذا بـ ML.

لذلك ، نريد تدريب المحلل. قمنا بتنزيل المشروع من جيثب. نحن نعرف التصحيح ونفترض أنه يصلح الخطأ. نريد أن يكون هذا التعديل مثالاً للتدريب. بمعنى آخر ، لدينا ملف .cpp قبل التحرير وبعده.

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

النظر في مثال. في البداية ، بدا الرمز كالتالي:

 bool Class::IsMagicWord() { return m_name == "ML"; } 

تم إصلاحها مثل هذا:

 bool Class::IsMagicWord() { return strcmp(m_name, "ML") == 0; } 

هل يجب أن أبدأ في التعلم على أساس هذه الحالة ، في المستقبل أقترح استبدال (x == "y") بـ strcmp (x ، "y")؟

لا يمكن الإجابة عن هذا السؤال دون معرفة كيفية الإعلان عن عضو m_name في الفصل. قد يكون هناك ، على سبيل المثال ، مثل هذه الخيارات:

 class Class { .... char *m_name; }; class Class { .... std::string m_name; }; 

سيتم إجراء التعديلات إذا كنا نتحدث عن فهرس عادي. إذا لم تأخذ نوع المتغير في الاعتبار ، فيمكنك تعلم إعطاء تحذيرات ضارة مع تحذيرات مفيدة (في حالة std :: string ).

عادةً ما يتم العثور على تعريفات الفئة في ملفات .h رأس. لذلك توصلنا إلى الحاجة إلى إجراء المعالجة المسبقة من أجل الحصول على جميع المعلومات اللازمة. هذا مهم للغاية بالنسبة إلى C و C ++.

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

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

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



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

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

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

تعليق. إذا كان القارئ لا يزال لا يفهم المشاكل ، فإننا نقترح إجراء التجربة التالية. خذ عشرة مشاريع C ++ متوسطة الحجم من GitHub وحاول تجميعها ، ثم احصل على الإصدار المُعالج مسبقًا لملفات .cpp. بعد ذلك ، فإن السؤال حول تعقيد هذه المهمة سوف تختفي :).

قد تواجه لغات أخرى مشكلات مماثلة ، ولكن في C و C ++ تكون واضحة بشكل خاص.

فارق بسيط السادسة. سعر القضاء على ايجابيات كاذبة.

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

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

 std::vector<int> numbers; .... for (int num : numbers) { if (num < 5) { numbers.push_back(0); break; // , , return } } 

نعم ، هذا عيب. في محلل الكلاسيكية ، والقضاء عليه سريع للغاية ورخيصة. في PVS-Studio ، يتكون تنفيذ هذا الاستثناء من 26 سطرًا من التعليمات البرمجية.

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

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

فارق بسيط السابع. نادرا ما تستخدم ميزات وذيل طويل.

في السابق ، تم النظر في مشكلة اللغات عالية التخصص ، والتي قد لا تكون هناك شفرة مصدر كافية للتعلم. مشكلة مماثلة مع وظائف نادرا ما تستخدم (النظام ، WinAPI ، من المكتبات الشعبية ، وما إلى ذلك).

إذا كنا نتحدث عن وظائف لغة C مثل strcmp ، فهناك أساس للتعلم. جيثب ، نتائج الكود المتاحة:

  • strcmp - 40،462،158
  • stricmp - 1،256،053

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

هل كل شيء رائع؟ لا.نحن هنا نواجه "ذيل طويل". باختصار شديد ، جوهر "الذيل الطويل" هو على النحو التالي. من غير العملي البيع في مكتبة Top50 الأكثر شعبية والتي تتم قراءتها حاليًا. نعم ، سيتم الحصول على كل كتاب ، على سبيل المثال ، أكثر من 100 مرة من الكتب غير المدرجة في هذه القائمة. ومع ذلك ، فإن معظم الإيرادات سوف تأتي من كتب أخرى ، والتي ، كما يقولون ، تجد قرائها. على سبيل المثال ، يحقق متجر Amazon.com على الإنترنت أكثر من نصف الأرباح مما يتجاوز 130 ألف "العناصر الأكثر شعبية".

هناك ميزات شعبية وعدد قليل منها. هناك منها غير شعبية ، ولكن هناك الكثير منها. على سبيل المثال ، لا تزال هناك أنواع من دالة مقارنة السلسلة:

  • g_ascii_strncasecmp - 35،695
  • lstrcmpiA - 27،512
  • _wcsicmp_l - 5،737
  • _strnicmp_l - 5،848
  • _mbscmp_l - 2،458
  • إلخ

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

في PVS-Studio ، نقوم بتعليق الوظائف يدويًا. على سبيل المثال ، بالنسبة إلى C و C ++ ، يتم حاليًا تسمية حوالي 7200 وظيفة. تخضع العلامة إلى:

  • WINAPI
  • مكتبة C القياسية
  • مكتبة النماذج القياسية (STL) ،
  • glibc (مكتبة جنو سي)
  • كيو تي
  • MFC
  • زليب
  • يببنغ
  • بينسل
  • إلخ

من ناحية ، يبدو هذا وكأنه طريق مسدود. من المستحيل التعليق على كل شيء. من ناحية أخرى ، كان يعمل.

الآن السؤال. ما هي فوائد ML؟ مزايا كبيرة غير مرئية ، ولكن الصعوبات واضحة.

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

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

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

 int buffer[10]; size_t n = _fread_nolock(buffer, size_of(int), 100, stream); 

فيما يلي شرح لهذه الوظيفة في PVS-Studio:

 C_"size_t _fread_nolock" "(void * _DstBuf, size_t _ElementSize, size_t _Count, FILE * _File);" ADD(HAVE_STATE | RET_SKIP | F_MODIFY_PTR_1, nullptr, nullptr, "_fread_nolock", POINTER_1, BYTE_COUNT, COUNT, POINTER_2). Add_Read(from_2_3, to_return, buf_1). Add_DataSafetyStatusRelations(0, 3); 

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

الآن دعونا نتحدث عن هذه الوظيفة من حيث ML. جيثب لن يساعدنا. هناك حوالي 15000 إشارة إلى هذه الوظيفة. هناك كود أقل عقلانية. جزء كبير من نتائج البحث مشابه:

 #define fread_unlocked _fread_nolock 

ما هي الخيارات؟

  1. لا تفعل شيئا. هذا هو الطريق إلى أي مكان.
  2. تدريب المحلل بكتابة مئات الأمثلة لهذه الوظيفة وحدها ، بحيث يفهم المحلل العلاقة بين حجم المخزن المؤقت والوسائط الأخرى. نعم ، يمكنك القيام بذلك ، لكنه غير منطقي اقتصاديًا. هذا هو الطريق إلى أي مكان.
  3. , , . , . ML :). .

كما ترون ، لم يتم الجمع بين ML والذيل الطويل من وظائف نادرا ما تستخدم.

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

الهيئات الوظيفية قد لا تكون معروفة. على سبيل المثال ، قد تكون دالة متعلقة بـ WinAPI. إذا كانت هذه وظيفة نادرة الاستخدام ، فكيف يفهم المحلل ما يفعل؟ يمكننا تخيل أنه خلال التدريب سوف يستخدم المحلل Google نفسه ، ويجد وصف الوظيفة ، ويقرأها ويفهمها . علاوة على ذلك ، من الضروري استخلاص استنتاجات عالية المستوى من الوثائق. في وصف _fread_nolockفي أي مكان يقال حول العلاقة بين حجم المخزن المؤقت ، الحجة الثانية والثالثة. يجب أن نستنتج هذه المقارنة بالذكاء الاصطناعي بشكل مستقل ، بناءً على فهم المبادئ العامة للبرمجة وكيف تعمل لغة C ++. يبدو أنه من الممكن أن نفكر في الأمر بجدية في كل هذا خلال 20 عامًا ،

فقد تكون هيئات الوظائف متاحة ، لكن قد لا يكون هناك أي معنى من هذا. النظر في وظيفة مثل memmove . غالبا ما يتم تنفيذها بطريقة ما مثل هذا:

 void *memmove (void *dest, const void *src, size_t len) { return __builtin___memmove_chk(dest, src, len, __builtin_object_size(dest, 0)); } 

وما هو __builtin___memmove_chk ؟ هذه وظيفة مضمّنة يقوم بتنفيذها المترجم نفسه. هذه الوظيفة لا تحتوي على شفرة المصدر.

أو قد يبدو memmove بشيء من هذا القبيل: النسخة الأولى التي ظهرت في المجمع . يمكنك تعليم المحلل أن يفهم متغيرات المجمّع المختلفة ، لكن هناك شيئًا غير صحيح.

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

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

الفروق الدقيقة الأخرى:

هناك نقاط أخرى يجب أن تؤخذ بعين الاعتبار ، لكننا لم نفكر فيها بعناية. والمادة قد جر بالفعل على. لذلك ، نقوم بإدراج بعض الفروق الدقيقة لفترة وجيزة ، وتركها للقارئ للتفكير.
  • . , , . , - . . C++ auto_ptr . unique_ptr .
  • . , C C++ , . , . , . , long Windows 32/64 32 . Linux 32/64 . , . -. , , . , ( ). , .
  • . ML, , , . أي , — , , . , . , , — , . . , / , , , . . : " PVS-Studio: ". , , .

النتائج


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

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

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

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

قد تستفيد منهجية التحليل الثابت من تنفيذ ML ، ولكن لا تبالغ في قدرات هذه التكنولوجيا.

PS


نظرًا لأن المقال هام بشكل عام ، فقد يعتقد شخص ما أننا خائفون من الجديد وكيف حارب Luddites ضد ML ، خوفًا من خسارة السوق بسبب أدوات التحليل الثابتة.

لوديت يونيكورن


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

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

شكرا لاهتمامكم ونحن نقدم التعرف على مقالة " أسباب إدخال محلل الكود الثابت PVS-Studio في عملية التطوير ."



إذا كنت ترغب في مشاركة هذا المقال مع جمهور ناطق بالإنجليزية ، فيرجى استخدام رابط الترجمة: Andrey Karpov ، Victoria Khanieva. التعلم الآلي في التحليل الثابت لبرنامج شفرة المصدر .

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


All Articles