التعلم الآلي في التحليل الثابت لبرنامج شفرة المصدر

التعلم الآلي في التحليل الثابت

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

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

مناهج جديدة


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

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



لنلقِ نظرة على بعض الأمثلة المعروفة:

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

DeepCode


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



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

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





تتوفر أيضًا سلسلة من المنشورات حول الطرق الأساسية للمحلل على الموقع.

استنتج


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

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

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

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

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

SapFix


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



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

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

Embold


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



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



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



باستخدام البرمجة اللغوية العصبية NLP ، تقوم Embold بتفكيك الكود والبحث عن الترابط والتبعيات بين الدوال والطرق ، مما يوفر وقت إعادة التجهيز.



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

المصدر {د}


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



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

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

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

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



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





يركز التعليم على عدة عناصر أساسية: المسافات ، الجدولة ، فواصل الأسطر ، إلخ.



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

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

ارتكاب ذكي


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

CodeGuru


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

يقول الموقع أن CodeGuru كانت تتعلم على قاعدة كود Amazon ، بالإضافة إلى أكثر من 10،000 مشروع مفتوح المصدر.

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



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

يُقال أيضًا أن توصيات كود المستخدم تميل إلى التحسن بعد تلقي التعليقات عليها.

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

  • أفضل الممارسات AWS
  • التزامن
  • تسرب الموارد
  • تسرب المعلومات السرية
  • "أفضل ممارسات" الترميز العامة

شكوكنا


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

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

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

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

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


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

if (A == A) 

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

فهل من الممكن تحديد هذه العيوب في التعليمات البرمجية باستخدام خوارزميات ML؟ نعم هو كذلك. الشيء - لماذا نحتاجها؟

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

بعد كل شيء ، نريد أن نكتشف ليس فقط حالات (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("Oh boy! Holy cow!", left, 501, Level_1, "CWE-571"); } } 

وهذا كل شيء! لا تحتاج إلى أي قاعدة من الأمثلة ل ML!

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

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

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

  • malloc
  • memcmp
  • سلسلة :: فارغة

هذا ما يفعله تشخيص PVS-Studio V530 .

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

استغرق تنفيذ التشخيص V530 مع جميع الاستثناءات 258 سطر من التعليمات البرمجية في محلل PVS-Studio ، 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: Memory Leaks ". إذا كانت الحالة (pkey.n0inv == 0) صحيحة ، فستخرج الوظيفة دون تحرير المخزن المؤقت ، حيث يتم تخزين المؤشر في المتغير n .

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

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

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

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

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

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

التعلم على كمية كبيرة من شفرة المصدر المفتوح


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

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

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

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

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

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

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

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

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

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

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

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

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

دعنا ننظر إلى هذه المشكلة باستخدام مثال معين. ظهرت مجموعة تستند إلى حلقة في 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 ، حيث يمكن للمرء قراءة وصف بديل للمشكلة. ومع ذلك ، في بعض الأحيان لا يفهم المستخدمون شيئًا ما ، ويطلبون منا توضيح الأسئلة.

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

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

 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(); // The line is added 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) المساعد ، التي تنتجها شركة " سيلفر بوليت ".

لا أعتقد أن هناك حاجة إلى توضيحات محددة حول سبب صعوبة نهج ML في اللغات المتخصصة.

فارق بسيط الخامس. 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") forstrcmp (x ، "y")؟

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

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

سيتم إجراء التعديلات في حالة ما إذا كنا نتحدث عن مؤشر عادي. إذا لم نأخذ في الاعتبار النوع المتغير ، فقد يتعلم المحلل إصدار تحذيرات جيدة وأخرى سيئة (على سبيل المثال مع الحالة std :: string ).

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

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

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

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



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

نعم ، هناك العديد من الأمثلة على الاستخدام. ربما سيتعلم المحلل أن يلاحظ ، على سبيل المثال ، الأنماط التالية:

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

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

هناك وظائف شعبية وهناك عدد قليل منها. هناك لا تحظى بشعبية ، ولكن هناك الكثير منهم. على سبيل المثال ، هناك الاختلافات التالية من دالة مقارنة السلسلة:

  • g_ascii_strncasecmp - 35،695
  • lstrcmpiA - 27،512
  • _wcsicmp_l - 5،737
  • _strnicmp_l - 5،848
  • _mbscmp_l - 2،458
  • وغيرها.

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

في PVS-Studio ، نقوم بتعليق الميزات يدويًا. على سبيل المثال ، في الوقت الحالي ، تم تعليق حوالي 7،200 وظيفة لـ C و C ++. هذا هو ما نحتفل به:

  • 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 عامًا أيضًا.

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

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

  • Outdated recommendations. As mentioned, languages change, and recommendations for their use change, respectively. If the analyzer learns on old source code, it might start issuing outdated recommendations at some point. Example. Formerly, C++ programmers have been recommended using auto_ptr instead of half-done pointers. This smart pointer is now considered obsolete and it is recommended that you use unique_ptr .
  • Data models. At the very least, C and C++ languages have such a thing as a data model . This means that data types have different number of bits across platforms. If you don't take this into account, you can incorrectly teach the analyzer. For example, in Windows 32/64 the long type always has 32 bits. But in Linux, its size will vary and take 32/64 bits depending on the platform's number of bits. Without taking all this into account, the analyzer can learn to miscalculate the size of the types and structures it forms. But the types also align in different ways. All this, of course, can be taken into account. You can teach the analyzer to know about the size of the types, their alignment and mark the projects (indicate how they are building). However, all this is an additional complexity, which is not mentioned in the research articles.
  • Behavioral unambiguousness. Since we're talking about ML, the analysis result is more likely to have probabilistic nature. That is, sometimes the erroneous pattern will be recognized, and sometimes not, depending on how the code is written. From our experience, we know that the user is extremely irritated by the ambiguity of the analyzer's behavior. He wants to know exactly which pattern will be considered erroneous and which will not, and why. In the case of the classical analyzer developing approach, this problem is poorly expressed. Only sometimes we need to explain our clients why there is a/there is no analyzer warning and how the algorithm works, what exceptions are handled in it. Algorithms are clear and everything can always be easily explained. An example of this kind of communication: " False Positives in PVS-Studio: How Deep the Rabbit Hole Goes ". It's not clear how the described problem will be solved in the analyzers built on ML.

Conclusions


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

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

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

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

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

PS


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

Luddites


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

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

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

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


All Articles