تعد مكتبات .NET Core واحدة من أكثر مشاريع C # شيوعًا على GitHub. إنها بالكاد مفاجأة ، لأنها معروفة ومستخدمة على نطاق واسع. بسبب هذا ، أصبحت محاولة الكشف عن الزوايا المظلمة للكود المصدري أكثر جاذبية. لذلك هذا هو ما سنحاول القيام به بمساعدة محلل ثابت PVS-Studio. ما رأيك - هل سنجد شيئًا مثيرًا للاهتمام في النهاية؟
لقد تم طريقي نحو هذه المقالة لأكثر من عام ونصف. في مرحلة ما ، كانت لدي فكرة في رأسي مفادها أن مكتبات .NET Core هي فكرة سريعة ، وأن فحصها يعد بوعد كبير. كنت أتحقق من المشروع عدة مرات ، واصل المحلل العثور على شظايا رمز أكثر وأكثر إثارة للاهتمام ، لكنه لم يذهب أبعد من مجرد تمرير قائمة التحذيرات. وهنا - حدث أخيرًا! تم التحقق من المشروع ، والمقالة أمامك مباشرة.
تفاصيل حول المشروع والتحقق
إذا كنت تسعى جاهدة للغوص في التحقيق في التعليمات البرمجية - يمكنك حذف هذا القسم. ومع ذلك ، أود منك كثيرًا أن تقرأه ، حيث أنني أخبر المزيد عن المشروع والمحلل ، وكذلك عن إجراء التحليل وإعادة إنتاج الأخطاء.
مشروع تحت الشيك
ربما ، كان من الممكن تخطي إخبار CoreFX (مكتبات .NET Core) ، ولكن في حالة عدم سماعك بذلك ، يرد الوصف أدناه. هو نفسه كما في
صفحة المشروع على جيثب ، حيث يمكنك أيضًا تنزيل الكود المصدري.
الوصف:
يحتوي هذا الريبو على تطبيق المكتبة (يسمى "CoreFX") لـ .NET Core. ويشمل System.Collections و System.IO و System.Xml والعديد من المكونات الأخرى. يحتوي repo .NET Core Runtime المطابق (يسمى "CoreCLR") على تطبيق وقت التشغيل لـ .NET Core. ويشمل RyuJIT و .NET GC والعديد من المكونات الأخرى. يعيش رمز مكتبة خاصة بوقت التشغيل (System.Private.CoreLib) في replo CoreCLR. يجب أن يتم بنائه وإصداره جنباً إلى جنب مع وقت التشغيل. ما تبقى من CoreFX غير مناسب لتطبيق وقت التشغيل ويمكن تشغيله على أي وقت تشغيل .NET متوافق (مثل CoreRT) .
استخدام محلل وطريقة التحليل
راجعت الكود باستخدام
محلل ثابت PVS-Studio . بشكل عام ، يمكن لـ PVS-Studio تحليل ليس فقط رمز C # ، ولكن أيضًا C ، C ++ ، Java. يعمل تحليل رمز C # حتى الآن فقط في نظام Windows ، في حين يمكن تحليل كود C و C ++ و Java في أنظمة Windows و Linux و macOS.
عادةً للتحقق من مشاريع C # ، أستخدم مكون PVS-Studio الإضافي لـ Visual Studio (يدعم إصدارات 2010-2019) ، لأنه على الأرجح سيناريو التحليل الأكثر سهولة وملاءمة في هذه الحالة: الحل المفتوح ، تشغيل التحليل ، التعامل مع قائمة التحذيرات. ومع ذلك ، فقد جاء الأمر أكثر تعقيدًا مع CoreFX.
الجزء الصعب هو أن المشروع لا يحتوي على ملف .sln واحد ، وبالتالي فتحه في Visual Studio وإجراء تحليل كامل ، باستخدام البرنامج المساعد PVS-Studio ، غير ممكن. ربما يكون هذا أمرًا جيدًا - لا أعرف حقًا كيف سيتعامل Visual Studio مع حل بهذا الحجم.
ومع ذلك ، لم تكن هناك مشاكل في التحليل ، حيث أن توزيع PVS-Studio يتضمن إصدار سطر الأوامر الخاص بالمحلل لمشاريع MSBuild (و .sln). كل ما كان علي فعله هو كتابة نص برمجي صغير ، يعمل على "PVS-Studio_Cmd.exe" لكل .sln في دليل CoreFX وحفظ النتائج في دليل منفصل (يتم تحديده بواسطة علامة سطر الأوامر للمحلل) .
المعزوفة! كنتيجة لذلك ، أمتلك صندوق Pandora مع مجموعة من التقارير لتخزين بعض الأشياء المثيرة للاهتمام. إذا رغبت في ذلك ، يمكن دمج هذه السجلات مع الأداة المساعدة PlogConverter ، والتي تأتي كجزء من التوزيع. بالنسبة لي ، كان أكثر ملاءمة للعمل مع سجلات منفصلة ، لذلك لم دمجها.
عند وصف بعض الأخطاء ، أشير إلى الوثائق من حزم docs.microsoft.com و NuGet ، وهي متاحة للتنزيل من nuget.org. أفترض أن الكود الموضح في الوثائق / الحزم قد يكون مختلفًا قليلاً عن الكود الذي تم تحليله. ومع ذلك ، سيكون من الغريب جدًا ألا تشرح الوثائق ، على سبيل المثال ، الاستثناءات التي تم إنشاؤها عند وجود مجموعة بيانات إدخال معينة ، ولكن إصدار الحزمة الجديد سيشملها. يجب أن تعترف أنه سيكون مفاجأة مشكوك فيها. يوضح إعادة إنشاء الأخطاء في الحزم من NuGet باستخدام نفس بيانات الإدخال التي تم استخدامها لتصحيح المكتبات أن هذه المشكلة ليست جديدة. الأهم من ذلك ، يمكنك "لمس" دون بناء المشروع من مصادر.
وبالتالي ، إذا سمحت بإمكانية حدوث بعض عدم التزامن النظري للرمز ، أجد أنه من المقبول الإشارة إلى وصف الطرق ذات الصلة على docs.microsoft.com ولإعادة إنتاج المشكلات باستخدام حزم من nuget.org.
بالإضافة إلى ذلك ، أود أن أشير إلى أن الوصف من خلال الروابط المعطاة ، المعلومات (التعليقات) في الحزم (في الإصدارات الأخرى) كان يمكن تغييرها أثناء كتابة المقال.
مشاريع أخرى تم فحصها
بالمناسبة ، هذه المقالة ليست فريدة من نوعها. نكتب مقالات أخرى عن الشيكات المشروع. بواسطة هذا الرابط ، يمكنك العثور على
قائمة المشاريع المحددة . علاوة على ذلك ، ستجد على موقعنا ليس فقط مقالات عن اختبارات المشروع ، ولكن أيضًا مقالات تقنية متنوعة حول C ، C ++ ، C # ، Java ، وكذلك بعض الملاحظات المثيرة للاهتمام. يمكنك أن تجد كل هذا في
المدونة .
قام زميلي بالفعل بمراجعة مكتبات .NET Core في عام 2015. يمكن العثور على نتائج التحليل السابق في المقالة ذات الصلة: "
تحليل عيد الميلاد لـ .NET Core Libraries (CoreFX) ."
اكتشاف الأخطاء ، شظايا مثيرة للاهتمام ومثيرة للاهتمام
كما هو الحال دائمًا ، لمزيد من الاهتمام ، أقترح أن تبحث أولاً عن الأخطاء في الأجزاء المعطاة بنفسك ، وعندها فقط تقرأ رسالة المحلل ووصف المشكلة.
للراحة ، قمت بفصل الأجزاء بوضوح عن بعضها البعض باستخدام ملصقات
Issue N - وبهذه الطريقة يكون من السهل معرفة أين ينتهي وصف خطأ واحد ، يليه الخطأ التالي. بالإضافة إلى ذلك ، من السهل الإشارة إلى أجزاء محددة.
العدد 1abstract public class Principal : IDisposable { .... public void Save(PrincipalContext context) { .... if ( context.ContextType == ContextType.Machine || _ctx.ContextType == ContextType.Machine) { throw new InvalidOperationException( SR.SaveToNotSupportedAgainstMachineStore); } if (context == null) { Debug.Assert(this.unpersisted == true); throw new InvalidOperationException(SR.NullArguments); } .... } .... }
تحذير PVS-Studio: V3095 تم استخدام كائن "السياق" قبل التحقق من صحته. خطوط الفحص: 340 ، 346. Principal.cs 340
يوضح المطورون بوضوح أن القيمة
الخالية لمعلمة
السياق غير صالحة ، فهم يريدون التأكيد على ذلك باستخدام نوع
InvalidOperationException . ومع ذلك ، فقط أعلاه في الشرط السابق يمكننا أن نرى إرجاء غير مشروط
للسياق المرجعي -
context.ContextType . نتيجة لذلك ، إذا كانت قيمة
السياق خالية ، فسيتم إنشاء استثناء نوع
NullReferenceException بدلاً من
InvalidOperationExcetion المتوقع
.دعنا نحاول إعادة إنتاج المشكلة. سنقوم بإضافة مرجع إلى مكتبة
System.DirectoryServices.AccountManagement إلى المشروع وتنفيذ التعليمات البرمجية التالية:
GroupPrincipal groupPrincipal = new GroupPrincipal(new PrincipalContext(ContextType.Machine)); groupPrincipal.Save(null);
يرث
GroupPrincipal من فئة الملخص
الرئيسية التي تنفذ طريقة
الحفظ التي نهتم بها. لذلك نحن ننفذ الكود ونرى ما هو المطلوب لإثباته.
من أجل الاهتمام ، يمكنك محاولة تنزيل الحزمة المناسبة من NuGet وتكرار المشكلة بالطريقة نفسها. قمت بتثبيت الحزمة 4.5.0 وحصلت على النتيجة المتوقعة.
العدد 2 private SearchResultCollection FindAll(bool findMoreThanOne) { searchResult = null; DirectoryEntry clonedRoot = null; if (_assertDefaultNamingContext == null) { clonedRoot = SearchRoot.CloneBrowsable(); } else { clonedRoot = SearchRoot.CloneBrowsable(); } .... }
تحذير PVS-Studio: V3004 عبارة "then" مكافئة لبيان "else". DirectorySearcher.cs 629
بغض النظر عما إذا كانت
_assertDefaultNamingContext == شرطية صحيحة أو خاطئة ، سيتم اتخاذ نفس الإجراءات ، كما في
ذلك الحين ،
وإلا فإن فروع
if if لها نفس الهيئات. يجب أن يكون هناك إجراء آخر في أحد الفروع ، أو يمكنك حذف عبارة
if لا تخلط بين المطورين والمحلل.
العدد 3 public class DirectoryEntry : Component { .... public void RefreshCache(string[] propertyNames) { .... object[] names = new object[propertyNames.Length]; for (int i = 0; i < propertyNames.Length; i++) names[i] = propertyNames[i]; .... if (_propertyCollection != null && propertyNames != null) .... .... } .... }
تحذير PVS-Studio: V3095 تم استخدام كائن "propertyNames" قبل أن يتم التحقق منه ضد قيمة خالية. خطوط التحقق: 990 ، 1004. DirectoryEntry.cs 990
مرة أخرى ، نرى ترتيبًا غريبًا للأفعال. في الطريقة ، هناك
خاصية propertyNames! check
خالية = Null ، أي أن المطورين يغطون قواعدهم من
null يدخل في الطريقة. ولكن أعلاه يمكنك رؤية بعض عمليات الوصول من خلال هذا المرجع المحتمل أنه لاغٍ -
propertyNames.Length و
propertyNames [i] . النتيجة يمكن التنبؤ بها تمامًا - حدوث استثناء لنوع
NullReferenceExcepption في حالة إذا تم تمرير مرجع فارغ إلى الأسلوب.
يالها من مصادفة!
RefreshCache هي طريقة عامة في الفئة العامة. ماذا عن محاولة إعادة إنتاج المشكلة؟ للقيام بذلك ، سنقوم بتضمين نظام المكتبة المطلوب ،
خدمات الدليل للمشروع وسنكتب رمزًا مثل هذا:
DirectoryEntry de = new DirectoryEntry(); de.RefreshCache(null);
بعد تنفيذ التعليمات البرمجية ، يمكننا أن نرى ما توقعناه.
فقط للركلات ، يمكنك تجربة إعادة إنتاج المشكلة على نسخة إصدار حزمة NuGet. بعد ذلك ، نضيف مرجعًا إلى حزمة
System.DirectoryServices (استخدمت الإصدار 4.5.0) للمشروع وننفذ الكود المألوف بالفعل. والنتيجة هي أدناه.
العدد 4سننتقل الآن من الجهة المقابلة - سنحاول أولاً كتابة التعليمات البرمجية ، التي تستخدم مثيلًا صفيًا ، ثم سننظر إلى الداخل. دعنا نشير إلى بنية
System.Drawing.CharacterRange من مكتبة
System.Drawing.Common وحزمة NuGet التي تحمل الاسم نفسه.
سنستخدم هذه الشفرة:
CharacterRange range = new CharacterRange(); bool eq = range.Equals(null); Console.WriteLine(eq);
في حالة حدوث ذلك ، من أجل تشغيل ذاكرتنا فقط ، سنقوم بمعالجة
docs.microsoft.com لتذكر القيمة المرتجعة المتوقعة من التعبير
obj.Equals (خالية) :
يجب أن تكون العبارات التالية صحيحة بالنسبة لجميع عمليات تنفيذ الأسلوب Equals (Object) . في القائمة ، تمثل x و y و z مراجع الكائنات التي ليست خالية.....x.Equals (خالية) بإرجاع false.هل تعتقد أنه سيتم عرض النص "خطأ" في وحدة التحكم؟ بالطبع لا. سيكون من السهل جدا. :) لذلك ، ننفذ الكود وننظر إلى النتيجة.
كان الإخراج من التعليمات البرمجية أعلاه باستخدام حزمة نظام NuGet.Drawing.Common من الإصدار 4.5.1. الخطوة التالية هي تشغيل نفس الرمز مع إصدار مكتبة تصحيح الأخطاء. هذا ما نراه:
الآن دعونا نلقي نظرة على الكود المصدري ، على وجه الخصوص ، تنفيذ طريقة
Equals في هيكل
CharacterRange وتحذير محلل:
public override bool Equals(object obj) { if (obj.GetType() != typeof(CharacterRange)) return false; CharacterRange cr = (CharacterRange)obj; return ((_first == cr.First) && (_length == cr.Length)); }
تحذير PVS-Studio: V3115 لا ينبغي أن يؤدي تمرير "لاغٍ" إلى "يساوي" إلى "NullReferenceException". CharacterRange.cs 56
يمكننا أن نلاحظ ، ما كان يجب إثباته - يتم التعامل مع المعلمة
obj بطريقة غير صحيحة. لهذا السبب ، يحدث استثناء
NullReferenceException في التعبير الشرطي عند استدعاء
GetType أسلوب المثيل
.العدد 5أثناء استكشافنا لهذه المكتبة ، لننظر في جزء آخر مثير للاهتمام - طريقة
حفظ الرمز . قبل البحث ، دعونا نلقي نظرة على وصف الطريقة.
لا يوجد وصف للطريقة:
دعونا نتناول docs.microsoft.com - "
Icon.Save (Stream) Method ". ومع ذلك ، لا توجد أيضًا قيود على المدخلات أو المعلومات حول الاستثناءات التي تم إنشاؤها.
الآن دعنا ننتقل إلى فحص الكود.
public sealed partial class Icon : MarshalByRefObject, ICloneable, IDisposable, ISerializable { .... public void Save(Stream outputStream) { if (_iconData != null) { outputStream.Write(_iconData, 0, _iconData.Length); } else { .... if (outputStream == null) throw new ArgumentNullException("dataStream"); .... } } .... }
تحذير PVS-Studio: V3095 تم استخدام كائن 'outputStream' قبل أن يتم التحقق منه ضد قيمة خالية. خطوط التحقق: 654 ، 672. Icon.Windows.cs 654
مرة أخرى ، إنها القصة التي نعرفها بالفعل - احتمال مرجعي للإشارة الفارغة ، حيث يتم إلغاء تحديد معلمة الطريقة دون التحقق من عدم وجود قيمة. مرة أخرى ، تتزامن الظروف الناجحة - كل من الطبقة والطريقة علنية ، حتى نتمكن من محاولة إعادة إنتاج المشكلة.
مهمتنا بسيطة - إحضار تنفيذ التعليمات البرمجية إلى تعبير
outputStream.Write (_iconData، 0، _iconData.Length)؛ وفي الوقت نفسه حفظ قيمة متغير
outputStream -
فارغة . استيفاء الشرط
_iconData! = خالية كافية لهذا.
دعونا نلقي نظرة على أبسط المنشئ العام:
public Icon(string fileName) : this(fileName, 0, 0) { }
إنه يفوض العمل إلى مُنشئ آخر.
public Icon(string fileName, int width, int height) : this() { using (FileStream f = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { Debug.Assert(f != null, "File.OpenRead returned null instead of throwing an exception"); _iconData = new byte[(int)f.Length]; f.Read(_iconData, 0, _iconData.Length); } Initialize(width, height); }
هذا كل شيء ، هذا ما نحتاجه. بعد استدعاء هذا المُنشئ ، إذا قرأنا البيانات من الملف بنجاح ولم تحدث أعطال في طريقة
التهيئة ،
فسيحتوي الحقل
_iconData على مرجع إلى كائن ، وهذا ما نحتاج إليه.
اتضح أنه يتعين علينا إنشاء مثيل لفئة
Icon وتحديد ملف رمز حقيقي لإعادة إنتاج المشكلة. بعد ذلك نحتاج إلى استدعاء الأسلوب
حفظ ، بعد اجتياز القيمة
الخالية كوسيطة ، هذا ما نقوم به. قد يبدو الرمز هكذا ، على سبيل المثال:
Icon icon = new Icon(@"D:\document.ico"); icon.Save(null);
نتيجة التنفيذ متوقعة.
العدد 6نواصل الاستعراض والمضي قدما. محاولة للعثور على 3 الاختلافات بين الإجراءات ، نفذت في
حالة CimType.UInt32 والحالة الأخرى.
private static string ConvertToNumericValueAndAddToArray(....) { string retFunctionName = string.Empty; enumType = string.Empty; switch(cimType) { case CimType.UInt8: case CimType.SInt8: case CimType.SInt16: case CimType.UInt16: case CimType.SInt32: arrayToAdd.Add(System.Convert.ToInt32( numericValue, (IFormatProvider)CultureInfo.InvariantCulture .GetFormat(typeof(int)))); retFunctionName = "ToInt32"; enumType = "System.Int32"; break; case CimType.UInt32: arrayToAdd.Add(System.Convert.ToInt32( numericValue, (IFormatProvider)CultureInfo.InvariantCulture .GetFormat(typeof(int)))); retFunctionName = "ToInt32"; enumType = "System.Int32"; break; } return retFunctionName; }
بالطبع ، لا توجد اختلافات ، حيث يحذرنا المحلل من ذلك.
تحذير PVS-Studio: V3139 يقوم اثنان أو أكثر من فروع الحالات بتنفيذ نفس الإجراءات. WMIGenerator.cs 5220
شخصيا ، هذا النمط الكود ليس واضحا جدا. أعتقد أنه في حالة عدم وجود خطأ ، فلن يتم تطبيق نفس المنطق على حالات مختلفة.
العدد 7مكتبة
Microsoft .
private static IList<KeyValuePair<string, object>> QueryDynamicObject(object obj) { .... List<string> names = new List<string>(mo.GetDynamicMemberNames()); names.Sort(); if (names != null) { .... } .... }
تحذير PVS-Studio: V3022 Expression 'names! = Null' يكون دائمًا صحيحًا. DynamicDebuggerProxy.cs 426
من المحتمل أن أتجاهل هذا التحذير جنبًا إلى جنب مع العديد من التحذيرات المماثلة التي صدرت عن التشخيص
V3022 و
V3063 . كان هناك العديد من (الكثير من) الشيكات الغريبة ، ولكن هذا واحد دخلت بطريقة ما في روحي. ربما يكمن السبب في ما يحدث قبل مقارنة متغير
الأسماء المحلية بالقيمة
الخالية. لا يقتصر الأمر على تخزين المرجع في متغير
الأسماء لكائن تم إنشاؤه حديثًا ، بل تسمى أيضًا طريقة
فرز مثيل. بالتأكيد ، هذا ليس خطأ ولكن ، بالنسبة لي ، يستحق الاهتمام به.
العدد 8جزء آخر مثير للاهتمام من الكود:
private static void InsertChildNoGrow(Symbol child) { .... while (sym?.nextSameName != null) { sym = sym.nextSameName; } Debug.Assert(sym != null && sym.nextSameName == null); sym.nextSameName = child; .... }
تحذير PVS-Studio: V3042 NullReferenceException ممكن. "؟" و "." تستخدم عوامل التشغيل للوصول إلى أعضاء الكائن "sym" SymbolStore.cs 56
انظروا الى ما هو الشيء. تنتهي الحلقة عند الامتثال لشرطين على الأقل:
- sym == فارغة ؛
- sym.nextSameName == فارغة .
لا توجد مشاكل مع الشرط الثاني ، والذي لا يمكن قوله عن الشرط الأول. نظرًا لأنه يتم الوصول إلى حقل مثيل
الأسماء أدناه دون قيد أو شرط وإذا كان
sym -
null ، فسيحدث استثناء من نوع
NullReferenceException .
هل انت عمياء هناك مكالمة
Debug.Assert ، حيث يتم التحقق من ذلك
sym! = Null "- قد يجادل شخص ما. على العكس تماما ، هذه هي النقطة! عند العمل في إصدار الإصدار ، لن يكون
Debug.Assert مفيدًا ومع الشرط أعلاه ، كل ما سنحصل عليه هو
NullReferenceException . علاوة على ذلك ، لقد رأيت بالفعل خطأً مماثلاً في مشروع آخر من Microsoft -
Roslyn ، حيث حدث موقف مماثل مع
Debug.Assert . اسمحوا لي أن أنتقل جانبا للحظة لروزلين.
يمكن أن تتكرر المشكلة عند استخدام مكتبات
Microsoft.CodeAnalysis أو في Visual Studio عند استخدام Syntax Visualizer. في Visual Studio 16.1.6 + Syntax Visualizer 1.0 ، لا يزال من الممكن إعادة إنتاج هذه المشكلة.
هذا الرمز يكفي لذلك:
class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } }
علاوة على ذلك ، في Syntax Visualizer ، نحتاج إلى العثور على عقدة شجرة بناء الجملة لنوع
ConstantPatternSyntax ، المطابقة
للقيمة الخالية في الكود وطلب
TypeSymbol لذلك.
بعد ذلك ، سيتم إعادة تشغيل Visual Studio. إذا ذهبنا إلى "عارض الأحداث" ، فسنجد بعض المعلومات حول المشكلات في المكتبات:
Application: devenv.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.Resources.MissingManifestResourceException at System.Resources.ManifestBasedResourceGroveler .HandleResourceStreamMissing(System.String) at System.Resources.ManifestBasedResourceGroveler.GrovelForResourceSet( System.Globalization.CultureInfo, System.Collections.Generic.Dictionary'2 <System.String,System.Resources.ResourceSet>, Boolean, Boolean, System.Threading.StackCrawlMark ByRef) at System.Resources.ResourceManager.InternalGetResourceSet( System.Globalization.CultureInfo, Boolean, Boolean, System.Threading.StackCrawlMark ByRef) at System.Resources.ResourceManager.InternalGetResourceSet( System.Globalization.CultureInfo, Boolean, Boolean) at System.Resources.ResourceManager.GetString(System.String, System.Globalization.CultureInfo) at Roslyn.SyntaxVisualizer.DgmlHelper.My. Resources.Resources.get_SyntaxNodeLabel() ....
بالنسبة إلى المشكلة مع devenv.exe:
Faulting application name: devenv.exe, version: 16.1.29102.190, time stamp: 0x5d1c133b Faulting module name: KERNELBASE.dll, version: 10.0.18362.145, time stamp: 0xf5733ace Exception code: 0xe0434352 Fault offset: 0x001133d2 ....
باستخدام إصدارات تصحيح أخطاء مكتبات Roslyn ، يمكنك العثور على المكان الذي يوجد فيه استثناء:
private Conversion ClassifyImplicitBuiltInConversionSlow( TypeSymbol source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); if ( source.SpecialType == SpecialType.System_Void || destination.SpecialType == SpecialType.System_Void) { return Conversion.NoConversion; } .... }
هنا ، كما هو الحال في التعليمات البرمجية من مكتبات .NET Core المذكورة أعلاه ، هناك فحص
Debug.Assert والذي لن يساعد عند استخدام إصدارات إصدار المكتبات.
العدد 9لدينا القليل من الأوفست هنا ، لذلك دعونا نعود إلى مكتبات .NET Core. تحتوي حزمة
System.IO.IsolatedStorage على الكود المثير للاهتمام التالي.
private bool ContainsUnknownFiles(string directory) { .... return (files.Length > 2 || ( (!IsIdFile(files[0]) && !IsInfoFile(files[0]))) || (files.Length == 2 && !IsIdFile(files[1]) && !IsInfoFile(files[1])) ); }
تحذير PVS-Studio: V3088 تم
إحاطة التعبير بأقواس مرتين: ((تعبير)). زوج واحد من الأقواس غير ضروري أو وجود خطأ مطبعي. IsolatedStorageFile.cs 839
إن قول أن تنسيق الكود أمر مربك هو طريقة أخرى لقول أي شيء. بعد إلقاء نظرة قصيرة على هذا الرمز ، أود أن أقول أن المعامل الأيسر الأول || عامل التشغيل الذي
واجهته كان
files.Length> 2 ، والحق هو واحد بين قوسين. على الأقل يتم تنسيق الرمز مثل هذا. بعد النظر بعناية أكبر ، يمكنك أن تفهم أن الأمر ليس كذلك. في الواقع ، المعامل الصحيح -
((! IsIdFile (ملفات [0]) &&! IsInfoFile (ملفات [0]))) . أعتقد أن هذا الرمز مربك للغاية.
العدد 10قدم PVS-Studio 7.03 قاعدة التشخيص
V3138 ، والتي تبحث عن الأخطاء في السلسلة المحرف. بتعبير أدق ، في السلسلة التي كان من المرجح أن يتم استيفائها ، ولكن بسبب رمز
$ المفقود ، فإنهم لا يفعلون ذلك
. في مكتبات
System.Net ، وجدت العديد من الأحداث المثيرة للاهتمام لهذه القاعدة التشخيصية.
internal static void CacheCredential(SafeFreeCredentials newHandle) { try { .... } catch (Exception e) { if (!ExceptionCheck.IsFatal(e)) { NetEventSource.Fail(null, "Attempted to throw: {e}"); } } }
تحذير PVS-Studio: تحتوي السلسلة الحرفية
V3138 على تعبير محتمل محتمل. النظر في التفتيش: ه. SSPIHandleCache.cs 42
من المحتمل جدًا أن تكون الوسيطة الثانية لطريقة
Fail عبارة عن سلسلة محرف ، حيث سيتم استبدال تمثيل السلسلة الخاص بالاستثناء
e . ومع ذلك ، بسبب وجود رمز
$ مفقود ، لم يتم استبدال أي تمثيل سلسلة.
العدد 11هنا حالة أخرى مماثلة.
public static async Task<string> GetDigestTokenForCredential(....) { .... if (NetEventSource.IsEnabled) NetEventSource.Error(digestResponse, "Algorithm not supported: {algorithm}"); .... }
تحذير PVS-Studio: تحتوي السلسلة الحرفية
V3138 على تعبير محتمل محتمل. النظر في التفتيش: الخوارزمية. AuthenticationHelper.Digest.cs 58
الموقف مشابه للحالة الموضحة أعلاه ، ومرة أخرى يتم تفويت الرمز
$ ، مما يؤدي إلى سلسلة غير صحيحة ، والدخول في أسلوب
Error .العدد 12حزمة
System.Net . الطريقة صغيرة ، وسأذكرها بأكملها من أجل جعل البحث عن الأخطاء أكثر إثارة للاهتمام.
internal void SetContent(Stream stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (_streamSet) { _stream.Close(); _stream = null; _streamSet = false; } _stream = stream; _streamSet = true; _streamUsedOnce = false; TransferEncoding = TransferEncoding.Base64; }
تحذير PVS-Studio: V3008 يتم تعيين قيم "_streamSet" مرتين على التوالي. ربما هذا خطأ. خطوط التحقق: 123 ، 119. MimePart.cs 123
تعيين قيمة مزدوجة لمتغير
_streamSet يبدو غريبا (أولاً - تحت الشرط ، ثم - خارج). نفس القصة مع إعادة تعيين متغير
الدفق . نتيجة لذلك ،
سيظل _stream يحتوي على
دفق القيمة ، وسيكون
_streamSet صحيحًا.العدد 13جزء رمز مثير للاهتمام من مكتبة
System.Linq.Expressions التي تقوم بتشغيل تحذيرين للمحلل في آن واحد. في هذه الحالة ، يشبه الميزة أكثر من كونه خطأ. ومع ذلك ، فإن الطريقة غير عادية ...
تحذيرات PVS-Studio:- V3010 يجب استخدام قيمة الإرجاع للدالة "GetType". التعليمات. 36
- V3080 dereference ممكن. النظر في تفتيش "س". التعليمات. 36
ربما لا يوجد شيء للتعليق هنا.
العدد 14دعنا نفكر في قضية أخرى ، والتي سنتعامل معها "من الخارج". أولاً ، سنكتب الرمز ، ونكتشف المشاكل ، وبعد ذلك سننظر إلى الداخل. سنأخذ مكتبة
System.Configuration.ConfigurationManager وحزمة NuGet التي تحمل الاسم نفسه للمراجعة. لقد استخدمت حزمة الإصدار 4.5.0. سنتعامل مع فئة
System.Configuration.CommaDelimitedStringCollection .
دعونا نفعل شيئا غير متطور. على سبيل المثال ، سنقوم بإنشاء كائن ، واستخراج تمثيل السلسلة والحصول على طول هذه السلسلة ، ثم طباعتها. الكود ذو الصلة:
CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); Console.WriteLine(collection.ToString().Length);
فقط في هذه الحالة ، سوف نتحقق من وصف طريقة
ToString :
لا شيء خاص - يتم إرجاع تمثيل سلسلة كائن. فقط في حالة ، سوف تحقق من docs.microsoft.com - "
CommaDelimitedStringCollection.ToString Method ". يبدو أنه لا يوجد شيء خاص هنا.
حسنًا ، دعنا ننفذ الكود ، aaand ...
هم ، مفاجأة. حسنًا ، دعنا نحاول إضافة عنصر إلى المجموعة ثم الحصول على تمثيل السلسلة الخاص به. بعد ذلك ، سنضيف "بالصدفة المطلقة" سلسلة فارغة :). سيتم تغيير الرمز وتبدو كما يلي:
CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); collection.Add(String.Empty); Console.WriteLine(collection.ToString().Length);
تنفيذ وانظر ...
ماذا ، مرة أخرى؟ حسنًا ، دعنا أخيرًا نعالج تطبيق الأسلوب
ToString من فئة
CommaDelimitedStringCollection . الكود أدناه:
public override string ToString() { if (Count <= 0) return null; StringBuilder sb = new StringBuilder(); foreach (string str in this) { ThrowIfContainsDelimiter(str);
تحذيرات PVS-Studio:- V3108 لا يوصى بإرجاع "خالية" من طريقة "ToSting ()". StringAttributeCollection.cs 57
- V3108 لا يوصى بإرجاع "خالية" من طريقة "ToSting ()". StringAttributeCollection.cs 71
هنا يمكننا أن نرى شظايا ، حيث يمكن تنفيذ
ToString الحالي إرجاع
فارغة. في هذه المرحلة ، سنتذكر توصية Microsoft بشأن تنفيذ طريقة
ToString . لذلك دعونا الرجوع إلى docs.microsoft.com - "
Object.ToString Method ":
ملاحظات إلى Inheritors .... يجب أن تتبع Overrides للأسلوب ToString () هذه الإرشادات:- ....
- يجب ألا يؤدي تجاوز ToString () إلى إرجاع سلسلة فارغة أو فارغة .
- ....
هذا هو ما يحذر PVS-Studio. تحصل شظايا الكود المعطاة أعلاه على أننا نكتب لإعادة إنتاج المشكلة على نقاط خروج مختلفة - نقطتي العودة الأولى والثانية على التوالي. دعونا حفر أعمق قليلا.
الحالة الأولى.
Count هي خاصية لفئة
StringCollection الأساسية. نظرًا لأنه لم تتم إضافة أي عناصر ،
العدد == 0 ، يكون
عدد الشرط
<= 0 صحيحًا ، يتم إرجاع القيمة
الخالية .
في الحالة الثانية ، أضفنا العنصر ، باستخدام أسلوب
CommaDelimitedStringCollection.Add الخاص به.
public new void Add(string value) { ThrowIfReadOnly(); ThrowIfContainsDelimiter(value); _modified = true; base.Add(value.Trim()); }
الاختبارات ناجحة في طريقة
ThrowIf ... ويتم إضافة العنصر في المجموعة الأساسية. وفقًا لذلك ، تصبح قيمة
العدد 1. والآن دعنا نعود إلى طريقة
ToString . قيمة التعبير
عدد <= 0 -
خطأ ، وبالتالي لا تُرجع الطريقة ويستمر تنفيذ التعليمات البرمجية. يتم اجتياز المجموعة الداخلية ، تتم إضافة عنصرين إلى مثيل نوع
StringBuilder - سلسلة فارغة وفاصلة. نتيجة لذلك ، اتضح أن
sb يحتوي فقط على فاصلة ، تساوي قيمة خاصية
الطول على التوالي 1. قيمة التعبير
sb.Length> 0 صحيحة ، يتم تنفيذ الطرح والكتابة بـ
sb.Length ، والآن أصبحت القيمة of
sb.Length هو 0. وهذا يؤدي إلى حقيقة أن القيمة
الخالية يتم إرجاعها مرة أخرى من الطريقة.
العدد 15فجأة ، حصلت على شغف لاستخدام فئة
System.Configuration.ConfigurationProperty . لنأخذ مُنشئًا له أكبر عدد من المعلمات:
public ConfigurationProperty( string name, Type type, object defaultValue, TypeConverter typeConverter, ConfigurationValidatorBase validator, ConfigurationPropertyOptions options, string description);
لنرى وصف المعلمة الأخيرة:
هو نفسه مكتوب في وصف المنشئ في docs.microsoft.com. حسنًا ، دعنا نلقي نظرة على كيفية استخدام هذه المعلمة في جسم المُنشئ:
public ConfigurationProperty(...., string description) { ConstructorInit(name, type, options, validator, typeConverter); SetDefaultValue(defaultValue); }
صدق أو لا تصدق ، لا يتم استخدام المعلمة.
تحذير PVS-Studio: لا يتم استخدام
V3117 " مُنشئ المعلمة" الوصف. ConfigurationProperty.cs 62
ربما ، لا يستخدمه مؤلفو الكود عمداً ، لكن وصف المعلمة ذات الصلة مربك للغاية.
العدد 16إليك جزءًا آخر مشابهًا: حاول العثور على الخطأ بنفسك ، فأنا أعطي رمز المنشئ أدناه.
internal SectionXmlInfo( string configKey, string definitionConfigPath, string targetConfigPath, string subPath, string filename, int lineNumber, object streamVersion, string rawXml, string configSource, string configSourceStreamName, object configSourceStreamVersion, string protectionProviderName, OverrideModeSetting overrideMode, bool skipInChildApps) { ConfigKey = configKey; DefinitionConfigPath = definitionConfigPath; TargetConfigPath = targetConfigPath; SubPath = subPath; Filename = filename; LineNumber = lineNumber; StreamVersion = streamVersion; RawXml = rawXml; ConfigSource = configSource; ConfigSourceStreamName = configSourceStreamName; ProtectionProviderName = protectionProviderName; OverrideModeSetting = overrideMode; SkipInChildApps = skipInChildApps; }
تحذير PVS-Studio: لا يتم استخدام معلمة مُنشئ V3117 "configSourceStreamVersion". SectionXmlInfo.cs 16
هناك خاصية مناسبة ، ولكن بصراحة ، تبدو غريبة بعض الشيء:
internal object ConfigSourceStreamVersion { set { } }
بشكل عام ، يبدو الرمز مشبوهًا. ربما يتم ترك المعلمة / الخاصية للتوافق ، ولكن هذا مجرد تخميني.
العدد 17دعونا نلقي نظرة على الأشياء المثيرة للاهتمام في مكتبة
System.Runtime.WindowsRuntime.UI.Xaml ورمز حزمة الاسم نفسه.
public struct RepeatBehavior : IFormattable { .... public override string ToString() { return InternalToString(null, null); } .... }
تحذير PVS-Studio: V3108 لا يوصى بإرجاع "خالية" من طريقة "ToSting ()". RepeatBehavior.cs 113
قصة مألوفة نعرفها بالفعل - يمكن لطريقة
ToString إرجاع القيمة
الخالية . نتيجة لهذا ، قد يفاجأ مؤلف رمز المتصل ، الذي يفترض أن
RepeatBehavior.ToString دائمًا بإرجاع مرجع غير فارغ ، في وقت ما. مرة أخرى ، يتعارض مع إرشادات Microsoft.
حسنًا ، لكن الطريقة لا توضح أنه يمكن لـ
ToString العودة
خالية - نحن بحاجة إلى التعمق والتعمق في طريقة
InternalToString .
internal string InternalToString(string format, IFormatProvider formatProvider) { switch (_Type) { case RepeatBehaviorType.Forever: return "Forever"; case RepeatBehaviorType.Count: StringBuilder sb = new StringBuilder(); sb.AppendFormat( formatProvider, "{0:" + format + "}x", _Count); return sb.ToString(); case RepeatBehaviorType.Duration: return _Duration.ToString(); default: return null; } }
اكتشف المحلل أنه في حالة تنفيذ الفرع
الافتراضي في
التبديل ، ستُرجع
InternalToString القيمة
الخالية . لذلك ، سيعود
ToString فارغة .
يعد
RepeatBehavior بنية عامة ، و
ToString هو أسلوب عام ، لذلك يمكننا محاولة إعادة إنتاج المشكلة في الممارسة العملية. للقيام بذلك ، سنقوم بإنشاء مثيل
RepeatBehavior ، واستدعاء أسلوب
ToString منه ، وأثناء القيام بذلك ، يجب ألا نفوت أن
_Type يجب ألا يكون مساويًا لـ
RepeatBehaviorType.Forever أو
RepeatBehaviorType.Count أو
RepeatBehaviorType.Duration .
_Type هو حقل خاص ، يمكن تعيينه عبر خاصية عامة:
public struct RepeatBehavior : IFormattable { .... private RepeatBehaviorType _Type; .... public RepeatBehaviorType Type { get { return _Type; } set { _Type = value; } } .... }
جيد جدا دعنا ننتقل ونرى ما هو نوع
RepeatBehaviorType .
public enum RepeatBehaviorType { Count, Duration, Forever }
كما نرى ، فإن
RepeatBehaviorType هو التعداد ، الذي يحتوي على جميع العناصر الثلاثة. إلى جانب هذا ، تتم تغطية جميع هذه العناصر الثلاثة في تعبير
التبديل الذي نحن مهتمون به. هذا ، ومع ذلك ، لا يعني أن الفرع الافتراضي غير قابل للوصول.
لإعادة إنتاج المشكلة ، سنضيف مرجعًا إلى حزمة
System.Runtime.WindowsRuntime.UI.Xaml للمشروع (كنت أستخدم الإصدار 4.3.0) وننفذ الكود التالي.
RepeatBehavior behavior = new RepeatBehavior() { Type = (RepeatBehaviorType)666 }; Console.WriteLine(behavior.ToString() is null);
يتم عرض
True في وحدة التحكم كما هو متوقع ، مما يعني إرجاع
ToString فارغًا ، لأن
_Type لم يكن مساويا لأي من القيم في فروع
الحالة ، وتلقى الفرع
الافتراضي التحكم. هذا ما كنا نحاول القيام به.
أود أيضًا أن أشير إلى أنه لا توجد تعليقات على الطريقة ولا
docs.microsoft.com تحدد أن هذه الطريقة يمكنها إرجاع القيمة
الخالية .
العدد 18بعد ذلك ، سنقوم بالتحقق من عدة تحذيرات من
System.Private.DataContractSerialization .
private static class CharType { public const byte None = 0x00; public const byte FirstName = 0x01; public const byte Name = 0x02; public const byte Whitespace = 0x04; public const byte Text = 0x08; public const byte AttributeText = 0x10; public const byte SpecialWhitespace = 0x20; public const byte Comment = 0x40; } private static byte[] s_charType = new byte[256] { .... CharType.None, CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, CharType.None, CharType.None, CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace, CharType.None, .... };
تحذيرات PVS-Studio:- V3001 هناك تعابير فرعية مماثلة "CharType.Comment" إلى اليسار وإلى يمين "|" المشغل. XmlUTF8TextReader.cs 56
- V3001 هناك تعابير فرعية مماثلة "CharType.Comment" إلى اليسار وإلى يمين "|" المشغل. XmlUTF8TextReader.cs 58
- V3001 هناك تعابير فرعية مماثلة "CharType.Comment" إلى اليسار وإلى يمين "|" المشغل. XmlUTF8TextReader.cs 64
وجد المحلل استخدام تعبير
CharType.Comment | CharType.Comment مشبوهًا. تبدو غريبة بعض الشيء ، مثل
(CharType.Comment | CharType.Comment) == CharType.Comment . عند تهيئة عناصر الصفيف الأخرى ، والتي تستخدم
CharType.Comment ، لا يوجد مثل هذا الازدواجية.
العدد 19دعنا نستمر. دعنا نتحقق من المعلومات حول القيمة
المرجعة لطريقة
XmlBinaryWriterSession.TryAdd في وصف الطريقة وفي docs.microsoft.com - "
XmlBinaryWriterSession.TryAdd (XmlDictionaryString ، Int32) الطريقة ":
المرتجعات: true إذا كان من الممكن إضافة السلسلة ؛ خلاف ذلك ، خطأ.الآن دعنا ننظر إلى نص الأسلوب:
public virtual bool TryAdd(XmlDictionaryString value, out int key) { IntArray keys; if (value == null) throw System.Runtime .Serialization .DiagnosticUtility .ExceptionUtility .ThrowHelperArgumentNull(nameof(value)); if (_maps.TryGetValue(value.Dictionary, out keys)) { key = (keys[value.Key] - 1); if (key != -1) {
تحذير PVS-Studio: V3009 من الغريب أن هذه الطريقة تُرجع دائمًا نفس القيمة "صواب". XmlBinaryWriterSession.cs 29
يبدو غريباً أن الطريقة إما تُرجع
صواب أو تطرح استثناءً ، لكن القيمة
الخاطئة لا تُرجع أبدًا.
العدد 20صادفت الكود بمشكلة مشابهة ، لكن في هذه الحالة ، على العكس - الطريقة دائما تُرجع
خطأ :
internal virtual bool OnHandleReference(....) { if (xmlWriter.depth < depthToCheckCyclicReference) return false; if (canContainCyclicReference) { if (_byValObjectsInScope.Contains(obj)) throw ....; _byValObjectsInScope.Push(obj); } return false; }
تحذير PVS-Studio: V3009 من الغريب أن هذه الطريقة تُرجع دائمًا نفس القيمة "false". XmlObjectSerializerWriteContext.cs 415
حسنًا ، لقد قطعنا شوطًا طويلًا بالفعل! لذا ، قبل الانتقال ، أقترح أن لديك استراحة صغيرة: حرك عضلاتك ، والتجول ، واستريح لعينيك ، وانظر من النافذة ...
آمل في هذه المرحلة أن تكون مليئًا بالطاقة مرة أخرى ، لذلك دعونا نستمر. :)
العدد 21دعنا نراجع بعض الأجزاء الجذابة لمشروع
System.Security.Cryptography.Algorithms .
public override byte[] GenerateMask(byte[] rgbSeed, int cbReturn) { using (HashAlgorithm hasher = (HashAlgorithm)CryptoConfig.CreateFromName(_hashNameValue)) { byte[] rgbCounter = new byte[4]; byte[] rgbT = new byte[cbReturn]; uint counter = 0; for (int ib = 0; ib < rgbT.Length;) {
تحذير PVS-Studio: V3080 dereference null ممكن. النظر في تفتيش "هاش". PKCS1MaskGenerationMethod.cs 37
يحذر المحلل من أن قيمة المتغير
hasher يمكن أن تكون
فارغة عند تقييم
hasher. التعبير
TransformBlock ينتج عنه استثناء من نوع
NullReferenceException . أصبح هذا التحذير ممكنًا بسبب التحليل المتداخل.
لمعرفة ما إذا كان
hasher يمكن أن يأخذ القيمة
الخالية في هذه الحالة ، نحتاج إلى التراجع إلى أسلوب
CreateFromName .
public static object CreateFromName(string name) { return CreateFromName(name, null); }
لا شيء حتى الآن - دعنا نذهب أعمق.
نص إصدار
CreateFromName الذي تم تحميله بشكل زائد مع معلمتين كبير جدًا ، لذلك
أقتبس الإصدار القصير.
public static object CreateFromName(string name, params object[] args) { .... if (retvalType == null) { return null; } .... if (cons == null) { return null; } .... if (candidates.Count == 0) { return null; } .... if (rci == null || typeof(Delegate).IsAssignableFrom(rci.DeclaringType)) { return null; } .... return retval; }
كما ترى ، هناك العديد من نقاط الخروج في الطريقة التي يتم فيها إرجاع القيمة الفارغة بشكل صريح. لذلك ، على الأقل من الناحية النظرية ، في الطريقة أعلاه ، التي تسببت في حدوث تحذير ، قد يحدث استثناء من نوع NullReferenceException .النظرية رائعة ، ولكن دعونا نحاول إعادة إنتاج المشكلة في الممارسة. للقيام بذلك ، سنلقي نظرة أخرى على الطريقة الأصلية ولاحظ النقاط الرئيسية. أيضًا ، سنقوم بتقليل الكود غير ذي الصلة من الطريقة. public class PKCS1MaskGenerationMethod : ....
دعنا نلقي نظرة فاحصة على النقاط الرئيسية:1 ، 3 . تحتوي الفئة والأسلوب على معدلات وصول عامة . وبالتالي ، تتوفر هذه الواجهة عند إضافة مرجع إلى مكتبة - يمكننا محاولة إعادة إنتاج هذه المشكلة.2 . الطبقة غير مجردة المثال ، لديه منشئ العامة. يجب أن يكون من السهل إنشاء مثيل ، سنعمل معه. في بعض الحالات ، التي فكرت فيها ، كانت الفصول مجردة ، وذلك لإعادة إنتاج المشكلة التي كان علي البحث فيها عن الموروثين وطرق الحصول عليها.4 . يجب ألا تنشئ CreateFromName أي استثناءات ويجب أن تُرجع خالية - النقطة الأكثر أهمية ، سنعود إليها لاحقًا.5 ، 6 . من و cbReturnيجب أن تكون القيمة> 0 (ولكن ، بالطبع ، ضمن حدود كافية لإنشاء صفيف بنجاح). هناك حاجة إلى الامتثال لشرط cbReturn> 0 لتلبية الشرط الإضافي ib <rgbT.Length وإدخال جسم الحلقة.7 . Helpres.ConvertIntToByteArray يجب أن تعمل دون استثناءات.للوفاء بالشروط التي تعتمد على معلمات الطريقة ، يكفي تمرير الوسائط المناسبة ، على سبيل المثال:- rgbCeed - بايت جديد [] {0 ، 1 ، 2 ، 3} ؛
- cbReturn - 42.
من أجل "تشويه" طريقة CryptoConfig.CreateFromName ، نحتاج إلى أن نكون قادرين على تغيير قيمة الحقل _hashNameValue . لحسن الحظ ، لدينا ، لأن الفئة تحدد خاصية المجمع لهذا الحقل: public string HashName { get { return _hashNameValue; } set { _hashNameValue = value ?? DefaultHash; } }
من خلال تحديد قيمة "تركيبية" لـ HashName (أي _hashNameValue) ، يمكننا الحصول على القيمة الخالية من طريقة CreateFromName عند نقطة الخروج الأولى من تلك التي قمنا بتمييزها . لن أخوض في تفاصيل تحليل هذه الطريقة (آمل أن تغفر لي هذا) ، لأن الطريقة كبيرة جدًا.نتيجة لذلك ، قد تبدو التعليمة البرمجية التي ستؤدي إلى استثناء من نوع NullReferenceException كما يلي: PKCS1MaskGenerationMethod tempObj = new PKCS1MaskGenerationMethod(); tempObj.HashName = "Dummy"; tempObj.GenerateMask(new byte[] { 1, 2, 3 }, 42);
نضيف الآن مرجعًا إلى مكتبة تصحيح الأخطاء ، وقم بتشغيل التعليمات البرمجية والحصول على النتيجة المتوقعة:للمتعة فقط ، حاولت تنفيذ نفس الكود باستخدام حزمة NuGet من الإصدار 4.3.1.لا توجد معلومات حول الاستثناءات الناتجة ، وقيود معلمات الإخراج في وصف الطريقة. Docs.microsoft.com أسلوب PKCS1MaskGenerationMethod.GenerateMask (Byte []، Int32) "لا يحدده أيضًا.بالمناسبة ، عند كتابة المقالة ووصف ترتيب الإجراءات لإعادة إنتاج المشكلة ، وجدت طريقتين أخريين ل "استراحة" هذه الطريقة:- تمرير قيمة كبيرة للغاية كوسيطة cbReturn ؛
- تمرير القيمة الخالية كـ rgbSeed.
في الحالة الأولى ، سنحصل على استثناء من نوع OutOfMemoryException .في الحالة الثانية ، سنحصل على استثناء من نوع NullReferenceException عند تنفيذ تعبير rgbSeed.Length . في هذه الحالة ، من المهم أن يكون لهذا التجزئة قيمة غير فارغة. خلاف ذلك ، لن يصل تدفق التحكم إلى rgbSeed.Length .العدد 22صادفت بضعة أماكن مماثلة. public class SignatureDescription { .... public string FormatterAlgorithm { get; set; } public string DeformatterAlgorithm { get; set; } public SignatureDescription() { } .... public virtual AsymmetricSignatureDeformatter CreateDeformatter( AsymmetricAlgorithm key) { AsymmetricSignatureDeformatter item = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName(DeformatterAlgorithm); item.SetKey(key);
تحذيرات PVS-Studio:- V3080 dereference ممكن. النظر في فحص "البند". SignatureDescription.cs 31
- V3080 dereference ممكن. النظر في فحص "البند". SignatureDescription.cs 38
مرة أخرى ، في خصائص FormatterAlgorithm و DeformatterAlgorithm ، يمكننا كتابة مثل هذه القيم ، حيث تقوم CryptoConfig.CreateFromName بإرجاع القيمة الخالية في أساليب CreateDeformatter و CreateFormatter . علاوة على ذلك ، عند استدعاء أسلوب مثيل SetKey ، سيتم إنشاء استثناء NullReferenceException . المشكلة ، مرة أخرى ، يتم استنساخها بسهولة في الممارسة العملية: SignatureDescription signature = new SignatureDescription() { DeformatterAlgorithm = "Dummy", FormatterAlgorithm = "Dummy" }; signature.CreateDeformatter(null);
في هذه الحالة ، عند استدعاء CreateDeformatter وكذلك استدعاء CreateFormatter ، يتم طرح استثناء من نوع NullReferenceException .العدد 23:دعونا نراجع الأجزاء المهمة من مشروع System.Private.Xml . public override void WriteBase64(byte[] buffer, int index, int count) { if (!_inAttr && (_inCDataSection || StartCDataSection())) _wrapped.WriteBase64(buffer, index, count); else _wrapped.WriteBase64(buffer, index, count); }
تحذير PVS-Studio: V3004 عبارة "then" مكافئة لبيان "else". QueryOutputWriterV1.cs 242يبدو غريبا أن ثم و شيء آخر من فروع إذا بيانا يحتوي على نفس الرمز. إما أن يكون هناك خطأ هنا ويجب اتخاذ إجراء آخر في أحد الفروع ، أو يمكن حذف عبارة if .العدد 24 internal void Depends(XmlSchemaObject item, ArrayList refs) { .... if (content is XmlSchemaSimpleTypeRestriction) { baseType = ((XmlSchemaSimpleTypeRestriction)content).BaseType; baseName = ((XmlSchemaSimpleTypeRestriction)content).BaseTypeName; } else if (content is XmlSchemaSimpleTypeList) { .... } else if (content is XmlSchemaSimpleTypeRestriction) { baseName = ((XmlSchemaSimpleTypeRestriction)content).BaseTypeName; } else if (t == typeof(XmlSchemaSimpleTypeUnion)) { .... } .... }
تحذير PVS-Studio: V3003 استخدام 'if (A) {...} آخر إذا تم اكتشاف (A) {...}' النموذج. هناك احتمال لوجود خطأ منطقي. خطوط التحقق: 381 ، 396. ImportContext.cs 381في تسلسل if-else-if ، هناك تعبيران شرطان متساويان - المحتوى هو XmlSchemaSimpleTypeRestriction . ما هو أكثر من ذلك ، يحتوي على أجزاء من فروع البيانات ثم مجموعة مختلفة من التعبيرات. على أي حال ، سيتم تنفيذ نص الفرع الأول ذي الصلة ثم (إذا كان التعبير الشرطي صحيحًا) ، أو لن يتم تنفيذ أي منها في حال كان التعبير ذي الصلة خاطئًا.العدد 25لجعله أكثر إثارة للاهتمام للبحث عن الخطأ في الطريقة التالية ، سأذكر هو الجسم بأكمله. public bool MatchesXmlType(IList<XPathItem> seq, int indexType) { XmlQueryType typBase = GetXmlType(indexType); XmlQueryCardinality card; switch (seq.Count) { case 0: card = XmlQueryCardinality.Zero; break; case 1: card = XmlQueryCardinality.One; break; default: card = XmlQueryCardinality.More; break; } if (!(card <= typBase.Cardinality)) return false; typBase = typBase.Prime; for (int i = 0; i < seq.Count; i++) { if (!CreateXmlType(seq[0]).IsSubtypeOf(typBase)) return false; } return true; }
إذا تعاملت - مبروك!إن لم يكن - PVS-Studio لإنقاذ: V3102 وصول مشبوه إلى عنصر كائن "seq" بواسطة فهرس ثابت داخل حلقة. XmlQueryRuntime.cs 738من و لل حلقة يتم تنفيذه، والتعبير ط <seq.Count هو المستخدمة كما هو الحال مع أي من حالة الخروج. إنها تشير إلى فكرة أن المطورين يريدون تجاوز تسلسل seq . لكن في الحلقة ، يصل المؤلفون إلى عناصر التسلسل ليس باستخدام عداد [se] [i] ، لكن عددًا حرفيًا - صفر ( seq [0] ).العدد 26الخطأ التالي يناسب جزء صغير من التعليمات البرمجية ، لكنه لا يقل إثارة للاهتمام. public override void WriteValue(string value) { WriteValue(value); }
تحذير PVS-Studio: V3110 العودية لانهائية المحتملة داخل طريقة "WriteValue". XmlAttributeCache.cs 166تستدعي الطريقة نفسها ، وتشكيل العودية دون شرط الخروج.العدد 27 public IList<XPathNavigator> DocOrderDistinct(IList<XPathNavigator> seq) { if (seq.Count <= 1) return seq; XmlQueryNodeSequence nodeSeq = (XmlQueryNodeSequence)seq; if (nodeSeq == null) nodeSeq = new XmlQueryNodeSequence(seq); return nodeSeq.DocOrderDistinct(_docOrderCmp); }
تحذير PVS-Studio: V3095 تم استخدام كائن "seq" قبل أن يتم التحقق منه ضد قيمة خالية. خطوط التحقق: 880 ، 884. XmlQueryRuntime.cs 880يمكن للطريقة الحصول على القيمة الخالية كوسيطة. لهذا السبب ، عند الوصول إلى خاصية Count ، سيتم إنشاء استثناء لنوع NullReferenceException . تحت متغير nodeSeq محددا. يتم الحصول على nodeSeq كنتيجة لصياغة seq صريحة ، ولكن لا يزال من غير الواضح سبب إجراء الفحص. إذا كانت قيمة seq خالية ، فلن يتمكن تدفق التحكم من الوصول إلى هذا الفحص بسبب الاستثناء. إذا كانت قيمة seq ليست كذلكلاغية ، ثم:- إذا فشل الاختيار ، سيتم إنشاء استثناء من نوع InvalidCastException ؛
- إذا نجحت عملية الصب ، فإن nodeSeq بالتأكيد ليست خالية .
العدد 28صادفت 4 صانعي ، تحتوي على معلمات غير مستخدمة. ربما يتم تركهم للتوافق ، لكنني لم أجد تعليقات إضافية على هذه المعلمات غير المستخدمة.تحذيرات PVS-Studio:- لا يتم استخدام معلمة مُنشئ V3117 'securityUrl'. XmlSecureResolver.cs 15
- V3117 لم يتم استخدام معامل المنشئ 'strdata'. XmlEntity.cs 18
- V3117 لم يتم استخدام المعلمة الخاصة بالمنشئ . Compilation.cs 58
- V3117 لا يتم استخدام معلمة مُنشئ "الوصول". XmlSerializationILGen.cs 38
أول ما أثار اهتمامي أكثر (على الأقل ، دخل في قائمة التحذيرات الخاصة بالمقال). ما هو خاص جدا؟ لست متأكدا ربما ، اسمها. public XmlSecureResolver(XmlResolver resolver, string securityUrl) { _resolver = resolver; }
فقط من أجل الاهتمام ، لقد قمت بفحص ما هو مكتوب في docs.microsoft.com - " XmlSecureResolver Constructors " حول المعلمة securityUrl :عنوان URL المستخدم لإنشاء PermissionSet الذي سيتم تطبيقه على XmlResolver الأساسي. يستدعي XmlSecureResolver PermitOnly () على PermissionSet الذي تم إنشاؤه قبل استدعاء GetEntity (Uri ، String ، Type) على XmlResolver الأساسي.المشكلة 29في حزمة System.Private.Uri ، وجدت الطريقة التي لم تكن تتبع إرشادات Microsoft بالضبط بشأن تجاوز الأسلوب ToString . نحن هنا بحاجة إلى التذكير بأحد النصائح من الصفحة " Object.ToString Method ":يجب ألا يؤدي تجاوز ToString () إلى استثناء .الطريقة الغالبة نفسها تبدو كالتالي: public override string ToString() { if (_username.Length == 0 && _password.Length > 0) { throw new UriFormatException(SR.net_uri_BadUserPassword); } .... }
تحذير PVS-Studio: V3108 لا يوصى بإلقاء استثناءات من طريقة "ToSting ()". UriBuilder.cs 406يقوم الكود أولاً بتعيين سلسلة فارغة لحقل _username وآخر غير فارغ لحقل _password على التوالي من خلال الخصائص العامة UserName و Password. بعد ذلك تستدعي طريقة ToString . في النهاية سوف تحصل هذه الشفرة على استثناء. مثال على هذا الرمز: UriBuilder uriBuilder = new UriBuilder() { UserName = String.Empty, Password = "Dummy" }; String stringRepresentation = uriBuilder.ToString(); Console.WriteLine(stringRepresentation);
ولكن في هذه الحالة ، يحذر المطورون بصدق من أن الدعوة قد تؤدي إلى استثناء. يوصف في التعليقات على الطريقة وفي docs.microsoft.com - " UriBuilder.ToString Method ".العدد 30انظر إلى التحذيرات الصادرة على رمز مشروع System.Data.Common . private ArrayList _tables; private DataTable GetTable(string tableName, string ns) { .... if (_tables.Count == 0) return (DataTable)_tables[0]; .... }
تحذير PVS-Studio: مؤشر V3106 المحتمل هو خارج النطاق . يشير الفهرس "0" إلى ما وراء "الجداول". XMLDiffLoader.cs 277هل تبدو هذه الشفرة غير عادية؟ ما رأيك هو؟ طريقة غير عادية لإنشاء استثناء من نوع ArgumentOutOfRangeException ؟ لن أفاجأ من هذا النهج. بشكل عام ، إنه رمز غريب ومريب للغاية.العدد 31 internal XmlNodeOrder ComparePosition(XPathNodePointer other) { RealFoliate(); other.RealFoliate(); Debug.Assert(other != null); .... }
تحذير PVS-Studio: V3095 تم استخدام الكائن "الآخر" قبل أن يتم التحقق منه مقابل لاغٍ. خطوط التحقق: 1095 ، 1096. XPathNodePointer.cs 1095يشير التعبير الآخر! = لاغ كوسيطة لطريقة Debug.Assert ، إلى أن أسلوب ComparePosition يمكن الحصول على القيمة الخالية كوسيطة. على الأقل ، كان القصد للقبض على مثل هذه الحالات. ولكن في الوقت نفسه ، يتم استدعاء السطر الموجود أعلى طريقة المثيل الآخر. RealFoliate . نتيجة لذلك ، إذا كان لدى الآخر قيمة فارغة ، فسيتم إنشاء استثناء لنوع NullReferenceException قبل التحقق من التأكيد .العدد 32 private PropertyDescriptorCollection GetProperties(Attribute[] attributes) { .... foreach (Attribute attribute in attributes) { Attribute attr = property.Attributes[attribute.GetType()]; if ( (attr == null && !attribute.IsDefaultAttribute()) || !attr.Match(attribute)) { match = false; break; } } .... }
تحذير PVS-Studio: V3080 dereference null ممكن. النظر في تفتيش "attr". DbConnectionStringBuilder.cs 534التعبير الشرطي عن العبارة if يبدو مريبًا للغاية. المطابقة هي طريقة مثيل. وفقًا لـ check att == null ، null هي القيمة المقبولة (المتوقعة) لهذا المتغير. لذلك ، إذا وصل تدفق التحكم إلى المعامل الأيمن لـ || عامل التشغيل (إذا كان attr - null ) ، فسنحصل على استثناء من نوع NullReferenceException .وفقًا لشروط حدوث الاستثناء هي التالية:- The value of attr — null . The right operand of the && operator is evaluated.
- The value of !attribute.IsDefaultAttribute() — false . The overall result of the expression with the && operator — false .
- Since the left operand of the || operator is of the false value, the right operand is evaluated.
- Since attr — null , when calling the Match method, an exception is generated.
Issue 33 private int ReadOldRowData( DataSet ds, ref DataTable table, ref int pos, XmlReader row) { .... if (table == null) { row.Skip();
تحذير PVS-Studio: V3021 هناك بيانان "if" مع تعبيرات شرطية متطابقة. تحتوي العبارة "if" الأولى على طريقة إرجاع. وهذا يعني أن الثانية "إذا" البيان XMLDiffLoader.cs لا معنى لها 301وهناك لمدة يومين واذا البيانات، ويحتوي على قدم المساواة التعبير - الجدول == لاغية . هذا و، ثم فروع هذه البيانات تتضمن إجراءات مختلفة - في الحالة الأولى، يتم إنهاء الأسلوب مع القيمة -1، في ثانية واحدة - يتم إنشاء استثناء. من و الجدول لم يتم تغيير متغير بين الشيكات. وبالتالي ، لن يتم إنشاء الاستثناء المدروس.العدد 34انظر إلى الطريقة المثيرة للاهتمام من System.ComponentModel.TypeConverterالمشروع. حسنًا ، دعنا أولاً نقرأ التعليق ، واصفًا إياه:يزيل الحرف الأخير من السلسلة المنسقة. (إزالة الحرف الأخير في السلسلة الافتراضية). عند الخروج ، يحتوي param out على الموضع الذي تم فيه تنفيذ العملية بالفعل. هذا الموقف بالنسبة لسلسلة الاختبار. يوفر MaskedTextResultHint out param مزيدًا من المعلومات حول نتيجة العملية. إرجاع صحيح عند النجاح ، خطأ .النقطة الأساسية في قيمة الإرجاع: إذا نجحت العملية ، فستُرجع الطريقة بشكل صحيح ، وإلا - خطأ . دعونا نرى ما يحدث في الواقع. public bool Remove(out int testPosition, out MaskedTextResultHint resultHint) { .... if (lastAssignedPos == INVALID_INDEX) { .... return true;
تحذير PVS-Studio: V3009 من الغريب أن هذه الطريقة تُرجع دائمًا نفس القيمة "صواب". MaskedTextProvider.cs 1529في الواقع ، اتضح أن القيمة المرجعة الوحيدة للطريقة صحيحة .العدد 35 public void Clear() { if (_table != null) { .... } if (_table.fInitInProgress && _delayLoadingConstraints != null) { .... } .... }
تحذير PVS-Studio: V3125 تم استخدام الكائن '_table' بعد أن تم التحقق منه ضد قيمة خالية. تحقق خطوط: 437، 423. ConstraintCollection.cs 437من و ! _table = فارغة تحقق يتحدث عن نفسه - و _table متغير يمكن أن يكون باطلا من القيمة. على الأقل ، في هذه الحالة يتم إعادة تأمين مؤلفي كود الحالة. ومع ذلك ، أدناه يعالجون حقل المثيل عبر _table ولكن بدون التحقق من وجود null - _table .fInitInProgress .العدد 36الآن لننظر في العديد من التحذيرات الصادرة لكود System.Runtime.Serialization.Formatters . private void Write(....) { .... if (memberNameInfo != null) { .... _serWriter.WriteObjectEnd(memberNameInfo, typeNameInfo); } else if ((objectInfo._objectId == _topId) && (_topName != null)) { _serWriter.WriteObjectEnd(topNameInfo, typeNameInfo); .... } else if (!ReferenceEquals(objectInfo._objectType, Converter.s_typeofString)) { _serWriter.WriteObjectEnd(typeNameInfo, typeNameInfo); } }
تحذير PVS-Studio: V3038 تم تمرير الوسيطة إلى الطريقة عدة مرات. من الممكن أن يتم تمرير وسيطة أخرى بدلاً من ذلك. BinaryObjectWriter.cs 262تم خلط المحلل بواسطة المكالمة الأخيرة _serWriter.WriteObjectEnd بوسيطتين متساويتين - typeNameInfo . يبدو وكأنه خطأ مطبعي ، لكن لا يمكنني القول بالتأكيد. قررت التحقق من ما هي طريقة WriteObjectEnd callee . internal void WriteObjectEnd(NameInfo memberNameInfo, NameInfo typeNameInfo) { }
حسنا ... دعنا ننتقل. :)
العدد 37 internal void WriteSerializationHeader( int topId, int headerId, int minorVersion, int majorVersion) { var record = new SerializationHeaderRecord( BinaryHeaderEnum.SerializedStreamHeader, topId, headerId, minorVersion, majorVersion); record.Write(this); }
عند مراجعة هذا الرمز ، لن أقول على الفور ما هو الخطأ هنا أو ما الذي يبدو مريبًا. لكن المحلل قد يقول ما هو الشيء.تحذير PVS-Studio: V3066 تم تمرير ترتيب غير صحيح للوسيطات إلى مُنشئ "SerializationHeaderRecord": "minorVersion" و "majorVersion". BinaryFormatterWriter.cs 111راجع مُنشئ callee لفئة SerializationHeaderRecord . internal SerializationHeaderRecord( BinaryHeaderEnum binaryHeaderEnum, int topId, int headerId, int majorVersion, int minorVersion) { _binaryHeaderEnum = binaryHeaderEnum; _topId = topId; _headerId = headerId; _majorVersion = majorVersion; _minorVersion = minorVersion; }
كما يمكننا أن نرى ، المعلمات منشئ اتبع في الترتيب majorVersion ، minorVersion ؛ بينما عند استدعاء المنشئ يتم تمريرها بالترتيب التالي: minorVersion ، majorVersion . يبدو وكأنه خطأ مطبعي. في حال تم إجراؤه عمداً (ماذا لو؟) - أعتقد أنه سيتطلب تعليقًا إضافيًا.العدد 38 internal ObjectManager( ISurrogateSelector selector, StreamingContext context, bool checkSecurity, bool isCrossAppDomain) { _objects = new ObjectHolder[DefaultInitialSize]; _selector = selector; _context = context; _isCrossAppDomain = isCrossAppDomain; }
: استوديو التحذير من PVS V3117 لم يتم استخدام منشئ المعلمة 'checksecurity. ObjectManager.cs 33 لايتم استخدام المعلمة checkSecurity الخاصة بالمنشئ بأي طريقة. لا توجد تعليقات على ذلك. أعتقد أنه ترك للتوافق ، ولكن على أي حال ، في سياق المحادثات الأمنية الأخيرة ، يبدو الأمر مثيرًا للاهتمام.العدد 39هنا هو الكود الذي بدا غير عادي بالنسبة لي. يبدو النموذج متشابهًا في الحالات الثلاث المكتشفة ، ويقع في أساليب لها أسماء متساوية وأسماء متغيرات. وبناء على ذلك:- إما أنا لست مستنير بما فيه الكفاية للحصول على الغرض من هذا الازدواجية ؛
- أو تم نشر الخطأ بواسطة طريقة نسخ اللصق.
الكود نفسه: private void EnlargeArray() { int newLength = _values.Length * 2; if (newLength < 0) { if (newLength == int.MaxValue) { throw new SerializationException(SR.Serialization_TooManyElements); } newLength = int.MaxValue; } FixupHolder[] temp = new FixupHolder[newLength]; Array.Copy(_values, 0, temp, 0, _count); _values = temp; }
تحذيرات PVS-Studio:- تعبير V3022 'newLength == int.MaxValue' غير صحيح دائمًا. ObjectManager.cs 1423
- تعبير V3022 'newLength == int.MaxValue' غير صحيح دائمًا. ObjectManager.cs 1511
- تعبير V3022 'newLength == int.MaxValue' غير صحيح دائمًا. ObjectManager.cs 1558
ما هو مختلف في الطرق الأخرى هو نوع عناصر مجموعة temp (ليس FixupHolder ، ولكن طويل أو كائن ). لذلك لا يزال لدي شكوك حول النسخ واللصق ...العدد 40من التعليمات البرمجية من مشروع System.Data.Odbc . public string UnquoteIdentifier(....) { .... if (!string.IsNullOrEmpty(quotePrefix) || quotePrefix != " ") { .... } .... }
تحذير PVS-Studio: V3022 Expression '! String.IsNullOrEmpty (quotePrefix) || quotePrefix! = "" "صحيح دائمًا. OdbcCommandBuilder.cs 338يفترض المحلل أن التعبير المعطى له دائمًا القيمة الحقيقية . انها حقا كذلك. حتى لا يهم ما هي القيمة بالفعل في quotePrefix - الشرط نفسه مكتوب بشكل غير صحيح. دعنا نصل إلى أسفل هذا.لدينا || عامل التشغيل ، لذلك ستكون قيمة التعبير صحيحة ، إذا كان المعامل الأيسر أو الأيمن (أو كلاهما) سيكون له القيمة الحقيقية . كل شيء واضح مع اليسار. سيتم تقييم الشخص الصحيح فقط في حالة ما إذا كان الشخص الأيسر لديه الخطأقيمة. هذا يعني أنه إذا تم تكوين التعبير بالطريقة التي تكون بها قيمة المعامل الأيمن صحيحة دائمًا عندما تكون قيمة المعيار الأيسر غير صحيحة ، فستكون نتيجة التعبير بالكامل صحيحة بشكل دائم .من التعليمة البرمجية أعلاه ، نعلم أنه إذا تم تقييم المعامل الصحيح ، فإن قيمة سلسلة التعبير. IsNullOrEmpty (quotePrefix) - صواب ، لذلك تكون إحدى هذه العبارات صحيحة:- quotePrefix == null ؛
- quotePrefix.Length == 0 .
إذا كان أحد هذه العبارات صحيحًا ، فسيكون التعبير quotePrefix! = "" صحيحًا أيضًا ، وهو ما أردنا إثباته. بمعنى أن قيمة التعبير بالكامل صحيحة دائمًا ، بغض النظر عن محتويات quotePrefix .العدد 41 -العودة إلى المنشئات ذات المعلمات غير المستخدمة: private sealed class PendingGetConnection { public PendingGetConnection( long dueTime, DbConnection owner, TaskCompletionSource<DbConnectionInternal> completion, DbConnectionOptions userOptions) { DueTime = dueTime; Owner = owner; Completion = completion; } public long DueTime { get; private set; } public DbConnection Owner { get; private set; } public TaskCompletionSource<DbConnectionInternal> Completion { get; private set; } public DbConnectionOptions UserOptions { get; private set; } }
استوديو تحذر من PVS: V3117 منشئ المعلمة 'userOptions' غير مستعملة. DbConnectionPool.cs 26يمكننا أن نرى من تحذيرات المحلل والرمز ، أن معلمة مُنشئ واحدة فقط لا تُستخدم - userOptions ، وغيرها تُستخدم لتهيئة خصائص الاسم نفسه. يبدو أن مطورًا قد نسي تهيئة إحدى الخصائص.العدد 42يوجد كود مشبوه ، صادفناه مرتين. النمط هو نفسه. private DataTable ExecuteCommand(....) { .... foreach (DataRow row in schemaTable.Rows) { resultTable.Columns .Add(row["ColumnName"] as string, (Type)row["DataType"] as Type); } .... }
تحذيرات PVS-Studio:- V3051 الزهر من النوع الزائد. الكائن بالفعل من نوع "النوع". DbMetaDataFactory.cs 176
- V3051 الزهر من النوع الزائد. الكائن بالفعل من نوع "النوع". OdbcMetaDataFactory.cs 1109
يبدو تعبير الصف (النوع) ["DataType"] كنوع مشبوهًا. أولا، صريح الصب إرادة القيام بها، وبعد ذلك - عن طريق صب كما هو الحال مع المشغل. إذا كان صف القيمة ["DataType"] - خاليًا ، فسيتم "تمرير" بنجاح في كلتا القوالب وسيتم القيام به كوسيطة للأسلوب Add . إذا كان الصف ["DataType"] يُرجع القيمة ، والتي لا يمكن تحويلها إلى نوع الكتابة ، فسيتم إنشاء استثناء لنوع InvalidCastException مباشرة أثناء عملية الإرسال الصريحة. في النهاية ، لماذا نحتاج إلى اثنين من المسبوكات هنا؟ السؤال مفتوح.العدد 43لنلقي نظرة على الجزء المشبوه منSystem.Runtime.InteropServices.RuntimeInformation . public static string FrameworkDescription { get { if (s_frameworkDescription == null) { string versionString = (string)AppContext.GetData("FX_PRODUCT_VERSION"); if (versionString == null) { .... versionString = typeof(object).Assembly .GetCustomAttribute< AssemblyInformationalVersionAttribute>() ?.InformationalVersion; .... int plusIndex = versionString.IndexOf('+'); .... } .... } .... } }
تحذير PVS-Studio: V3105 تم استخدام المتغير ' versionString ' بعد تعيينه من خلال عامل التشغيل الفارغ الشرطي. NullReferenceException ممكن. RuntimeInformation.cs 29من محلل يحذر نحو ممكن قبل باستثناء لNullReferenceException من النوع عند استدعاء IndexOf طريقة ل VersionString متغير. عند استلام القيمة لأحد المتغيرات ، يستخدم مؤلفو الكود "؟". عامل التشغيل لتجنب استثناء NullReferenceException عند الوصول إلى خاصية InfromationalVersion . الخدعة هي أنه إذا كانت استدعاء GetCustomAttribute <...> بإرجاع فارغة، وهو من إرادة باستثناء BE للا يزال ولدت، ولكن دون - وعند استدعاء IndexOf الطريقة، كما هو الحال مع VersionString ومن لديهم لاغية من القيمة.العدد 44دعنا نتناول مشروع System.ComponentModel.Composition وننظر في العديد من التحذيرات. تم إصدار تحذيرين للشفرة التالية: public static bool CanSpecialize(....) { .... object[] genericParameterConstraints = ....; GenericParameterAttributes[] genericParameterAttributes = ....;
تحذيرات PVS-Studio:- V3125 تم استخدام عنصر "genericParameterConstrict" بعد أن تم التحقق منه ضد قيمة خالية. خطوط التحقق: 603 ، 589. GenericSpecializationPartCreationInfo.cs 603
- V3125 تم استخدام عنصر "genericParameterAttributes" بعد أن تم التحقق منه مقابل خالية. خطوط التحقق: 604 ، 594. GenericSpecializationPartCreationInfo.cs 604
في التعليمات البرمجية ، هناك اختبارات genericParameterAttributes! = Null و genericParameterConstrict! = خالية . لذلك ، القيم الخالية - المقبولة لهذه المتغيرات ، سنأخذها في الاعتبار. إذا كان كلا المتغيرين لهما قيمة فارغة ، فسنخرج من الطريقة ، بلا أسئلة. ماذا لو كان أحد المتغيرين المذكورين أعلاه باطلاً ، لكن في قيامنا بذلك ، فإننا لا ننهي هذه الطريقة؟ إذا كانت مثل هذه الحالة ممكنة وتمكّن التنفيذ من اجتياز الحلقة ، فسنحصل على استثناء من نوع NullReferenceException .العدد 45بعد ذلك سننتقل إلى تحذير آخر مثير للاهتمام من هذا المشروع. ومع ذلك ، دعونا نفعل شيئًا مختلفًا - أولاً سنستخدم الفصل مرة أخرى ، ثم ننظر إلى الكود. بعد ذلك ، سنضيف مرجعًا إلى حزمة NuGet التي تحمل نفس الاسم لآخر إصدار تجريبي متاح في المشروع (قمت بتثبيت حزمة الإصدار 4.6.0-preview6.19303.8). لنكتب رمزًا بسيطًا ، على سبيل المثال ، مثل: LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(null); Console.WriteLine(eq);
من و ويساوي لا تعليقا الطريقة، أنا من ألم العثور على وصف هذه الطريقة ل. NET الأساسية AT docs.microsoft.com، فقط لإطار عمل .NET. إذا نظرنا إليها (" طريقة LazyMemberInfo.Equals (كائن) ") - فلن نرى أي شيء خاصًا سواء كان يُرجع صواب أو خطأ ، لا توجد معلومات حول الاستثناءات التي تم إنشاؤها. سننفذ الكود ونرى:يمكننا الحصول على القليل من الملتوية وكتابة التعليمة البرمجية التالية وكذلك الحصول على إخراج مثير للاهتمام: LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(typeof(String)); Console.WriteLine(eq);
نتيجة تنفيذ التعليمات البرمجية.ومن المثير للاهتمام ، يتم إنشاء هذين الاستثناءين في نفس التعبير. دعونا ننظر insidethe طريقة يساوي . public override bool Equals(object obj) { LazyMemberInfo that = (LazyMemberInfo)obj;
تحذير PVS-Studio: V3115 لا ينبغي أن يؤدي تمرير "لاغٍ" إلى "يساوي" إلى "NullReferenceException". LazyMemberInfo.cs 116في الواقع في هذه الحالة ، حلل المحلل قليلاً ، حيث أصدر تحذيرًا لتعبير that._memberType . ومع ذلك ، تحدث الاستثناءات السابقة عند تنفيذ تعبير (LazyMemberInfo) obj . لقد قدمنا بالفعل مذكرة منه.أعتقد أن كل شيء واضح مع InvalidCastException. لماذا يتم إنشاء NullReferenceException ؟ والحقيقة هي أن LazyMemberInfo هو هيكل ، وبالتالي ، فإنه يحصل على علبته. من و باطلة من قيمة الميزة علبته، في المنعطفات،يؤدي إلى حدوث استثناء من NullReferenceExceptionاكتب. يوجد أيضًا بعض الأخطاء المطبعية في التعليقات - ربما ينبغي على المؤلفين إصلاحها. استثناء صريح لا يزال على أيدي المؤلفين.العدد 46بالمناسبة ، صادفت حالة مماثلة في System.Drawing.Common في بنية TriState . public override bool Equals(object o) { TriState state = (TriState)o; return _value == state._value; }
تحذير PVS-Studio: V3115 لا ينبغي أن يؤدي تمرير "لاغٍ" إلى "يساوي" إلى "NullReferenceException". TriState.cs 53المشاكل هي نفسها كما في الحالة الموضحة أعلاه.العدد 47لننظر في العديد من الأجزاء من System.Text.Json .تذكر أنني كتبت أن ToString يجب ألا يعود فارغًا ؟ الوقت لترسيخ هذه المعرفة. public override string ToString() { switch (TokenType) { case JsonTokenType.None: case JsonTokenType.Null: return string.Empty; case JsonTokenType.True: return bool.TrueString; case JsonTokenType.False: return bool.FalseString; case JsonTokenType.Number: case JsonTokenType.StartArray: case JsonTokenType.StartObject: {
للوهلة الأولى ، لا تعود هذه الطريقة خالية ، لكن المحلل يجادل العكس.تحذير PVS-Studio: V3108 لا يوصى بإرجاع "خالية" من طريقة "ToSting ()". JsonElement.cs 1460يشير المحلل إلى السطر مع استدعاء الأسلوب GetString () . دعونا نلقي نظرة على ذلك. public string GetString() { CheckValidInstance(); return _parent.GetString(_idx, JsonTokenType.String); }
دعنا نذهب أعمق في الإصدار الزائد من أسلوب GetString : internal string GetString(int index, JsonTokenType expectedType) { .... if (tokenType == JsonTokenType.Null) { return null; } .... }
مباشرة بعد رؤية الشرط الذي سينتج عن تنفيذه القيمة الخالية - سواء من هذه الطريقة أو ToString التي نظرنا فيها مبدئيًا.العدد 48جزء آخر مثير للاهتمام: internal JsonPropertyInfo CreatePolymorphicProperty(....) { JsonPropertyInfo runtimeProperty = CreateProperty(property.DeclaredPropertyType, runtimePropertyType, property.ImplementedPropertyType, property?.PropertyInfo, Type, options); property.CopyRuntimeSettingsTo(runtimeProperty); return runtimeProperty; }
تحذير PVS-Studio: V3042 NullReferenceException ممكن. "؟" و "." يتم استخدام عوامل التشغيل للوصول إلى أعضاء كائن "الخاصية" JsonClassInfo.AddProperty.cs 179عند استدعاء الأسلوب CreateProperty ، تتم إحالة الخصائص عدة مرات من خلال خاصية المتغير : property.DeclaredPropertyType ، property.ImplementedPropertyType ، property؟ .PropertyInfo . كما ترون ، يستخدم مؤلفو كود الحالة "؟" المشغل.
إذا لم يكن المكان غير موجود هنا ويمكن أن يكون للممتلكات القيمة الخالية ، فلن يكون هذا المشغل مفيدًا ، حيث سيتم إنشاء استثناء لنوع NullReferenceException من خلال الوصول المباشر.العدد 49تم العثور على الأجزاء المشبوهة التالية في مشروع System.Security.Cryptography.Xml . يتم إقرانها ، كما كانت عدة مرات مع تحذيرات أخرى. مرة أخرى ، يشبه الرمز نسخ اللصق ، قارن هذه بنفسك.الجزء الأول: public void Write(StringBuilder strBuilder, DocPosition docPos, AncestralNamespaceContextManager anc) { docPos = DocPosition.BeforeRootElement; foreach (XmlNode childNode in ChildNodes) { if (childNode.NodeType == XmlNodeType.Element) { CanonicalizationDispatcher.Write( childNode, strBuilder, DocPosition.InRootElement, anc); docPos = DocPosition.AfterRootElement; } else { CanonicalizationDispatcher.Write(childNode, strBuilder, docPos, anc); } } }
الجزء الثاني. public void WriteHash(HashAlgorithm hash, DocPosition docPos, AncestralNamespaceContextManager anc) { docPos = DocPosition.BeforeRootElement; foreach (XmlNode childNode in ChildNodes) { if (childNode.NodeType == XmlNodeType.Element) { CanonicalizationDispatcher.WriteHash( childNode, hash, DocPosition.InRootElement, anc); docPos = DocPosition.AfterRootElement; } else { CanonicalizationDispatcher.WriteHash(childNode, hash, docPos, anc); } } }
تحذيرات PVS-Studio:- تتم إعادة كتابة المعلمة V3061 "docPos" دائمًا في نص الطريقة قبل استخدامها. CanonicalXmlDocument.cs 37
- تتم إعادة كتابة المعلمة V3061 "docPos" دائمًا في نص الطريقة قبل استخدامها. CanonicalXmlDocument.cs 54
في كلتا الطريقتين ، يتم استبدال المعلمة docPos قبل استخدام قيمتها. لذلك ، يتم ببساطة تجاهل القيمة المستخدمة كوسيطة أسلوب.العدد 50دعنا نأخذ في الاعتبار العديد من التحذيرات على رمز مشروع System.Data.SqlClient . private bool IsBOMNeeded(MetaType type, object value) { if (type.NullableType == TdsEnums.SQLXMLTYPE) { Type currentType = value.GetType(); if (currentType == typeof(SqlString)) { if (!((SqlString)value).IsNull && ((((SqlString)value).Value).Length > 0)) { if ((((SqlString)value).Value[0] & 0xff) != 0xff) return true; } } else if ((currentType == typeof(string)) && (((String)value).Length > 0)) { if ((value != null) && (((string)value)[0] & 0xff) != 0xff) return true; } else if (currentType == typeof(SqlXml)) { if (!((SqlXml)value).IsNull) return true; } else if (currentType == typeof(XmlDataFeed)) { return true;
تحذير PVS-Studio: V3095 تم استخدام كائن "value" قبل التحقق من صحته. خطوط التحقق: 8696 ، 8708. TdsParser.cs 8696كان المحير في حيرة من أمره بسبب قيمة الاختيار ! = خالية في أحد الشروط. يبدو أنه كان خلال إعادة بيع ديون خسر هناك، كما هو الحال مع قيمة ويحصل على محتويات المؤشر عدة مرات. وإذا قيمة يمكن أن يكون باطلا من قيمة - أشياء سيئة.العدد 51الخطأ التالي من الاختبارات ، لكنه بدا مثيراً للاهتمام بالنسبة لي ، لذلك قررت الاستشهاد به. protected virtual TDSMessageCollection CreateQueryResponse(....) { .... if (....) { .... } else if ( lowerBatchText.Contains("name") && lowerBatchText.Contains("state") && lowerBatchText.Contains("databases") && lowerBatchText.Contains("db_name"))
تحذير PVS-Studio: V3053 تعبير مفرط. فحص substrings "اسم" و "db_name". QueryEngine.cs 151الحقيقة هي أنه في هذه الحالة ، يكون الجمع بين تعبيرات فرعية أقلخلفيات النص.تحتوي على ("اسم") و LowerBatchText.Contains ("db_name") متكررة. بالفعل ، إذا كانت السلسلة المحددة تحتوي على السلسلة الفرعية "db_name" ، فستحتوي على السلسلة الفرعية "name" أيضًا. إذا لم تحتوي السلسلة على "اسم" ، فلن تحتوي على "db_name" أيضًا. نتيجةً لذلك ، اتضح أن علامة التحقق LowerBatchText.Contains ("الاسم")لا لزوم لها. ما لم يمكن أن يقلل من عدد التعبيرات التي تم تقييمها ، إذا كانت السلسلة المحددة لا تحتوي على "اسم" .العدد 52جزء مشبوه من رمز مشروع System.Net.Requests . protected override PipelineInstruction PipelineCallback( PipelineEntry entry, ResponseDescription response, ....) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Command:{entry?.Command} Description:{response?.StatusDescription}");
تحذير PVS-Studio: V3125 تم استخدام كائن "الإدخال" بعد التحقق من أنه لاغٍ. الاختيار خطوط: 270، 227. FtpControlStream.cs 270لعند إنشاء أحد محرف: سلسلة هذه التعبيرات كما هو الحال مع ؟ دخول .Command و ؟ رد. الوصف تستخدم. "؟" يتم استخدام المشغل بدلاً من "." عامل التشغيل لا يحصل على استثناء من نوع NullReferenceException في حالة ما إذا كان أي من المعلمات المطابقة له قيمة فارغة . في هذه الحالة ، تعمل هذه التقنية. علاوة على ذلك ، كما نرى من الكود ، يتم تقسيم قيمة فارغة محتملة للاستجابة (الخروج من الطريقة إذا كانت الاستجابة == فارغة) ، في حين لا يوجد شيء مماثل للدخول. A من نتيجة و، وإذا دخول - اغية وعلاوة على ذلك على طول تحت الرمز عند تقييم entry.Command ( '.' '؟'. ومع استخدام، لا)، وهو من استثناء ولدت إرادة BE.في هذه المرحلة ، ينتظرنا مراجعة مدونة مفصلة إلى حد ما ، لذلك أقترح أن يكون لديك استراحة أخرى ، هدئ أعصابك ، وصنع بعض الشاي أو القهوة. بعد ذلك سأكون هنا للمتابعة.هل عدت ثم دعنا نستمر. :)
العدد 53الآن لنجد شيئًا مثيرًا للاهتمام في مشروع System.Collections.Immutable . هذه المرة سيكون لدينا بعض التجارب مع System.Collections.Immutable.ImmutableArray <T> struct. أساليب IStructuralEquatable.Equals و IStructuralComparable.CompareTo هي ذات أهمية خاصة بالنسبة لنا.لنبدأ مع أسلوب IStructuralEquatable.Equals . الكود الوارد أدناه ، أقترح أن تحاول معرفة ما هو الخطأ بنفسك: bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { var self = this; Array otherArray = other as Array; if (otherArray == null) { var theirs = other as IImmutableArray; if (theirs != null) { otherArray = theirs.Array; if (self.array == null && otherArray == null) { return true; } else if (self.array == null) { return false; } } } IStructuralEquatable ours = self.array; return ours.Equals(otherArray, comparer); }
هل تمكنت؟ إذا كانت الإجابة بنعم - مبروك لي. :)
تحذير PVS-Studio: V3125 تم استخدام كائن "خاص بنا" بعد التحقق من صحته. الاختيار خطوط: 1212، 1204. ImmutableArray_1.cs 1212من محلل هو: الخلط بواسطة استدعاء مثيل لليساوي طريقة من خلال من لنا متغير، ويقع في لمشاركة بيان عودة التعبير، كما هو الحال مع تكنولوجيا المعلومات يشير إلى أن أحد من قبل باستثناء لNullReferenceException من نوع القوة وجدت هنا تحدث تشغيل. لماذا يشير المحلل إلى ذلك؟ لتسهيل التوضيح ، أعطي جزءًا بسيطًا من التعليمات البرمجية لنفس الطريقة أدناه. bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { .... if (....) { .... if (....) { .... if (self.array == null && otherArray == null) { .... } else if (self.array == null) { .... } } } IStructuralEquatable ours = self.array; return ours.Equals(otherArray, comparer); }
التعبيرات لآخر مع و، <BR> يمكننا أن نرى أن من قيمة لنا متغير يأتي من self.array . يتم التحقق من self.array == فارغة عدة مرات أعلاه. وهذا يعني ، لدينا ، نفس صفيف الذات ، يمكن أن يكون لها قيمة فارغة . على الأقل من الناحية النظرية. هل هذه الحالة قابلة للوصول في الممارسة؟ دعنا نحاول معرفة ذلك. للقيام بذلك ، مرة أخرى ، استشهد بنسق الطريقة باستخدام نقاط رئيسية محددة. bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { var self = this;
النقطة الرئيسية 1. self.array == this.array (بسبب الذات = هذا ). لذلك ، قبل استدعاء الطريقة ، نحتاج إلى الحصول على الشرط this.array == null .النقطة الرئيسية 2 . يمكننا تجاهل هذا إذا ، والذي سيكون أبسط طريقة للحصول على ما نريد. لتجاهل هذا إذا ، نحتاج فقط إلى أن يكون المتغير الآخر من نوع Array أو أحد المتغيرات المشتقة ، وليس لاحتواء القيمة الخالية . الطريقة هذه، بعد استخدام كما المشغل، وسوف تكون مكتوبة إشارة غير خالية في otherArray وسنقوم تجاهل أولا إذا البيان .النقطة الرئيسية 3. هذه النقطة تتطلب نهجا أكثر تعقيدا. نحتاج بالتأكيد إلى الخروج من الجملة الثانية إذا كانت العبارة (العبارة ذات التعبير الشرطي بها ! = خالية ). إذا لم يحدث ذلك ، ثم بدأ الفرع في التنفيذ ، فمن المؤكد أننا لن نحصل على النقطة المطلوبة 5 تحت الشرط self.array == null بسبب النقطة الرئيسية 4. لتجنب إدخال عبارة if الخاصة بالنقطة الرئيسية 3 ، واحدة من هذه الشروط يجب أن تتحقق:- و أخرى ذات قيمة لديه إلى BE اغية .
- يجب ألا يقوم النوع الفعلي الآخر بتطبيق واجهة IImmutableArray .
النقطة الرئيسية 5 . إذا وصلنا إلى هذه النقطة مع القيمة self.array == فارغة ، فهذا يعني أننا قد وصلنا إلى هدفنا ، وسيتم إنشاء استثناء لنوع NullReferenceException .نحصل على مجموعات البيانات التالية التي ستقودنا إلى النقطة المطلوبة.أولاً: this.array - null .ثانياً - واحد مما يلي:- أخرى - لاغية .
- الآخر لديه نوع المصفوفة أو أحدهما مشتق ؛
- الآخر لا يحتوي على نوع Array أو مشتق منه وفي القيام بذلك ، لا يقوم بتطبيق واجهة IImmutableArray .
الصفيف هو الحقل ، المعلن بالطريقة التالية: internal T[] array;
نظرًا لأن ImmutableArray <T> عبارة عن بنية ، فإن لها مُنشئًا افتراضيًا (بدون وسيطات) سينتج عن حقل الصفيف أخذ القيمة افتراضيًا ، والتي تكون خالية. وهذا ما نحتاجه.دعونا لا ننسى أننا نحقق في تطبيق صريح لأسلوب الواجهة ، وبالتالي ، يجب إجراء عملية الإرسال قبل الاتصال.الآن لدينا اللعبة في متناول اليد للوصول إلى حدوث استثناء في ثلاث طرق. نضيف مرجعًا إلى إصدار مكتبة تصحيح الأخطاء ، ونكتب الرمز ، وننفذ ونرى ما سيحدث.جزء الكود 1. var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(null, comparer);
جزء الكود 2. var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(new string[] { }, comparer);
جزء الكود 3. var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(typeof(Object), comparer);
ستكون نتيجة التنفيذ لشظايا الشفرات الثلاثة هي نفسها ، ولن يتحقق ذلك إلا عن طريق إدخال بيانات إدخال مختلفة ومسارات التنفيذ.العدد 54إذا لم تنسَ ، فلدينا طريقة أخرى نحتاج إلى تشويه سمعتها. :) لكن هذه المرة لن نغطيها بمثل هذه التفاصيل. علاوة على ذلك ، نحن نعرف بالفعل بعض المعلومات من المثال السابق. int IStructuralComparable.CompareTo(object other, IComparer comparer) { var self = this; Array otherArray = other as Array; if (otherArray == null) { var theirs = other as IImmutableArray; if (theirs != null) { otherArray = theirs.Array; if (self.array == null && otherArray == null) { return 0; } else if (self.array == null ^ otherArray == null) { throw new ArgumentException( SR.ArrayInitializedStateNotEqual, nameof(other)); } } } if (otherArray != null) { IStructuralComparable ours = self.array; return ours.CompareTo(otherArray, comparer);
تحذير PVS-Studio: V3125 تم استخدام كائن "خاص بنا" بعد التحقق من صحته. خطوط التحقق: 1265 ، 1251. ImmutableArray_1.cs 1265كما ترون ، الحالة تشبه إلى حد كبير المثال السابق.دعنا نكتب الكود التالي: Object other = ....; var comparer = Comparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralComparable)immutableArray).CompareTo(other, comparer);
سنحاول العثور على بعض بيانات الإدخال للوصول إلى النقطة ، حيث قد يحدث استثناء من نوع NullReferenceException :القيمة: أخرى - سلسلة جديدة [] {} ؛النتيجة:وبالتالي ، تمكنا مرة أخرى من معرفة هذه البيانات ، والتي يحدث استثناء في هذه الطريقة.العدد 55في مشروع System.Net HTTP المستمع عثرت على عدة أماكن مشبوهة ومتشابهة للغاية. مرة أخرى ، لا يمكنني التخلص من الشعور بنسخ النسخ ، التي تحدث هنا. نظرًا لأن النمط هو نفسه ، سننظر في مثال واحد للرمز. سوف أذكر تحذيرات محلل للحالات الباقية. public override IAsyncResult BeginRead(byte[] buffer, ....) { if (NetEventSource.IsEnabled) { NetEventSource.Enter(this); NetEventSource.Info(this, "buffer.Length:" + buffer.Length + " size:" + size + " offset:" + offset); } if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } .... }
تحذير PVS-Studio: V3095 تم استخدام كائن "المخزن المؤقت" قبل أن يتم التحقق منه ضد قيمة خالية. أسطر الفحص: 51 ، 53. HttpRequestStream.cs 51إنشاء استثناء من نوع ArgumentNullException تحت شرط المخزن المؤقت الشرطي == null تشير بوضوح إلى أن null قيمة غير مقبولة لهذا المتغير. ومع ذلك، وإذا كان من قيمة NetEventSource.IsEnabled التعبير هو إلى true و العازلة " - لاغية ، وعندما تقييم buffer.length التعبير، من خلال استثناء من أحد لNullReferenceException من نوع الإرادة ولدت من BE. كما نرى ، لن نصل إلىالعازلة == تحقق فارغة في هذه الحالة.تحذيرات PVS-Studio الصادرة لطرق أخرى مع النمط:- V3095 تم استخدام الكائن 'buffer' قبل أن يتم التحقق منه ضد قيمة خالية. خطوط التحقق: 49 ، 51. HttpResponseStream.cs 49
- V3095 تم استخدام الكائن 'buffer' قبل أن يتم التحقق منه ضد قيمة خالية. خطوط التحقق: 74 ، 75. HttpResponseStream.cs 74
المشكلة 56 كانهناك مقتطف شفرة مشابه في مشروع System.Transactions.Local . internal override void EnterState(InternalTransaction tx) { if (tx._outcomeSource._isoLevel == IsolationLevel.Snapshot) { throw TransactionException.CreateInvalidOperationException( TraceSourceType.TraceSourceLtm, SR.CannotPromoteSnapshot, null, tx == null ? Guid.Empty : tx.DistributedTxId); } .... }
تحذير PVS-Studio: V3095 تم استخدام كائن 'tx' قبل التحقق من صحته. أسطر الفحص: 3282 ، 3285. TransactionState.cs 3282تحت شرط معين ، يريد المؤلف طرح استثناء من نوع InvalidOperationException . عند استدعاء طريقة إنشاء كائن استثناء ، يستخدم مؤلفو التعليمات البرمجية المعلمة tx ، والتحقق من أنها خالية لتجنب استثناء من نوع NullReferenceException عند تقييم تعبير tx.DistributedTxId . من المفارقات أن الفحص لن يكون مفيدًا ، كما هو الحال عند تقييم حالة عبارة if ، يتم الوصول إلى حقول المثيل عبر متغير tx -tx._outcomeSource._isoLevel .إصدار 57رمز من مشروع System.Runtime.Caching . internal void SetLimit(int cacheMemoryLimitMegabytes) { long cacheMemoryLimit = cacheMemoryLimitMegabytes; cacheMemoryLimit = cacheMemoryLimit << MEGABYTE_SHIFT; _memoryLimit = 0;
تحذير PVS-Studio: تعبير V3022 'cacheMemoryLimit! = 0 && _memoryLimit! = 0' خطأ دائمًا. CacheMemoryMonitor.cs 250إذا نظرت عن كثب إلى الشفرة ، ستلاحظ أن أحد التعبيرات - cacheMemoryLimit! = 0 && _memoryLimit! = 0 ستكون دائمًا خاطئة . نظرًا لأن _memoryLimit له القيمة 0 (تم تعيينها قبل العبارة if ) ، فإن المعامل الصحيح لعامل التشغيل && خطأ . لذلك ، نتيجة التعبير بأكمله خاطئة .المشكلة 58أذكر جزءًا من التعليمات البرمجية المشبوهة من مشروع System.Diagnostics.TraceSource أدناه. public override object Pop() { StackNode n = _stack.Value; if (n == null) { base.Pop(); } _stack.Value = n.Prev; return n.Value; }
تحذير PVS-Studio: V3125 تم استخدام الكائن 'n' بعد أن تم التحقق منه ضد قيمة خالية. خطوط التحقق: 115 ، 111. CorrelationManager.cs 115في الواقع ، إنها حالة مثيرة للاهتمام. بسبب الاختيار n == null ، أفترض ، أن null هي قيمة متوقعة لهذا المتغير المحلي. إذا كان الأمر كذلك ، فسيتم إنشاء استثناء لنوع NullReferenceException عند الوصول إلى خاصية المثيل - n.Prev . في هذه الحالة إذا ن لا يمكن أبدا أن يكون لاغيا ، base.Pop () لن يتم استدعاؤها.العدد 59جزء التعليمات البرمجية مثيرة للاهتمام من System.Drawing.Primitivesالمشروع. مرة أخرى ، أقترح أن تحاول إيجاد المشكلة بنفسك. إليك الكود: public static string ToHtml(Color c) { string colorString = string.Empty; if (c.IsEmpty) return colorString; if (ColorUtil.IsSystemColor(c)) { switch (c.ToKnownColor()) { case KnownColor.ActiveBorder: colorString = "activeborder"; break; case KnownColor.GradientActiveCaption: case KnownColor.ActiveCaption: colorString = "activecaption"; break; case KnownColor.AppWorkspace: colorString = "appworkspace"; break; case KnownColor.Desktop: colorString = "background"; break; case KnownColor.Control: colorString = "buttonface"; break; case KnownColor.ControlLight: colorString = "buttonface"; break; case KnownColor.ControlDark: colorString = "buttonshadow"; break; case KnownColor.ControlText: colorString = "buttontext"; break; case KnownColor.ActiveCaptionText: colorString = "captiontext"; break; case KnownColor.GrayText: colorString = "graytext"; break; case KnownColor.HotTrack: case KnownColor.Highlight: colorString = "highlight"; break; case KnownColor.MenuHighlight: case KnownColor.HighlightText: colorString = "highlighttext"; break; case KnownColor.InactiveBorder: colorString = "inactiveborder"; break; case KnownColor.GradientInactiveCaption: case KnownColor.InactiveCaption: colorString = "inactivecaption"; break; case KnownColor.InactiveCaptionText: colorString = "inactivecaptiontext"; break; case KnownColor.Info: colorString = "infobackground"; break; case KnownColor.InfoText: colorString = "infotext"; break; case KnownColor.MenuBar: case KnownColor.Menu: colorString = "menu"; break; case KnownColor.MenuText: colorString = "menutext"; break; case KnownColor.ScrollBar: colorString = "scrollbar"; break; case KnownColor.ControlDarkDark: colorString = "threeddarkshadow"; break; case KnownColor.ControlLightLight: colorString = "buttonhighlight"; break; case KnownColor.Window: colorString = "window"; break; case KnownColor.WindowFrame: colorString = "windowframe"; break; case KnownColor.WindowText: colorString = "windowtext"; break; } } else if (c.IsNamedColor) { if (c == Color.LightGray) {
حسنًا ، حسنًا ، فقط أمزح ... أم أنك ما زلت تجد شيئًا؟ على أي حال ، دعنا نخفّض الشفرة لتوضيح المشكلة بوضوح.إليك نسخة الرمز القصير: switch (c.ToKnownColor()) { .... case KnownColor.Control: colorString = "buttonface"; break; case KnownColor.ControlLight: colorString = "buttonface"; break; .... }
تحذير PVS-Studio: V3139 يقوم اثنان أو أكثر من فروع الحالات بتنفيذ نفس الإجراءات. ColorTranslator.cs 302لا أستطيع أن أقول بالتأكيد ، لكنني أعتقد أنه خطأ. في حالات أخرى ، عندما أراد أحد المطورين إرجاع القيمة نفسها لعدة عددين ، استخدم العديد من الحالات (الحالات) ، يتبع كل منهم الآخر. وأعتقد أنه من السهل أن نخطئ في لصق النسخ هنا.دعونا حفر أعمق قليلا. للحصول على قيمة "buttonface" من طريقة ToHtml التي تم تحليلها ، يمكنك تمرير إحدى القيم التالية إليها (متوقع):- SystemColors.Control ؛
- SystemColors.ControlLight .
إذا تحققنا من قيم ARGB لكل من هذه الألوان ، فسنرى ما يلي:- SystemColors.Control - (255 ، 240 ، 240 ، 240) ؛
- SystemColors.ControlLight - (255 ، 227 ، 227 ، 227) .
إذا قمنا باستدعاء طريقة التحويل العكسي FromHtml على القيمة المستلمة ( "buttonface" ) ، فسنحصل على التحكم في الألوان (255 ، 240 ، 240 ، 240) . <BR> لنا الحصول على العلبة ControlLight لون من FromHtml ؟ نعم.
تحتوي هذه الطريقة على جدول الألوان ، وهو أساس تكوين الألوان (في هذه الحالة). يحتوي مُهيئ الجدول على السطر التالي: s_htmlSysColorTable["threedhighlight"] = ColorUtil.FromKnownColor(KnownColor.ControlLight);
وفقا لذلك، FromHtml إرجاع ControlLight (255، 227، 227، 227) لون لل «ThreeDHighlight» من القيمة. أعتقد أن هذا بالضبط ما كان يجب استخدامه في حالة KnownColor.ControlLight .العدد 60سنقوم بفحص بعض التحذيرات المثيرة للاهتمام من مشروع System.Text.RegularExpressions . internal virtual string TextposDescription() { var sb = new StringBuilder(); int remaining; sb.Append(runtextpos); if (sb.Length < 8) sb.Append(' ', 8 - sb.Length); if (runtextpos > runtextbeg) sb.Append(RegexCharClass.CharDescription(runtext[runtextpos - 1])); else sb.Append('^'); sb.Append('>'); remaining = runtextend - runtextpos; for (int i = runtextpos; i < runtextend; i++) { sb.Append(RegexCharClass.CharDescription(runtext[i])); } if (sb.Length >= 64) { sb.Length = 61; sb.Append("..."); } else { sb.Append('$'); } return sb.ToString(); }
تحذير PVS-Studio: V3137 يتم تعيين المتغير "المتبقي" ولكن لا يتم استخدامه بحلول نهاية الوظيفة. RegexRunner.cs 612تتم كتابة القيمة في المتغير المحلي المتبقي ، ولكنها لم تعد مستخدمة في الطريقة. ربما تمت إزالة بعض التعليمات البرمجية باستخدامه ، ولكن تم تغيير المتغير نفسه. أو يوجد خطأ حاسم ويجب استخدام هذا المتغير بطريقة أو بأخرى.العدد 61 public void AddRange(char first, char last) { _rangelist.Add(new SingleRange(first, last)); if (_canonical && _rangelist.Count > 0 && first <= _rangelist[_rangelist.Count - 1].Last) { _canonical = false; } }
تحذير PVS-Studio: V3063 جزء من التعبير الشرطي يكون دائمًا صحيحًا إذا تم تقييمه: _rangelist.Count> 0. RegexCharClass.cs 523لاحظ المحلل بحق ، أن جزءًا من تعبير _rangelist.Count> 0 سيكون دائمًا صحيحًا ، إذا تم تنفيذ هذا الرمز. حتى لو كانت هذه القائمة (التي يشير إليها _rangelist في) ، فارغة ، فبعد إضافة العنصر _rangelist.Add (....) لن تكون هي نفسها.العدد 62لنظرة دعونا AT تحذيرات V3128 حكم التشخيص في المشاريع System.Drawing.Common و System.Transactions.Local . private class ArrayEnumerator : IEnumerator { private object[] _array; private object _item; private int _index; private int _startIndex; private int _endIndex; public ArrayEnumerator(object[] array, int startIndex, int count) { _array = array; _startIndex = startIndex; _endIndex = _index + count; _index = _startIndex; } .... }
تحذير PVS-Studio: V3128 يتم استخدام الحقل "_index" قبل أن يتم تهيئته في المنشئ. PrinterSettings.Windows.cs 1679عند تهيئة حقل _endIndex ، يتم استخدام حقل _index آخر ، والذي يحتوي على قيمة افتراضية قياسية (int) ، (أي 0 ) في لحظة استخدامه. من و _index المجال هو تهيئة أدناه. في حال لم يكن هذا خطأ - يجب حذف المتغير _index في هذا التعبير حتى لا يكون مربكًا.العدد 63 internal class TransactionTable { .... private int _timerInterval; .... internal TransactionTable() {
تحذير PVS-Studio: V3128 يتم استخدام الحقل "_timerInterval" قبل أن يتم تهيئته في المُنشئ. TransactionTable.cs 151الحالة مشابهة للحالة أعلاه. أولاً ، يتم استخدام قيمة الحقل _timerInterval (رغم أنه لا يزال الافتراضي (int) ) لتهيئة _timer. بعد ذلك فقط ، سيتم تهيئة الحقل _timerInterval نفسه.العدد 64تم إصدار التحذيرات التالية من قبل قاعدة التشخيص ، والتي لا تزال قيد التطوير. لا توجد وثائق أو رسالة نهائية ، لكننا وجدنا بالفعل شظايا مثيرة للاهتمام بمساعدتها. مرة أخرى ، تبدو هذه الأجزاء مثل لصق النسخ ، لذلك سننظر في جزء واحد فقط من الشفرة. private bool ProcessNotifyConnection(....) { .... WeakReference reference = (WeakReference)( LdapConnection.s_handleTable[referralFromConnection]); if ( reference != null && reference.IsAlive && null != ((LdapConnection)reference.Target)._ldapHandle) { .... } .... }
تحذير PVS-Studio (كعب): VXXXX TODO_MESSAGE. LdapSessionOptions.cs 974الخدعة هي أنه بعد التحقق من المرجع. على سبيل المثال ، قد يتم تجميع البيانات المهملة وسيتم تجميع الكائن الذي يشير إليه WeakReference . في هذه الحالة ، سيعود الهدف القيمة الخالية . نتيجة لذلك ، عند الوصول إلى حقل المثيل _ldapHandle ، سيحدث استثناء لنوع NullReferenceException . مايكروسوفت نفسها تحذر من هذا الفخ مع الاختيار IsAlive. اقتباس من docs.microsoft.com - " خاصية WeakReference.IsAlive ":Because an object could potentially be reclaimed for garbage collection immediately after the IsAlive property returns true, using this property is not recommended unless you are testing only for a false return value.Summary on Analysis
هل كل هذه الأخطاء وأماكن مثيرة للاهتمام ، وجدت خلال التحليل؟ بالطبع لا! عند الاطلاع على نتائج التحليل ، كنت أتحقق من التحذيرات بدقة. مع ازدياد عددهم واتضح أنه كان هناك عدد كافٍ منهم لمقال ، كنت أتصفح النتائج ، وأحاول فقط تحديد ما بدا لي الأكثر إثارة للاهتمام. عندما وصلت إلى آخرها (أكبر سجلات) ، كنت فقط قادراً على إلقاء نظرة على التحذيرات حتى اصطدم المشهد بشيء غير عادي. لذلك إذا كنت تتجول ، أنا متأكد من أنه يمكنك العثور على أماكن أكثر إثارة للاهتمام.مثال ل، وأنا من تجاهلها من قبل تقريبا كل V3022 و V3063 التحذيرات. إذا جاز التعبير ، إذا صادفت مثل هذا الرمز: String str = null; if (str == null) ....
أود أن أغفل ذلك ، حيث كان هناك العديد من الأماكن الأخرى المثيرة للاهتمام التي أردت وصفها. كانت هناك تحذيرات على القفل غير الآمن باستخدام بيان القفل مع القفل بهذا وهكذا - V3090 ؛ مكالمات الأحداث غير الآمنة - V3083 ؛ الكائنات، أي نوع من أنواع المصرية تنفذ في IDisposable ، ولكن بالنسبة للالذي وتخلص / لإغلاق لا يسمى - V3072 والمعلنين تحديد التشخيص مماثلة وأكثر من ذلك بكثير.كما أنني لم ألاحظ المشاكل المكتوبة في الاختبارات. على الأقل ، حاولت ، ولكن قد يستغرق الأمر بطريق الخطأ بعضًا. باستثناء بضعة أماكن وجدت أنها مثيرة للاهتمام بما يكفي للفت الانتباه إليها. لكن يمكن أن تحتوي شفرة الاختبار أيضًا على أخطاء ، بسببها ستعمل الاختبارات بشكل غير صحيح.بشكل عام ، لا يزال هناك الكثير من الأشياء التي يجب التحقيق فيها - لكنني لم أكن أنوي تحديد كافة المشكلات التي تم العثور عليها .بدا أن جودة الكود غير متساوية بالنسبة لي. كانت بعض المشروعات نظيفة تمامًا ، والبعض الآخر يحتوي على أماكن مشبوهة. ربما نتوقع مشاريع نظيفة ، خاصة عندما يتعلق الأمر بفئات المكتبات الأكثر استخدامًا.خلاصة القول ، يمكننا القول أن الشفرة ذات جودة عالية ، حيث كان حجمها كبيرًا. ولكن ، كما تشير هذه المقالة ، كانت هناك بعض الزوايا المظلمة.بالمناسبة ، مشروع بهذا الحجم هو أيضًا اختبار جيد للمحلل. تمكنت من العثور على عدد من التحذيرات الخاطئة / الغريبة التي اخترتها للدراسة والتصحيح. كنتيجة للتحليل ، تمكنت من العثور على النقاط ، حيث يتعين علينا العمل على PVS-Studio نفسه.استنتاج
إذا وصلت إلى هذا المكان من خلال قراءة المقال كاملاً - دعني أصافح يدك! آمل أن أكون قادرًا على إظهار الأخطاء المثيرة لك وإظهار فائدة التحليل الثابت. إذا كنت قد تعلمت شيئًا جديدًا لنفسك ، فسوف يتيح لك كتابة رمز أفضل - سأكون سعيدًا بشكل مضاعف.على أي حال ، لن يضر بعض المساعدة من التحليل الثابت ، لذا اقترح عليك تجربة PVS-Studio في مشروعك ومعرفة الأماكن المثيرة للاهتمام التي يمكن العثور عليها باستخدامه. إذا كان لديك أي أسئلة أو كنت ترغب فقط في مشاركة شظايا مثيرة للاهتمام - لا تتردد في الكتابة على support@viva64.com . :)
مع أطيب التحيات!PS لمطوري مكتبات .NET Core
شكرا جزيلا على ما تفعله! عمل جيد! نأمل أن تساعدك هذه المقالة في جعل الكود أفضل قليلاً. تذكر ، أنني لم أكتب جميع الأماكن المشبوهة وكنت أفضل مراجعة المشروع نفسك باستخدام محلل. وبهذه الطريقة ، ستتمكن من التحقيق في جميع التحذيرات بالتفاصيل. علاوة على ذلك ، سيكون التعامل معها أكثر ملاءمة ، بدلاً من التعامل مع سجل نصي بسيط / قائمة بالأخطاء ( كتبت عن ذلك بمزيد من التفاصيل هنا ).