
اليوم ، نحن نختبر مشروعًا آخر عالي الجودة من Microsoft ، ما زلنا نحاول فيه البحث عن الأخطاء باستخدام PVS-Studio. يرمز SARIF إلى تنسيق تبادل نتائج التحليل الثابت ، وهو معيار (تنسيق ملف) مصمم للتفاعل وتبادل نتائج أجهزة التحليل الثابتة مع أدوات أخرى: IDEs ، وأدوات شاملة للتحقق من الشفرة وتحليلها (على سبيل المثال ، SonarQube) ، وأنظمة التكامل المستمر إلخ يحتوي SARIF SDK ، على التوالي ، على أدوات مطور .NET لدعم SARIF ، وكذلك الملفات الداعمة.
تم إنشاء SARIF في Microsoft وهو الآن معيار تم تطويره بواسطة OASIS (كونسورتيوم غير ربحي يشارك في معايير مفتوحة). تم تصميم SARIF لنقل ليس فقط نتائج محلل ، ولكن أيضا البيانات الوصفية حول الأداة ، وكذلك البيانات حول كيفية إطلاقه ، الطوابع الزمنية ، وهلم جرا. يمكن الاطلاع على مزيد من التفاصيل حول المعيار على موقع
OASIS . يمكن تنزيل التعليمات البرمجية المصدر لـ SARIF SDK من المستودع على
GiHub . الصفحة الرئيسية للمشروع متاحة
هنا .
عن المشروع
كان مشروع SARIF SDK صغيرًا: 799 ملف .cs (حوالي 98000 سطر غير فارغ من التعليمات البرمجية). يحتوي المشروع على اختبارات استبعدها دائمًا من التحقق. وبالتالي ، كان جزء الكود الذي يهمنا هو 642 ملف .cs (حوالي 79000 سطر غير فارغ من الكود). هذا ، بالطبع ، ليس كافيًا. لكن التحقق والتحليل كانا سريعين وسهلين ، بين الأشياء ، التي حاولت التعبير عنها في الصورة في بداية المقال. ومع ذلك ، تم العثور على بعض الأخطاء المثيرة للاهتمام. دعنا ننظر إليهم.
أخطاء
V3070 [CWE-457] يستخدم متغير غير مهيأ "ثنائي" عند تهيئة المتغير "الافتراضي". MimeType.cs 90
public static class MimeType { ....
تتم تهيئة الحقل
الافتراضي بقيمة حقل آخر لم يتلق قيمة بعد. نتيجة لذلك ، ستتلقى القيمة الافتراضية القيمة الافتراضية
لسلسلة الكتابة - خالية. ربما ، لم يتم ملاحظة الخطأ ، لأن الحقل
الافتراضي لا يستخدم في أي مكان. لكن كل شيء يمكن أن يتغير ، وبعد ذلك سيواجه المطور نتيجة غير متوقعة أو تعطل البرنامج.
يتم دائمًا إعادة كتابة المعلمة
V3061 "logicalLocationToIndexMap" في نص الطريقة قبل استخدامها. PrereleaseCompatibilityTransformer.cs 1963
private static JArray ConvertLogicalLocationsDictionaryToArray( .... Dictionary<LogicalLocation, int> logicalLocationToIndexMap, ....) { .... logicalLocationToIndexMap = new Dictionary<LogicalLocation, int>(LogicalLocation.ValueComparer); .... }
لا يتم استخدام المعلمة
logicalLocationToIndexMap بأي طريقة ، وكتابة قيمة أخرى إليها. الغريب أن القيمة القديمة هي بالضبط نفس القاموس الفارغ الذي تم إنشاؤه في طريقة الاتصال:
private static bool ApplyChangesFromTC25ThroughTC30(....) { .... Dictionary<LogicalLocation, int> logicalLocationToIndexMap = null; .... logicalLocationToIndexMap = new Dictionary<LogicalLocation, int>(LogicalLocation.ValueComparer); run["logicalLocations"] = ConvertLogicalLocationsDictionaryToArray( ...., logicalLocationToIndexMap, ....); }
كود غريب و مشبوه.
V3008 [CWE-563] تم تعيين متغير "run.Tool" لقيم مرتين متتاليتين. ربما هذا خطأ. خطوط التحقق: 116 ، 114. ExportRulesMetadataCommandBase.cs 116
public partial class Run { .... public Tool Tool { get; set; } .... } public partial class Tool : .... { .... public Tool() { } .... } private void OutputSarifRulesMetada(....) { .... var run = new Run(); run.Tool = new Tool(); run.Tool = Tool.CreateFromAssemblyData(....);
يتم تعيين
الخاصية run.Tool قيمة مرتين. كما هو الحال عند إنشاء كائن
Tool ، وعند الكتابة إلى خاصية
Tool ، لا يتم تنفيذ أي عمل إضافي. لذلك ، إعادة التعيين تبدو مشبوهة.
V3042 [CWE-476] ممكن NullReferenceException. "؟" و "." يتم استخدام عوامل التشغيل للوصول إلى أعضاء الكائن "loc" WhereComparer.cs 152
private static Uri ArtifactUri(ArtifactLocation loc, Run run) { return loc?.Uri ?? loc.Resolve(run)?.Uri; }
إذا تبين أن متغير
loc فارغ ، فسيتم إجراء محاولة لإرجاع القيمة من الجانب الأيمن من عامل التشغيل ، مما سيؤدي إلى الوصول بواسطة مرجع فارغ.
V3042 [CWE-476] ممكن NullReferenceException. "؟" و "." تستخدم عوامل التشغيل للوصول إلى أعضاء كائن "formatString" InsertOptionalDataVisitor.cs 194
public override Message VisitMessage(Message node) { .... node.Text = node.Arguments?.Count > 0 ? string.Format(...., formatString.Text, ....) : formatString?.Text; .... }
في فرعين متوازيين من العبارة الشرطية ؟: يستخدمون خيارات وصول غير آمنة وآمنة باستخدام مرجع
stringString محتمل.
V3042 [CWE-476] ممكن NullReferenceException. "؟" و "." تستخدم عوامل التشغيل للوصول إلى أعضاء كائن "messageText" FortifyFprConverter.cs 1210
V3042 [CWE-476] ممكن NullReferenceException. "؟" و "." تستخدم عوامل التشغيل للوصول إلى أعضاء كائن "messageText" FortifyFprConverter.cs 1216
private void AddMessagesToResult(Result result) { .... string messageText = (rule.ShortDescription ?? rule.FullDescription)?.Text; .... if (....) {
هنا ، أصدر المحلل فورًا تحذيرين حول إمكانية الوصول عبر إشارة فارغة
messageText . يبدو تافها ، ولكن الخطأ من هذا لا يتوقف عن أن يكون خطأ.
V3080 [CWE-476] احتمال
إلغاء فارغة. النظر في فحص 'fileDataVersionOne.Uri'. SarifCurrentToVersionOneVisitor.cs 1030
private IDictionary<string, FileDataVersionOne> CreateFileDataVersionOneDictionary() { .... FileDataVersionOne fileDataVersionOne = CreateFileDataVersionOne(v2File); if (fileDataVersionOne.Uri.OriginalString.Equals(key)) { .... } .... }
يشتبه المحلل في أن
NullReferenceException كان ممكنًا عند العمل مع ارتباط
fileDataVersionOne.Uri . دعونا نرى من أين يأتي هذا المتغير ، وما إذا كان المحلل هو الصحيح. للقيام بذلك ، قم بفحص نص الأسلوب
CreateFileDataVersionOne :
private FileDataVersionOne CreateFileDataVersionOne(Artifact v2FileData) { FileDataVersionOne fileData = null; if (v2FileData != null) { .... fileData = new FileDataVersionOne { .... Uri = v2FileData.Location?.Uri, .... }; .... } return fileData; } public partial class FileDataVersionOne { .... public Uri Uri { get; set; } .... }
في الواقع ، عند إنشاء كائن من فئة
FileDataVersionOne ، يمكن تعيين الخاصية
Uri إلى
فارغة . مثال جيد على تعاون تحليل تدفق البيانات وآليات التحليل interprocedural.
V3080 [CWE-476] احتمال
إلغاء فارغة. النظر في تفتيش '_jsonTextWriter'. SarifLogger.cs 242
public virtual void Dispose() { .... if (_closeWriterOnDispose) { if (_textWriter != null) { _textWriter.Dispose(); } if (_jsonTextWriter == null) { _jsonTextWriter.Close(); }
هذا خطأ مطبعي. من الواضح ، يجب أن تكون الحالة الثانية
إذا كانت الكتلة
_jsonTextWriter! = خالية . خطر هذا الكود هو أنه على الأرجح لن يتلف ، لأن
_jsonTextWriter ليس
باطلًا تمامًا. ولكن في الوقت نفسه ، يبقى التدفق مفتوحًا.
V3083 [CWE-367] الاحتجاج غير الآمن للحدث "RuleRead" ، NullReferenceException ممكن. النظر في تعيين الحدث إلى متغير محلي قبل استدعاء ذلك. FxCopConverter.cs 897
private void ReadRule(....) { .... if (RuleRead != null) { RuleRead(....); } .... }
إنهم لا يعملون مع الحدث بأمان. خطأ غير حاسم يمكن إصلاحه بسهولة ، على سبيل المثال ، بعد مطالبة Visual Studio. هنا هو استبدال IDE العروض:
private void ReadRule(....) { .... RuleRead?.Invoke(....); .... }
إنه يعمل لبضع ثوانٍ ، لكن المحلل لم يعد يقسم ، ولا يبرز IDE الكود. خطأ مشابه آخر:
- V3083 [CWE-367] الاحتجاج غير الآمن للحدث "ResultRead" ، NullReferenceException ممكن. النظر في تعيين الحدث إلى متغير محلي قبل استدعاء ذلك. FxCopConverter.cs 813
V3095 [CWE-476] تم استخدام كائن 'v1Location' قبل التحقق من صحته. خطوط التحقق: 333 ، 335. SarifVersionOneToCurrentVisitor.cs 333
internal Location CreateLocation(LocationVersionOne v1Location) { .... string key = v1Location.LogicalLocationKey ?? v1Location.FullyQualifiedLogicalName; if (v1Location != null) { .... } .... }
اقترح مؤلف الكود أن رابط
v1Location قد يكون خاليًا ، لذلك أضاف الشيك المناسب. في الوقت نفسه ، في الرمز أعلى قليلاً ، لسبب ما يعمل مع هذا الرابط دون أي شيكات. عدم الانتباه أثناء إعادة بيع المساكن؟ من الصعب القول.
V3125 [CWE-476] تم استخدام كائن 'v1StackFrame' بعد أن تم التحقق منه ضد قيمة خالية. خطوط التحقق: 1182 ، 1171. SarifVersionOneToCurrentVisitor.cs 1182
internal StackFrame CreateStackFrame(StackFrameVersionOne v1StackFrame) { StackFrame stackFrame = null; if (v1StackFrame != null) { stackFrame = new StackFrame { .... }; } stackFrame.Location = CreateLocation(v1StackFrame.FullyQualifiedLogicalName, v1StackFrame.LogicalLocationKey, ....); return stackFrame; }
تقليديا ، العكس هو الصحيح. أولاً ،
يتم التحقق من ارتباط
v1StackFrame لاغياً ، ثم نسوا التحقق. ولكن هناك تحذير واحد مهم:
متغيرات v1StackFrame و
stackFrame مرتبطة منطقيا. راجع ، إذا كانت
v1StackFrame =
فارغة ،
فلن يتم إنشاء
كائن StackFrame وسيظل
stackFrame =
فارغ . في هذه الحالة ، يقع البرنامج في مكالمة إلى
stackFrame.Location ، نظرًا لعدم وجود
اختبارات هنا. وهذا يعني أن الاستخدام الخطير لـ
v1StackFrame ، كما أشار المحلل ، لن يصل إلى هذه النقطة.
يعمل هذا الرمز فقط
إذا تم
تمرير قيم
v1StackFrame غير صفرية
إلى الأسلوب CreateStackFrame . أظن أنه تم التحكم بطريقة ما في رمز الاتصال. تبدو مكالمات
CreateStackFrame بالشكل التالي:
Frames = v1Stack.Frames?.Select(CreateStackFrame).ToList()
يستخدم
CreateStackFrame كمحدد. لا يوجد التحقق من مراجع المساواة
فارغة . ربما في مكان ما عند ملء مجموعة
الإطارات ، يتم التحكم في هذا (تسجيل المراجع الصفرية) ، لكنني لم أحفر هذا الحد. الاستنتاج واضح بالفعل - تتطلب الشفرة انتباه المؤلفين.
استنتاج
لقد تبين أن المقالة صغيرة ، ولكن لم يكن لدى أحد وقت للملل :) أذكرك أنه يمكنك دائمًا
تنزيل برنامج التحليل الخاص بنا للبحث عن أخطاء في مشاريعك أو في مشاريع الآخرين.
وأخيراً ، إعلان صغير: مقالتي التالية ستكون حول الأخطاء الأكثر إثارة للاهتمام التي وجدتها أنا وزملائي في مشاريع C # في عام 2019. اتبع
بلوق لدينا. اراك قريبا!
للتعرف على منشورات المدونة الجديدة ، يمكنك الاشتراك في القنوات التالية:

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