التحقق من التعليمات البرمجية المصدر لمكتبات .NET Core بواسطة محلل ثابت PVS-Studio

صورة 19

تعد مكتبات .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.

عادةً ما أستخدم المكوّن الإضافي PVS-Studio لبرنامج Visual Studio لاختبار مشاريع C # (الإصدارات 2010-2019 مدعومة) ، نظرًا لأن هذا ربما يكون الطريقة الأسهل والأكثر ملاءمة للتحليل: فتح حل ، وبدء التحليل ، والعمل مع قائمة التحذيرات. مع CoreFX ، أصبحت الأمور أكثر تعقيدًا.

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

ومع ذلك ، لم تكن هناك مشاكل في التحليل ، لأن مجموعة توزيع PVS-Studio تتضمن إصدار سطر أوامر من محلل لمشاريع MSBuild (وفي الواقع ، .sln). كل ما كان مطلوبًا مني هو كتابة برنامج نصي صغير من شأنه تشغيل "PVS-Studio_Cmd.exe" لكل .sln في دليل CoreFX ووضع نتائج التحليل في دليل منفصل (يشار إليه بعلامة بدء تشغيل المحلل).

فويلا! - عند الخروج لدي مجموعة من السجلات ، والتي لديها الكثير من الأشياء المثيرة للاهتمام. إذا رغبت في ذلك ، يمكن دمج السجلات باستخدام الأداة المساعدة PlogConverter ، والتي تأتي مع مجموعة التوزيع. ولكن كان الأمر أكثر ملاءمة بالنسبة لي للعمل مع سجلات فردية ، لذلك لم أبدأ في الجمع بينهما.

عند وصف بعض الأخطاء ، أشير إلى الوثائق من docs.microsoft.com وإلى حزم NuGet المتاحة للتنزيل من nuget.org. أعترف أن الكود الموضح في الوثائق / الموجود في الحزم قد يختلف قليلاً عن الكود الذي تم تحليله. ومع ذلك ، سيكون غريباً للغاية إذا لم تتضمن الوثائق ، على سبيل المثال ، وصفًا للاستثناءات التي تم إنشاؤها لعدد من بيانات الإدخال ، وستظهر في الإصدار الجديد من الحزمة - توافق على أن هذه ستكون مفاجأة مشكوك فيها. يُظهر إعادة إنتاج الأخطاء في الحزم من NuGet على نفس بيانات الإدخال التي تم استخدامها لتصحيح أخطاء المكتبات أن المشكلة ليست جديدة ، والأهم من ذلك ، أنه يمكن "لمسها" دون إنشاء المشروع من المصدر.

وبالتالي ، بافتراض إمكانية حدوث بعض تزامن الخطأ في التعليمات البرمجية النظرية ، أجد أنه يجوز الإشارة إلى وصف الطرق المقابلة على docs.microsoft.com وإلى إعادة إنتاج المشاكل على الحزم من nuget.org.

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

مشاريع أخرى مجربة


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

قام زميلي في السابق باختبار مكتبات .NET Core في عام 2015. يمكن العثور على نتائج التحليل السابق في المقالة المطابقة: " التحقق من السنة الجديدة من .NET Core Libraries (CoreFX) ."

الأخطاء المكتشفة والأماكن المشبوهة والمثيرة للاهتمام


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

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

العدد 1

abstract 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 . ومع ذلك ، أعلى قليلاً ، في الحالة السابقة ، هناك dereference غير المشروط من ارتباط السياق - context.ContextType . نتيجة لذلك ، إذا كانت قيمة السياق خالية ، فسيتم طرح استثناء من النوع NullReferenceException بدلاً من InvalidOperationExcetion المتوقع.

دعنا نحاول إعادة إنتاج المشكلة. قم بتوصيل المكتبة المناسبة ( System.DirectoryServices.AccountManagement ) بالمشروع وتنفيذ التعليمات البرمجية التالية:

 GroupPrincipal groupPrincipal = new GroupPrincipal(new PrincipalContext(ContextType.Machine)); groupPrincipal.Save(null); 

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

الصورة 1


للمتعة ، يمكنك محاولة تنزيل الحزمة المناسبة من NuGet ومحاولة تكرار المشكلة بنفس الطريقة. لقد قمت بتثبيت إصدار الحزمة 4.5.0 وحصلت على النتيجة المتوقعة.

الصورة 2


العدد 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! = التحقق من Null ، أي المطورين تأمين أنفسهم أن الأسلوب إرجاع فارغة . فيما يلي أعلاه ، يمكنك مراقبة العديد من عمليات الوصول إلى هذا المرجع المحتمل أنه لاغٍ - propertyNames.Length و propertyNames [i] . النتيجة يمكن التنبؤ بها تمامًا - سيحدث استثناء من النوع NullReferenceExcepption إذا تم تمرير مرجع فارغ إلى الطريقة.

يالها من مصادفة أن RefreshCache هي طريقة عامة في فئة عامة. محاولة لتكرار المشكلة؟ للقيام بذلك ، قم بالاتصال بالمشروع بالمكتبة المرغوبة - System.DirectoryServices - واكتب الكود كما يلي:

 DirectoryEntry de = new DirectoryEntry(); de.RefreshCache(null); 

قم بتشغيل التعليمات البرمجية للتنفيذ وانظر الصورة المتوقعة.

الصورة 3


للمتعة ، يمكنك محاولة إعادة إنشاء المشكلة في إصدار إصدار حزمة NuGet. نقوم بتوصيل حزمة System.DirectoryServices بمشروع NuGet (استخدمت الإصدار 4.5.0) وقم بتشغيل التعليمات البرمجية المألوفة بالفعل للتنفيذ. النتيجة أقل.

صورة 4


العدد 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.

هل تعتقد أن النص "False" سيتم عرضه في وحدة التحكم؟ بالطبع لا ، سيكون ذلك سهلاً للغاية. :) ننفذ الكود وننظر إلى النتيجة.

الصورة 5

كان هذا هو الاستنتاج عند تنفيذ التعليمات البرمجية أعلاه باستخدام حزمة نظام NuGet System.Drawing.Common الإصدار 4.5.1. نقوم بتشغيل نفس الكود باستخدام إصدار تصحيح المكتبة ونرى ما يلي:

صورة 6


الآن دعنا نلقي نظرة على الكود المصدري - تطبيق طريقة 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

أثناء استكشاف هذه المكتبة ، فكر في مكان آخر مثير للاهتمام - طريقة Icon.Save . قبل الدراسة ، انظر وصف الطريقة.

لا يوجد وصف للطريقة:

الصورة 7

ننتقل إلى docs.microsoft.com - " Icon.Save (دفق) الأسلوب ". ومع ذلك ، لا توجد قيود على قيم الإدخال ولا توجد معلومات حول الاستثناءات التي تم إنشاؤها.

الآن دعنا ننتقل إلى رمز البحث.

 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! = Null راضٍ.

دعونا نلقي نظرة على أبسط المنشئ العام:

 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 بالرمز الفعلي ثم استدعاء الأسلوب Save ، ويمر بلا قيمة كحجة ، وهو ما سنفعله. قد يبدو الرمز ، على سبيل المثال ، كما يلي:

 Icon icon = new Icon(@"D:\document.ico"); icon.Save(null); 

نتيجة التنفيذ متوقعة.

الصورة 8

العدد 6

نواصل الاستعراض والانتقال إلى مكتبة System.Management . حاول العثور على 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.CSharp .

 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 == فارغة .

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

صورة 9

بعد ذلك ، سيتم إعادة تشغيل 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 (files [0]) &&! IsInfoFile (files [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 Warning : 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 Warning : V3138 تحتوي السلسلة الحرفية على تعبير محتمل محتمل. النظر في التفتيش: الخوارزمية. AuthenticationHelper.Digest.cs 58

الموقف مشابه للحالة الموضحة أعلاه ، يتم تخطي الرمز $ مرة أخرى - ينتقل الخط الخطأ إلى أسلوب Error .

العدد 12

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

 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 . نتيجة لذلك ، ستظل _stream مضبوطة على الدفق ، و _streamSet إلى صواب .

العدد 13

مكان مثير للاهتمام من مكتبة System.Linq.Expressions ، حيث أصدر المحلل فورًا تحذيرين. في هذه الحالة ، تعد ميزة أكثر من مجرد خطأ ، ولكن مع ذلك ، فإن الطريقة مثيرة للاهتمام للغاية ...

 // throws NRE when o is null protected static void NullCheck(object o) { if (o == null) { o.GetType(); } } 

تحذيرات PVS-Studio :

  • V3010 يجب استخدام قيمة الإرجاع للدالة "GetType". التعليمات. 36
  • V3080 dereference ممكن. النظر في تفتيش "س". التعليمات. 36

ربما لا يوجد شيء للتعليق عليه.

صورة 20

العدد 14

لنلقِ نظرة على حالة أخرى سنعمل بها "من الخارج". أولاً ، سنقوم بكتابة الكود ، وتحديد المشاكل ، ثم النظر إلى الداخل. للدراسة ، خذ مكتبة System.Configuration.ConfigurationManager وحزمة NuGet التي تحمل الاسم نفسه. لقد استخدمت حزمة الإصدار 4.5.0. سوف نعمل مع فئة System.Configuration.CommaDelimitedStringCollection .

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

 CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); Console.WriteLine(collection.ToString().Length); 

فقط في الحالة ، انظر إلى وصف طريقة ToString :

صورة 11

لا شيء غير عادي - يتم إرجاع تمثيل سلسلة الكائن ببساطة. فقط في حالة ، أنا أنظر أيضًا إلى docs.microsoft.com - " CommaDelimitedStringCollection.ToString Method ". يبدو أن لا شيء خاص أيضا.

حسنًا ، قم بتشغيل رمز التنفيذ ، الثاني ...

صورة 12

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

 CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); collection.Add(String.Empty); Console.WriteLine(collection.ToString().Length); 

نطلقه ونرى ...

صورة 13

ماذا ، مرة أخرى؟ حسنًا ، دعنا أخيرًا نلقي نظرة على تنفيذ طريقة ToString لفئة CommaDelimitedStringCollection . الكود مقدم أدناه:

 public override string ToString() { if (Count <= 0) return null; StringBuilder sb = new StringBuilder(); foreach (string str in this) { ThrowIfContainsDelimiter(str); // .... sb.Append(str.Trim()); sb.Append(','); } if (sb.Length > 0) sb.Length = sb.Length - 1; return sb.Length == 0 ? null : sb.ToString(); } 

تحذيرات PVS-Studio :

  • V3108 لا يوصى بإرجاع "خالية" من طريقة "ToSting ()". StringAttributeCollection.cs 57
  • V3108 لا يوصى بإرجاع "خالية" من طريقة "ToSting ()". StringAttributeCollection.cs 71

هنا نرى 2 الأماكن التي يمكن أن يعود فيها تطبيق ToString الحالي. استذكر ما تنصح به Microsoft عند تطبيق الأسلوب ToString ، والذي ننتقل من جديد إلى docs.microsoft.com - " Object.ToString Method ":

ملاحظات إلى Inheritors .... يجب أن تتبع Overrides للأسلوب ToString () هذه الإرشادات:
  • ....
  • يجب ألا يؤدي تجاوز ToString () إلى إرجاع سلسلة فارغة أو فارغة .
  • ....

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

الحالة الأولى. عدد - خاصية الفئة الأساسية StringCollection . نظرًا لأنه لم تتم إضافة أي عناصر ، عدد == 0 ، يتم استيفاء عدد الشرط <= 0 ، يتم إرجاع قيمة خالية .

في الحالة الثانية ، أضفنا عنصرًا باستخدام طريقة المثيل CommaDelimitedStringCollection.Add لهذا.

 public new void Add(string value) { ThrowIfReadOnly(); ThrowIfContainsDelimiter(value); _modified = true; base.Add(value.Trim()); } 

يتم التحقق بنجاح من أسلوب ThrowIf ... وتتم إضافة العنصر إلى المجموعة الأساسية. وفقًا لذلك ، تصبح قيمة Count مساوية 1. نعود الآن إلى أسلوب ToString . قيمة التعبير Count <= 0 غير صحيحة ، وبالتالي ، لا يتم إنهاء الطريقة ويستمر تنفيذ التعليمات البرمجية. يبدأ اجتياز المجموعة الداخلية ، وتتم إضافة عنصرين إلى StringBuilder - سلسلة فارغة وفاصلة. نتيجة لذلك ، اتضح أن sb يحتوي فقط على فاصلة ، وقيمة الخاصية Length ، على التوالي ، تساوي واحدة. قيمة التعبير sb.Length> 0 صحيحة ، يتم تنفيذ الطرح والكتابة إلى sb.Length ، والآن قيمة sb.Length هي 0. وهذا يؤدي إلى حقيقة أن الطريقة تُرجع خالية .

العدد 15

بشكل غير متوقع ، أردت استخدام فئة System.Configuration.ConfigurationProperty . خذ المنشئ مع معظم المعلمات:

 public ConfigurationProperty( string name, Type type, object defaultValue, TypeConverter typeConverter, ConfigurationValidatorBase validator, ConfigurationPropertyOptions options, string description); 

لنرى وصف المعلمة الأخيرة:

 // description: // The description of the configuration entity. 

وصف المنشئ على 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 : V3117 منشئ المعلمة 'configSourceStreamVersion' غير المستخدمة. SectionXmlInfo.cs 16

هناك خاصية مقابلة ، على الرغم من أنها تبدو غريبة بعض الشيء:

 internal object ConfigSourceStreamVersion { set { } } 

بشكل عام ، رمز يبدو مشبوهة. ربما تم ترك المعلمة / الخاصية للتوافق ، ولكن هذه مجرد تخميناتي.

العدد 17 ،

دعونا نرى ما هو مثير للاهتمام تم العثور عليه في رمز مكتبة System.Runtime.WindowsRuntime.UI.Xaml وحزمة NuGet التي تحمل الاسم نفسه.
 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 سيعود لاغياً ، وبالتالي ، ستُرجع null 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, /* 9 (.) */ CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, /* A (.) */ CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, /* B (.) */ CharType.None, /* C (.) */ CharType.None, /* D (.) */ CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace, /* E (.) */ CharType.None, .... }; 

تحذيرات PVS-Studio :

  • V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' المشغل. XmlUTF8TextReader.cs 56
  • V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' المشغل. XmlUTF8TextReader.cs 58
  • V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' المشغل. 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) الطريقة ": إرجاع: صحيح إذا كان يمكن إضافة السلسلة؛ خلاف ذلك ، خطأ.

الآن دعنا ننظر إلى نص الأسلوب:

 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) { // If the key is already set, then something is wrong throw System.Runtime .Serialization .DiagnosticUtility .ExceptionUtility .ThrowHelperError( new InvalidOperationException( SR.XmlKeyAlreadyExists)); } key = Add(value.Value); keys[value.Key] = (key + 1); return true; } key = Add(value.Value); keys = AddKeys(value.Dictionary, value.Key + 1); keys[value.Key] = (key + 1); return true; } 

تحذير 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

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

العدد 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;) { // Increment counter -- up to 2^32 * sizeof(Hash) Helpers.ConvertIntToByteArray(counter++, rgbCounter); hasher.TransformBlock(rgbSeed, 0, rgbSeed.Length, rgbSeed, 0); hasher.TransformFinalBlock(rgbCounter, 0, 4); byte[] hash = hasher.Hash; hasher.Initialize(); Buffer.BlockCopy(hash, 0, rgbT, ib, Math.Min(rgbT.Length - ib, hash.Length)); ib += hasher.Hash.Length; } return rgbT; } } 

تحذير PVS-Studio : V3080 dereference null. النظر في تفتيش "هاش". PKCS1MaskGenerationMethod.cs 37

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

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

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

 public class PKCS1MaskGenerationMethod : .... // <= 1 { .... public PKCS1MaskGenerationMethod() // <= 2 { _hashNameValue = DefaultHash; } .... public override byte[] GenerateMask(byte[] rgbSeed, int cbReturn) // <= 3 { using (HashAlgorithm hasher = (HashAlgorithm)CryptoConfig.CreateFromName(_hashNameValue)) // <= 4 { byte[] rgbCounter = new byte[4]; byte[] rgbT = new byte[cbReturn]; // <= 5 uint counter = 0; for (int ib = 0; ib < rgbT.Length;) // <= 6 { .... Helpers.ConvertIntToByteArray(counter++, rgbCounter); // <= 7 hasher.TransformBlock(rgbSeed, 0, rgbSeed.Length, rgbSeed, 0); .... } .... } } } 

لننظر في النقاط الرئيسية بمزيد من التفاصيل:

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); 

نقوم بتوصيل مكتبة التصحيح بالمشروع وتشغيله والحصول على النتيجة المتوقعة:

الصورة 10


من أجل الاهتمام ، حاولت تنفيذ نفس الكود على إصدار حزمة NuGet 4.3.1.

صورة 14


معلومات حول الاستثناءات التي تم إنشاؤها ، والقيود المفروضة على معلمات الإخراج في وصف الطريقة ، لم يتم توضيح ذلك على docs.microsoft.com إما - " PKCS1MaskGenerationMethod.GenerateMask (Byte []، Int32) Method ".

بالمناسبة ، أثناء كتابة المقال ووصف تسلسل تشغيل المشكلة ، وجدت طريقتين أخريين "لكسر" هذه الطريقة:

  • تمرير قيمة كبيرة جدًا كوسيطة إلى cbReturn ؛
  • تمر كما rgbSeed قيمة لاغية .

في الحالة الأولى ، حصلنا على استثناء من نوع OutOfMemoryException .

صورة 15

في الحالة الثانية ، نحصل على استثناء من النوع NullReferenceException عند تنفيذ تعبير rgbSeed.Length . في هذه الحالة ، من المهم أن يحتوي hasher على قيمة غير صفرية ، وإلا لن يصل مؤشر ترابط التنفيذ إلى 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); // <= return item; } public virtual AsymmetricSignatureFormatter CreateFormatter( AsymmetricAlgorithm key) { AsymmetricSignatureFormatter item = (AsymmetricSignatureFormatter) CryptoConfig.CreateFromName(FormatterAlgorithm); item.SetKey(key); // <= return item; } .... } 

تحذيرات 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); // NRE signature.CreateFormatter(null); // NRE 

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

العدد 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 A for loop ، باستخدام التعبير i <seq.Count كشرط خروج . تشير إلى أنهم يريدون تجاوز تسلسل seq . في الحلقة فقط ، يمكنهم الوصول إلى عناصر التسلسل التي لا تستخدم عداد ( seq [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

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

  • سيتم طرح استثناء من نوع InvalidCastException إذا فشل المدلى بها ؛
  • إذا نجح فريق العمل ، فإن nodeSeq له بالتأكيد قيمة أخرى غير القيمة null .

العدد 28

Met 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 : يجب ألا يؤدي تخطي 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 يبدو مريبًا إلى حد ما. المطابقة هي طريقة مثيل. اذا حكمنا من خلال التحقق من attr == null ، null هي قيمة صالحة (متوقعة) لهذا المتغير. لذلك ، إذا وصل مؤشر ترابط التنفيذ إلى المعامل الأيمن للمشغل || شريطة أن يكون attr خاليًا ، نحصل على استثناء من النوع NullReferenceException .

وفقًا لذلك ، تكون شروط الاستثناء كما يلي:

  1. attrnull . &&.
  2. !attribute.IsDefaultAttribute()false . && — false .
  3. || false , .
  4. attrnull , Match .

Issue 33

 private int ReadOldRowData( DataSet ds, ref DataTable table, ref int pos, XmlReader row) { .... if (table == null) { row.Skip(); // need to skip this element if we dont know about it, // before returning -1 return -1; } .... if (table == null) throw ExceptionBuilder.DiffgramMissingTable( XmlConvert.DecodeName(row.LocalName)); .... } 

تحذير PVS-Studio : V3021 هناك بيانان "if" مع تعبيرات شرطية متطابقة. تحتوي العبارة "if" الأولى على طريقة إرجاع. هذا يعني أن عبارة 'if' الثانية هي XMLDiffLoader.cs 301

لا معنى لها. هناك جملتان if يحتويان على نفس التعبير الشرطي - table == null . علاوة على ذلك ، تحتوي فروع المشغلين آنذاك على إجراءات مختلفة - في حالة واحدة ، يتم إنهاء الطريقة بقيمة -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; // nothing to remove. } .... 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! = Null تتحدث عن نفسها - يمكن أن يكون متغير _table خاليًا . على الأقل في هذه الحالة ، يتم إعادة التأمين. ومع ذلك، راجع أدناه إلى الميدان سبيل المثال من خلال _table بالفعل دون التحقق ل اغية - .fInitInProgress _table .

العدد 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 .

 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

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

العدد 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

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

  • إما أنني لست مستنيرًا بما فيه الكفاية ولم أفهم معنى هذه الازدواجية ؛
  • إما الخطأ نشر من خلال التعليمات البرمجية باستخدام طريقة نسخ لصق.

الكود نفسه:

 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 '! String.IsNullOrEmpty (quotePrefix) || quotePrefix! = "" "صحيح دائمًا. OdbcCommandBuilder.cs 338

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

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

من التعليمات البرمجية المذكورة أعلاه، ونحن نعلم أنه إذا كان المعامل الصحيح، وقيمة التعبير string.IsNullOrEmpty (quotePrefix) - إلى true ، وبالتالي، ليست واحدة من العبارات التالية:

  • 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-Studio : لا يتم استخدام معلمة مُنشئ V3117 "userOptions". DbConnectionPool.cs 26

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

العدد 42

هناك رمز مشبوه اجتمع في هذا المشروع 2 مرات. النمط هو نفسه.

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

العدد 44

ننتقل إلى مشروع System.ComponentModel.Composition ونرى بعض التحذيرات. تم إصدار تحذيرين فورًا للشفرة التالية:

 public static bool CanSpecialize(....) { .... object[] genericParameterConstraints = ....; GenericParameterAttributes[] genericParameterAttributes = ....; // if no constraints and attributes been specifed, anything can be created if ((genericParameterConstraints == null) && (genericParameterAttributes == null)) { return true; } if ((genericParameterConstraints != null) && (genericParameterConstraints.Length != partArity)) { return false; } if ((genericParameterAttributes != null) && (genericParameterAttributes.Length != partArity)) { return false; } for (int i = 0; i < partArity; i++) { if (!GenericServices.CanSpecialize( specialization[i], (genericParameterConstraints[i] as Type[]). CreateTypeSpecializations(specialization), genericParameterAttributes[i])) { return false; } } return true; } 

تحذيرات PVS-Studio :

  • V3125 تم استخدام عنصر "genericParameterConstrict" بعد أن تم التحقق منه ضد قيمة خالية. خطوط التحقق: 603 ، 589. GenericSpecializationPartCreationInfo.cs 603
  • V3125 تم استخدام عنصر "genericParameterAttributes" بعد أن تم التحقق منه مقابل خالية. خطوط التحقق: 604 ، 594. GenericSpecializationPartCreationInfo.cs 604

هناك genericParameterAttributes! = Null و genericParameterConstrict! = يتحقق Null في التعليمات البرمجية . لذلك ، القيم الخالية - الصالحة لهذه المتغيرات ، نأخذها في الاعتبار. إذا كان كلا المتغيرين فارغين ، فاخرج من الطريقة - لا توجد أسئلة. ولكن ماذا لو كان أي من المتغيرات باطلاً ، ولكن لم يتم الخروج من هذه الطريقة؟ إذا كانت هذه الحالة ممكنة ، ووصل التنفيذ إلى الحلقة ، فسنحصل على استثناء من النوع NullReferenceException .

العدد 45

دعنا ننظر إلى تحذير آخر مثير للاهتمام من نفس المشروع. وعلى الرغم من ذلك ، فلنفعل ذلك بطريقة مختلفة - أولاً نستخدم الفصل مرة أخرى ، ثم نلقي نظرة على الكود. سنقوم بتوصيل حزمة NuGet لآخر إصدار تجريبي متاح للمشروع (لقد قمت بتثبيت إصدار الحزمة 4.6.0-preview6.19303.8). لنكتب رمزًا بسيطًا ، على سبيل المثال ، هذا:

 LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(null); Console.WriteLine(eq); 

لم يتم التعليق على أسلوب المساواة ، في docs.microsoft.com لم أجد وصفًا لهذه الطريقة لـ .NET Core ، فقط لبرنامج .NET Framework. إذا نظرت إليها (" طريقة LazyMemberInfo.Equals (كائن) ") - لا شيء غير عادي مرئيًا - فهي تُرجع صحيحة أو خاطئة ، ولا توجد معلومات حول الاستثناءات التي تم إنشاؤها.

نقوم بتشغيل الكود للتنفيذ ونرى:

الصورة 16

يمكننا الانحراف قليلاً ، كتابة التعليمات البرمجية التالية وكذلك الحصول على استنتاج مثير للاهتمام:

 LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(typeof(String)); Console.WriteLine(eq); 

نتيجة تنفيذ التعليمات البرمجية.

صورة 17

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

 public override bool Equals(object obj) { LazyMemberInfo that = (LazyMemberInfo)obj; // Difefrent member types mean different members if (_memberType != that._memberType) { return false; } // if any of the lazy memebers create accessors in a delay-loaded fashion, // we simply compare the creators if ((_accessorsCreator != null) || (that._accessorsCreator != null)) { return object.Equals(_accessorsCreator, that._accessorsCreator); } // we are dealing with explicitly passed accessors in both cases if(_accessors == null || that._accessors == null) { throw new Exception(SR.Diagnostic_InternalExceptionMessage); } return _accessors.SequenceEqual(that._accessors); } 

تحذير 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: { // null parent should have hit the None case Debug.Assert(_parent != null); return _parent.GetRawValueAsString(_idx); } case JsonTokenType.String: return GetString(); case JsonTokenType.Comment: case JsonTokenType.EndArray: case JsonTokenType.EndObject: default: Debug.Fail($"No handler for {nameof(JsonTokenType)}.{TokenType}"); return string.Empty; } } 

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

تحذير 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; // Values will eventually converted to unicode string here } } return false; } 

تحذير 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")) // SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name() { // Delegate to current database response responseMessage = _PrepareDatabaseResponse(session); } .... } 

تحذير PVS-Studio : V3053 تعبير مفرط. فحص substrings "اسم" و "db_name". QueryEngine.cs 151

الحقيقة هي أنه في هذه الحالة ، يكون الجمع بين lowerBatchText.Contains ("name") و lowerBatchText.Contains ("db_name") عبارة عن تعبيرات فرعية زائدة عن الحاجة. في الواقع ، إذا كانت السلسلة التي يتم فحصها تحتوي على السلسلة الفرعية "db_name" ، فستتضمن أيضًا السلسلة الفرعية "الاسم" . إذا كانت السلسلة لا تحتوي على "اسم" ، فلن تحتوي على "db_name" أيضًا. اتضح أن التحقق من lowBatchText.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}"); // null response is not expected if (response == null) return PipelineInstruction.Abort; .... if (entry.Command == "OPTS utf8 on\r\n") .... .... } 

تحذير PVS-Studio : V3125 تم استخدام كائن "الإدخال" بعد التحقق من صحته. الاختيار خطوط: 270، 227. FtpControlStream.cs 270

في إعداد خطوط محرف تستخدم التعبير دخول .Command؟ و . الوصف استجابة؟ . بدلا من المشغل استخدم "؟" لعدم الحصول على استثناء من النوع NullReferenceException إذا كانت أي من المعلمات المقابلة خالية . في هذه الحالة ، تعمل هذه التقنية. علاوة على ذلك ، كما ترون من الكود ، يتم قطع القيمة الخالية المحتملة للاستجابة (الخروج من الطريقة إذا كانت الاستجابة == فارغة ) ، ولكن للإدخاللا شيء مثل هذا. ونتيجة لذلك، إذا كان دخول - اغية ، أدناه، والدعوة entry.Command (باستخدام بالفعل، بدلا من؟ ''. ') سوف بطرح استثناء.

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

صورة 23

هل عدت ثم دعنا نستمر. :)

العدد 53

الآن دعونا ننظر إلى شيء مثير للاهتمام من مشروع System.Collections.Immutable . هذه المرة ، دعنا نجرب قليلاً مع بنية System.Collections.Immutable.ImmutableArray <T> . نحن مهتمون بطرق 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); } 

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

 bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { var self = this; // <= 1 Array otherArray = other as Array; if (otherArray == null) // <= 2 { var theirs = other as IImmutableArray; if (theirs != null) // <= 3 { otherArray = theirs.Array; if (self.array == null && otherArray == null) { return true; } else if (self.array == null) // <= 4 { return false; } } IStructuralEquatable ours = self.array; // <= 5 return ours.Equals(otherArray, comparer); } 

النقطة الرئيسية 1 . self.array == this.array (بسبب الذات = هذا ). لذلك ، قبل استدعاء الطريقة ، من الضروري الوصول إلى الحالة this.array == فارغة .

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

النقطة الرئيسية 3 . هذه النقطة تنطوي على مسار أكثر تعقيدا. نحتاج بالتأكيد إلى الخروج من الجملة الثانية إذا كانت العبارة ( العبارة ذات التعبير الشرطي بها ! = خالية ). إذا لم يحدث هذا وبدأ تنفيذ الفرع في ذلك الوقت ، فنحن نضمن عدم الوصول إلى النقطة 5 التي نحتاجها ، شريطة self.array == null بسبب النقطة الرئيسية 4. لكي لا نذهب إلى if if من النقطة الرئيسية 3 ، يجب عليك تنفيذ واحدة من الشروط:

  • إلى قيمة الآخر كان باطلا .
  • بحيث لا يقوم النوع الفعلي الآخر بتطبيق واجهة IImmutableArray .

النقطة الرئيسية 5 . إذا وصلنا إلى هذه النقطة بقيمة self.array == فارغة ، فقد حققنا هدفنا ، وسيتم طرح استثناء من النوع NullReferenceException في الطريقة .

نحصل على مجموعات البيانات التالية التي ستقودنا إلى الهدف.

أولاً: هذا الصفيف باطل .

والثاني هو واحد مما يلي:

  • أخرى - لاغية .
  • الآخر من النوع 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); 

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

صورة 18

العدد 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); // <= } throw new ArgumentException(SR.ArrayLengthsNotEqual, nameof(other)); } 

تحذير 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 .

القيمة : أخرى - سلسلة جديدة [] {} ؛

النتيجة:

صورة 22

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

العدد 55

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

 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 مع المخزن المؤقت == خالية صراحة يلمح إلى أن فارغة قيمة غير صالحة لهذا المتغير. ومع ذلك، إذا كانت قيمة التعبير NetEventSource.IsEnabled - إلى true ، وفي نفس الوقت العازلة " - لاغية ، ورمي استثناء من نوع NullReferenceException عند حساب التعبير buffer.length . وفقا لذلك ، قبل التحقق من العازلة == فارغةفي هذه الحالة ، الأمر لا يصل حتى.

تحذيرات 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 . عند استدعاء أسلوب لإنشاء كائن استثناء باستخدام المعلمة تكساس ، وبالتالي يتم فحصها من أجل المساواة لاغية ، وليس لرمي استثناء من نوع NullReferenceException عند حساب التعبير tx.DistributedTxId . المفارقة هي أنه إذا كانت tx - null ، فلن يساعد هذا الاختيار ، لأنه عند حساب شروط العبارة ifيتم الوصول إلى حقول المثيل عبر متغير tx - tx._outcomeSource._isoLevel .

إصدار 57

رمز من مشروع System.Runtime.Caching .

 internal void SetLimit(int cacheMemoryLimitMegabytes) { long cacheMemoryLimit = cacheMemoryLimitMegabytes; cacheMemoryLimit = cacheMemoryLimit << MEGABYTE_SHIFT; _memoryLimit = 0; // never override what the user specifies as the limit; // only call AutoPrivateBytesLimit when the user does not specify one. if (cacheMemoryLimit == 0 && _memoryLimit == 0) { // Zero means we impose a limit _memoryLimit = EffectiveProcessMemoryLimit; } else if (cacheMemoryLimit != 0 && _memoryLimit != 0) { // Take the min of "cache memory limit" and // the host's "process memory limit". _memoryLimit = Math.Min(_memoryLimit, cacheMemoryLimit); } else if (cacheMemoryLimit != 0) { // _memoryLimit is 0, but "cache memory limit" // is non-zero, so use it as the limit _memoryLimit = cacheMemoryLimit; } .... } 

تحذير 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 . إذا كان n في هذه الحالة لا يمكن أبدًا أن يكون فارغًا ، فلن يتم تنفيذ call.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) { // special case due to mismatch between Html and enum spelling colorString = "LightGrey"; } else { colorString = c.Name; } } else { colorString = "#" + cRToString("X2", null) + cGToString("X2", null) + cBToString("X2", null); } return colorString; } 

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

فيما يلي نسخة مختصرة من الكود:

 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) .

إذا قمت بالاتصال بالقيمة المستلمة ( "buttonface" ) بالطريقة العكسية للتحويل - FromHtml ، فسنحصل على التحكم في الألوان (255 ، 240 ، 240 ، 240) . هل يمكنني الخروج من FromHtml اللون ControlLight ؟ نعم.في هذه الطريقة ، يوجد جدول ألوان يستند إلى (في حالة معينة) يتم الحصول على اللون. يحتوي مُهيئ الجدول على السطر التالي:

 s_htmlSysColorTable["threedhighlight"] = ColorUtil.FromKnownColor(KnownColor.ControlLight); 

وفقًا لذلك ، تقوم FromHtml بإرجاع اللون ControlLight (255 ، 227 ، 227 ، 227) لقيمة "ضوء الضوء" . أعتقد أنه كان يجب استخدام هذه القيمة في حالة 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

نظرة على تشغيل قواعد التشخيص 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() { // Create a timer that is initially disabled by specifing // an Infinite time to the first interval _timer = new Timer(new TimerCallback(ThreadTimer), null, Timeout.Infinite, _timerInterval); .... // Store the timer interval _timerInterval = 1 << TransactionTable.timerInternalExponent; .... } } 

تحذير 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

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


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

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

 String str = null; if (str == null) .... 

فاتني ذلك ، لأن هناك أماكن مثيرة للاهتمام أردت وصفها. كانت هناك مشغلات على قفل غير آمن باستخدام بيان قفل مع قفل على هذا ، الخ - V3090 ؛ مكالمات الأحداث غير الآمنة - V3083 ؛ الكائنات التي يتم تنفيذها أنواع IDisposable ، ولكن الذي لم تكن هناك مكالمة تخلص / إغلاق - V3072 والتشخيص مماثلة؛ وأكثر من ذلك بكثير.

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

بشكل عام ، لا يزال هناك شيء يجب تعلمه - لكنني لم أضع هدفًا لكتابة كل المشكلات الموجودة .

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

إذا تعميم - ربما ، يمكننا القول أن الكود عالي الجودة نظرًا لحقيقة أن مقدار الكود قد تم تحليله رغم ذلك. ولكن ، كما يلي من هذه المقالة ، كان هناك بعض الزوايا المظلمة.

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

استنتاج


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

صورة 24

بطريقة أو بأخرى ، لن تكون مساعدة التحليل الثابت غير ضرورية ، لذلك أقترح تجربة PVS-Studio في مشروعك ومعرفة الأماكن المثيرة للاهتمام التي يمكنك العثور عليها باستخدامها. إذا كانت لديك أي أسئلة أو ترغب فقط في مشاركة الأماكن المكتشفة المثيرة للاهتمام - فلا تتردد في الكتابة إلى support@viva64.com . :)

كل التوفيق!

ملاحظة: الاتصال بمطوري .NET Core Library


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



إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فالرجاء استخدام الرابط الخاص بترجمة: Sergey Vasiliev. التحقق من مصدر مصدر .NET Core Libraries بواسطة محلل ثابت PVS-Studio

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


All Articles