DeepCode: منظر جانبي

الصورة 1

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

ذكرنا بالفعل DeepCode في مقالتنا " استخدام التعلم الآلي في التحليل الثابت لشفرة المصدر للبرامج ." وسرعان ما نشروا مقالة " DeepCode يضيف دعمًا ثابتًا لتحليل التعليمات البرمجية المستندة إلى الذكاء الاصطناعي لـ C و C ++ " مع الإعلان عن دعم لتحليل المشاريع المكتوبة في C و C ++.

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

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

النتيجة الإجمالية لـ DeepCode هي كما يلي:

الصورة 2

هذا لا يعكس تمامًا عدد الرحلات ، حيث يتم تجميعها في تحذير واحد ، كما سأعرض لاحقًا. بالطبع ، من الواضح أن تحليل C / C ++ الثابت في DeepCode هو ابتكار ، وقد بدأ العمل عليه للتو ، ولكن أعتقد أنه من الممكن بالفعل استخلاص بعض الاستنتاجات.

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

صورة 3

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

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

علاوة على ذلك ، لا يعمل تشخيص PVS-Studio فقط على رقم Pi أو الثوابت الأخرى المستخدمة بشكل متكرر ، ولكن أيضًا على ثوابت محددة (على سبيل المثال ، ثابت Landau-Ramanujan) ، والذي قد يكون من الصعب التقاطه بشكل خاطئ بناءً على قاعدة تدريب كبيرة بسبب استخدامها النادر .

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

صورة 7

يعمل PVS-Studio على خيارات مختلفة لتقريب الثوابت.

صورة 8

بما في ذلك وجود رد على القسم الذي تم تغييره:

V624 من المحتمل وجود خطأ مطبعي في ثابت "3.14159263". جرب استخدام ثابت M_PI من <math.h>. Crab.cpp 219

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

دعونا نفكر في عملية أخرى. أصدر DeepCode تحذيرًا واحدًا على الرمز التالي:

bool Shader::loadShaderCode(const char* fragmentShaderCode, ....)
{
  ....
  if (mFragmentShaderSource != NULL)
    free(mFragmentShaderSource);

  ....

  if (mFragmentShaderSource != NULL)
    free(mFragmentShaderSource);
  ....
}

صورة 10

لدى PVS-Studio المزيد من الشكاوى حول هذا الرمز:

  1. V809 Verifying that a pointer value is not NULL is not required. The 'if (mFragmentShaderSource != 0)' check can be removed. Shader.cpp 178
  2. V809 Verifying that a pointer value is not NULL is not required. The 'if (mFragmentShaderSource != 0)' check can be removed. Shader.cpp 194
  3. V774 The 'mFragmentShaderSource' pointer was used after the memory was released. Shader.cpp 194
  4. V586 The 'free' function is called twice for deallocation of the same memory space. Inspect the first argument. Check lines: 179, 195. Shader.cpp 195

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

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

صورة 11

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

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

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

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

صورة 5

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

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

سأعطيك بعض الأخطاء المثيرة للاهتمام التي عثر عليها PVS-Studio.

جزء N1

V773تم إنهاء الوظيفة بدون تحرير مؤشر "الخط". من الممكن حدوث تسرب للذاكرة. PxTkBmpLoader.cpp 166

bool BmpLoader::loadBmp(PxFileHandle f)
{
  ....
  int lineLen = ....;
  unsigned char* line = static_cast<unsigned char*>(malloc(lineLen));
  for(int i = info.Height-1; i >= 0; i--)
  {
    num = fread(line, 1, static_cast<size_t>(lineLen), f);
    if (num != static_cast<size_t>(lineLen)) { fclose(f); return false; }
    ....
  }
  free(line);
  return true;
}

هنا ، في حالة مقاطعة القراءة من الملف ، يحدث تسرب للذاكرة ، حيث لا يتم تحرير الذاكرة قبل العودة من الوظيفة.

جزء N2

V595 تم استخدام مؤشر "gSphereActor" قبل التحقق من صلاحيته باستخدام nullptr. تحقق من الخطوط: 228 ، 230. SnippetContactReportCCD.cpp 228

void initScene()
{
  ....
  gSphereActor = gPhysics->createRigidDynamic(spherePose);
  gSphereActor->setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD, true);

  if (!gSphereActor)
    return;
  ....
}

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

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

صورة 15

صورة 13

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

الجزء N3

V517 تم الكشف عن استخدام نمط 'if (A) {...} else if (A) {...}'. هناك احتمال لوجود خطأ منطقي. خطوط التحقق: 266 ، 268. AABox.cpp 266

bool AABox::initRT(int depthSamples, int coverageSamples)
{
  ....
  int query;
  ....
  if (....)  
  {
    ....
    if ( query < coverageSamples)
      ret = false;
    else if ( query > coverageSamples) 
      coverageSamples = query;
    ....
    if ( query < depthSamples)
      ret = false;
    else if ( query > depthSamples)
      depthSamples = query;
  }
  else {
    ....
    if ( query < depthSamples)
      ret = false;
    else if ( query < depthSamples) // <=
      depthSamples = query;
    ....
  }
  ....
}

هنا يبدو أن خطأ النسخ واللصق قد تسلل إلى الداخل. كشف المحلل عن نفس الحالة في إذا و آخر . من if-else السابق ، يمكنك أن ترى أن ">" in إذا و "<" in else عادة ما يتم تحديدهما . على الرغم من أن النمط يبدو بسيطًا للغاية ، إلا أنني لم أجد مثل هذه الاستجابة بين تحذيرات DeepCode ، على الرغم من أنه يبدو أنه نمط بسيط للغاية لاكتشافه.

خاتمة

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

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


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

All Articles