كيف بدأ محلل PVS-Studio البحث عن المزيد من الأخطاء في مشاريع الوحدة

image1.png


أثناء تطوير محلل PVS-Studio الثابت ، نحاول تطويره في اتجاهات مختلفة. لذا ، يعمل فريقنا على المكونات الإضافية لـ IDE (Visual Studio ، Rider) ، وتحسين التكامل مع CI ، وما إلى ذلك. زيادة كفاءة تحليل المشروع لـ Unity هي أيضًا أحد أهدافنا ذات الأولوية. نحن نعتقد أن التحليل الثابت سيسمح للمبرمجين الذين يستخدمون محرك اللعبة هذا بتحسين جودة شفرة المصدر الخاصة بهم وتبسيط العمل في أي مشروع. لذلك ، أود زيادة شعبية PVS-Studio بين الشركات التي تعمل من أجل الوحدة. كانت إحدى الخطوات الأولى في تنفيذ هذه الفكرة هي كتابة التعليقات التوضيحية للطرق المحددة في المحرك. يسمح لك هذا بالتحكم في صحة الرمز المرتبط بمكالمات الطرق المشروحة.



المقدمة



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



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



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



كتابة شروح لأساليب الوحدة



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



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



جمع المعلومات



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



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



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



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



جعل "المحلل" المكتوب من الممكن العثور على الفئات ، ومتوسط ​​تكرار استخدامها في المشاريع التي تم العثور عليها هو الأعلى:



  • UnityEngine.Vector3
  • UnityEngine.Mathf
  • UnityEngine.Dugug
  • UnityEngine.GameObject
  • UnityEngine.Material
  • UnityEditor.EditorGUILayout
  • UnityEngine.Component
  • UnityEngine.Object
  • UnityEngine.GUILayout
  • UnityEngine.Quaternion
  • إلخ.


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



حاشية. ملاحظة



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



خلال هذه الاختبارات ، تم العثور على ميزات مثيرة للاهتمام لعمل بعض الأساليب. على سبيل المثال ، تشغيل التعليمات البرمجية



MeshRenderer renderer = cube.GetComponent<MeshRenderer>();
Material m = renderer.material;
List<int> outNames = null;
m.GetTexturePropertyNameIDs(outNames);


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



MeshRenderer renderer = cube.GetComponent<MeshRenderer>();
Material m = renderer.material;
string keyWord = null;
bool isEnabled = m.IsKeywordEnabled(keyWord);


هذه القضايا ذات صلة بـ Unity Editor 2019.3.10f1.



جمع النتائج



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



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



يتم استخدام النهج الموصوف أيضًا لمعرفة التغييرات التي تظهر في السجلات عند إضافة تشخيص جديد أو تغيير تشخيص موجود.



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



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



على سبيل المثال ، تم العثور على مكالمة غريبة إلى حد ما إلى GetComponent :



void OnEnable()
{
  GameObject uiManager = GameObject.Find("UIRoot");

  if (uiManager)
  {
    uiManager.GetComponent<UIManager>();
  }
}


تحذير المحلل : V3010 مطلوب قيمة الإرجاع للدالة 'GetComponent' لاستخدامها. - إضافية في UIEditorWindow.cs الحالي 22



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



وإليك مثال آخر على عمليات محلل إضافية:



public void ChangeLocalID(int newID)
{
  if (this.LocalPlayer == null)                          // <=
  {
    this.DebugReturn(
      DebugLevel.WARNING, 
      string.Format(
        ...., 
        this.LocalPlayer, 
        this.CurrentRoom.Players == null,                // <=
        newID  
      )
    );
  }

  if (this.CurrentRoom == null)                          // <=
  {
    this.LocalPlayer.ChangeLocalID(newID);               // <=
    this.LocalPlayer.RoomReference = null;
  }
  else
  {
    // remove old actorId from actor list
    this.CurrentRoom.RemovePlayer(this.LocalPlayer);

    // change to new actor/player ID
    this.LocalPlayer.ChangeLocalID(newID);

    // update the room's list with the new reference
    this.CurrentRoom.StorePlayer(this.LocalPlayer);
  }
}


تحذيرات محلل :



  • V3095 تم استخدام عنصر "this.CurrentRoom" قبل التحقق منه بدون قيمة. تحقق من الخطوط: 1709 ، 1712. - الإضافات في الحمل الحاليBalancingClient.cs 1709
  • V3125 تم استخدام عنصر 'this.LocalPlayer' بعد التحقق منه في مقابل null. التحقق من الخطوط: 1715 ، 1707. - الإضافات في الحمل الحاليBalancingClient.cs 1715


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



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



والحقيقة هي أنه يتم إجراء العديد من المكالمات في طريقة DebugReturn ، والتي قد تؤثر نظريًا على قيمة خاصية CurrentRoom :



public virtual void DebugReturn(DebugLevel level, string message)
{
  #if !SUPPORTED_UNITY
  Debug.WriteLine(message);
  #else
  if (level == DebugLevel.ERROR)
  {
    Debug.LogError(message);
  }
  else if (level == DebugLevel.WARNING)
  {
    Debug.LogWarning(message);
  }
  else if (level == DebugLevel.INFO)
  {
    Debug.Log(message);
  }
  else if (level == DebugLevel.ALL)
  {
    Debug.Log(message);
  }
  #endif
}


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



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



خاتمة



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



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





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



All Articles