أثر دعم Visual Studio 2019 في PVS-Studio على عدد من المكونات: المكون الإضافي نفسه ، ومحلل سطر الأوامر ، ومحلل محلل C ++ و C # ، وعدد قليل من الأدوات المساعدة. في هذه المقالة ، سأشرح بإيجاز المشكلات التي واجهناها عند تطبيق دعم IDE وكيف عالجناها.
قبل أن نبدأ ، أود إلقاء نظرة على تاريخ دعم الإصدارات السابقة من Visual Studio في PVS-Studio حتى تفهم بشكل أفضل رؤيتنا للمهمة والحلول التي توصلنا إليها في كل موقف.
منذ الإصدار الأول من PVS-Studio الذي تم إرفاقه ببرنامج إضافي لبرنامج Visual Studio (كان Visual Studio 2005 في ذلك الوقت) ، كان دعم الإصدارات الجديدة من IDE مهمة تافهة للغاية بالنسبة لنا ، والتي جاءت أساسًا في تحديث مشروع البرنامج المساعد ملف وتبعيات ملحقات API Visual Studio المختلفة. بين الحين والآخر ، سيتعين علينا إضافة دعم للميزات الجديدة لـ C ++ ، والتي كان برنامج التحويل البرمجي Visual C ++ يتعلم تدريجيًا العمل بها ، لكنها لم تكن مهمة صعبة بشكل عام ، ويمكن تنفيذها بسهولة قبل إصدار Visual Studio جديد . علاوة على ذلك ، كان لدى PVS-Studio محلل واحد فقط في ذلك الوقت - من أجل C / C ++.
تغيرت الأمور عند إصدار Visual Studio 2017. بالإضافة إلى التغييرات الهائلة التي طرأت على العديد من امتدادات واجهة برمجة تطبيقات IDE ، فقد واجهنا أيضًا مشكلة في الحفاظ على التوافق الخلفي لمحلل C # الجديد الذي تم إضافته قبل ذلك بفترة قصيرة (بالإضافة إلى طبقة محلل جديدة لـ C ++ للعمل مع مشاريع MSBuild) مع إصدارات جديدة من MSBuild \ Visual Studio.
بالنظر إلى كل هذا ، أوصي بشدة أن ترى مقالة ذات صلة حول دعم Visual Studio 2017 ، "
دعم Visual Studio 2017 و Roslyn 2.0 في PVS-Studio: في بعض الأحيان ، ليس من السهل استخدام حلول جاهزة كما قد يبدو "، قبل القراءة على. تتناول هذه المقالة المشكلات التي واجهناها في المرة الأخيرة ونموذج التفاعل بين المكونات المختلفة (مثل PVS-Studio و MSBuild و Roslyn). قد تساعدك معرفة هذه التفاصيل على فهم المقالة الحالية بشكل أفضل.
أدت معالجة هذه المشكلات في النهاية إلى تغييرات كبيرة على المحلل ، وكنا نأمل أن تساعد الأساليب الجديدة المطبقة حينئذٍ في دعم الإصدارات المستقبلية من Visual Studio \ MSBuild بشكل أسهل وأسرع. بدأ هذا الأمل بالفعل في إثبات الواقعية حيث تم إصدار تحديثات Visual Studio 2017 العديدة. هل ساعدنا النهج الجديد في دعم Visual Studio 2019؟ تابع القراءة لمعرفة ذلك.
PVS-Studio المساعد لبرنامج Visual Studio 2019
بدا أن البداية كانت واعدة. لم يستغرق الأمر الكثير من الجهد لتوصيل البرنامج المساعد بـ Visual Studio 2019 وجعله يعمل بشكل جيد. ولكن واجهنا بالفعل مشكلتين في وقت واحد يمكن أن يجلب المزيد من المتاعب في وقت لاحق.
يتعلق الأمر الأول بواجهة
IVsSolutionWorkspaceService المستخدمة لدعم وضع Lightweight Solution Load (والذي ، بالمناسبة ، تم تعطيله في أحد التحديثات السابقة ، مرة أخرى في Visual Studio 2017). لقد تم تزيينها
بخاصية Deprecated ، والتي لم تقم في الوقت الحالي إلا بتحذير في وقت البناء ولكنها ستصبح مشكلة كبيرة في المستقبل. هذا الوضع لم يدم طويلًا بالفعل ... كان من السهل إصلاحه - لقد توقفنا ببساطة عن استخدام هذه الواجهة.
كانت المشكلة الثانية هي الرسالة التالية التي ظللنا نحصل عليها عند تحميل Visual Studio مع تمكين المكون الإضافي:
اكتشف Visual Studio واحدًا أو أكثر من الإضافات المعرضة للخطر أو لا يعمل في تحديث ميزة VS.ساعدت سجلات تشغيل Visual Studio (ملف ActivityLog) على محو ذلك:
تحذير: تستخدم الإضافة "PVS-Studio" ميزة "التحميل التلقائي المتزامن" في Visual Studio. لن يتم دعم هذه الميزة في تحديث Visual Studio 2019 في المستقبل ، وعندها لن يعمل هذا الامتداد. يرجى الاتصال ببائع التمديد للحصول على تحديث.ما يعنيه بالنسبة لنا هو أنه سيتعين علينا التبديل من وضع التحميل المتزامن إلى غير المتزامن. أرجو ألا تمانع في أن أوفر لك تفاصيل كيفية تعاملنا مع واجهات COM في Visual Studio ، ثم حدد التغييرات لفترة وجيزة.
هناك مقال نشرته Microsoft حول تحميل الإضافات بشكل غير متزامن: "
كيفية: استخدام AsyncPackage لتحميل VSPackages في الخلفية ". ومع ذلك ، كان من الواضح بالفعل أن هناك المزيد من التغييرات القادمة.
كان أحد أكبر التغييرات في وضع التحميل ، أو بالأحرى وضع التهيئة. في الإصدارات السابقة ، تم إجراء كل التهيئة اللازمة باستخدام طريقتين:
تهيئة صفنا ورثًا من
Package و
OnShellPropertyChange . يجب إضافة الأخير لأنه عند التحميل بشكل متزامن ، قد لا يزال Visual Studio نفسه في طور التحميل والتهيئة ، وبالتالي ، كان من المستحيل تنفيذ بعض الإجراءات الضرورية أثناء تهيئة المكون الإضافي. إحدى الطرق لإصلاح هذا الأمر هي تأخير تنفيذ هذه الإجراءات حتى إنهاء Visual Studio لحالة "zombie". لقد كان هذا الجزء من المنطق هو
اختيارنا لأسلوب
OnShellPropertyChange مع التحقق من حالة "الزومبي".
طريقة
التهيئة للفئة التجريدية
AsyncPackage ، والتي يتم تحميل الإضافات المتزامنة
الموروثة منها ، تكون
مختومة ، لذلك يجب أن تتم
التهيئة بالطريقة المتجاوزة
InitializeAsync ، وهو ما فعلناه بالضبط. يجب تغيير منطق التحقق من "الزومبي" أيضًا لأن معلومات الحالة لم تعد متاحة لمكوِّننا الإضافي. إضافة إلى ذلك ، لا يزال يتعين علينا تنفيذ تلك الإجراءات التي يجب القيام بها بعد تهيئة المكون الإضافي. لقد تم حل ذلك من خلال استخدام أسلوب
OnPackageLoaded لواجهة
IVsPackageLoadEvents ، حيث تم تنفيذ تلك الإجراءات المتأخرة.
هناك مشكلة أخرى ناتجة عن التحميل غير المتزامن وهي أنه لا يمكن استخدام أوامر المكون الإضافي إلا بعد تحميل Visual Studio. أدى فتح سجل محلل عن طريق النقر المزدوج في مدير الملفات (إذا كنت بحاجة إلى فتحه من Visual Studio) إلى إطلاق الإصدار المقابل من devenv.exe باستخدام أمر لفتح السجل. بدا الأمر التشغيل مثل هذا:
"C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\Common7\IDE\devenv.exe" /command "PVSStudio.OpenAnalysisReport C:\Users\vasiliev\source\repos\ConsoleApp\ConsoleApp.plog"
يتم استخدام علامة "/ الأمر" هنا لتشغيل الأمر المسجل في Visual Studio. هذا النهج لم يعد يعمل لأن الأوامر لم تعد متوفرة إلا بعد تحميل البرنامج المساعد. كان الحل البديل الذي توصلنا إليه هو تحليل أمر بدء تشغيل devenv.exe بعد قيام المكون الإضافي بتحميل وتشغيل الأمر log open إذا وجد في أمر الإطلاق. وبالتالي ، سمح لنا تجاهل فكرة استخدام الواجهة "المناسبة" للعمل مع الأوامر بالاحتفاظ بالوظائف اللازمة ، مع تأخر فتح السجل بعد أن تم تحميل المكون الإضافي بالكامل.
Phew ، يبدو أننا جعلناها في النهاية ؛ يتم تحميل البرنامج المساعد ويفتح كما هو متوقع ، دون أي تحذيرات.
وهنا عندما تسوء الأمور. يقوم Paul (Hi Paul!) بتثبيت المكوّن الإضافي على جهاز الكمبيوتر الخاص به ويسأل لماذا لم ننتقل بعد إلى التحميل غير المتزامن.
القول بأننا صدمنا سيكون بخس. لا يمكن أن يكون! لكنه حقيقي: إليك الإصدار الجديد من المكون الإضافي ، وهنا تظهر رسالة تفيد بأن الحزمة يتم تحميلها بشكل متزامن. ألكساندر (Hi Alexander!) وأنا أجرب نفس الإصدار على أجهزة الكمبيوتر الخاصة بنا - إنه يعمل بشكل جيد. كيف يكون ذلك ممكنا؟ ثم يحدث لنا التحقق من إصدارات مكتبات PVS-Studio المحملة في Visual Studio - ونجد أن هذه هي مكتبات Visual Studio 2017 ، بينما تحتوي حزمة VSIX على الإصدارات الجديدة ، أي Visual Studio 2019.
بعد العبث بـ VSIXInstaller لفترة ، تمكنا من معرفة أن المشكلة كانت تتعلق بذاكرة التخزين المؤقت للحزم. تم دعم هذه النظرية أيضًا من خلال تقييد الوصول إلى الحزمة المخزنة مؤقتًا (C: \ ProgramData \ Microsoft \ VisualStudio \ Packages) الناتجة عن VSIXInstaller لإخراج رسالة خطأ في السجل. من الغريب أنه عندما لا يحدث الخطأ ، لم تظهر المعلومات حول تثبيت الحزم المخزنة مؤقتًا.
ملاحظة جانبية . أثناء دراسة سلوك VSIX Installer والمكتبات المصاحبة له ، اعتقدت كم هو رائع أن Roslyn و MSBuild مفتوحان المصدر ، مما يسمح لك بقراءة التعليمات البرمجية الخاصة بها وتصحيحها بسهولة وتتبع منطق عملها.
لذلك ، هذا ما حدث: عند تثبيت المكوّن الإضافي ، رأى VSIX Installer أن الحزمة المقابلة قد تم تخزينها مؤقتًا بالفعل (كانت بالفعل حزمة .vsix لـ Visual Studio 2017) وتثبيت تلك الحزمة بدلاً من الحزمة الجديدة. لماذا تجاهلت القيود / المتطلبات المحددة في ملف .vsixmanifest (والذي ، من بين أشياء أخرى ، تقييد تثبيت الامتدادات على إصدار محدد من Visual Studio) هو سؤال لم تتم الإجابة عليه بعد. نتيجة لذلك ، تم تثبيت المكون الإضافي المصمم لـ Visual Studio 2017 على Visual Studio 2019 - على الرغم من القيود المحددة في ملف .vsixmanifest.
الأسوأ من ذلك ، أن هذا التثبيت حطم الرسم البياني للتبعيات في Visual Studio ، وعلى الرغم من أن IDE بدا أنه يعمل بشكل جيد ، إلا أن الأمور كانت فظيعة بالفعل. لا يمكنك تثبيت أو حذف الملحقات أو التحديث أو ما إلى ذلك. كانت عملية "الاستعادة" مؤلمة للغاية حيث اضطررنا إلى حذف الامتداد (أي الملفات التي تحتوي عليه) يدويًا - وكذلك يدويًا - تحرير ملفات التكوين التي تخزن معلومات حول الحزمة المثبتة. وبعبارة أخرى ، لم يكن متعة على الإطلاق.
لإصلاح ذلك وللتأكد من أننا لم نواجه أي مواقف مثل تلك في المستقبل ، قررنا إنشاء GUID الخاص بنا للحزمة الجديدة لجعل حزم Visual Studio 2017 و Visual Studio 2019 معزولة بشكل آمن عن بعضهما البعض ( كانت الحزم الأقدم جيدة ؛ فقد استخدموا دائمًا GUID مشتركًا).
منذ أن بدأنا الحديث عن المفاجآت غير السارة ، إليك مفاجأة أخرى: بعد التحديث إلى Preview 2 ، انتقلت قائمة PVS-Studio إلى علامة التبويب "الإضافات". ليست مشكلة كبيرة ، لكنها جعلت الوصول إلى وظائف البرنامج المساعد أقل ملاءمة. استمر هذا السلوك خلال إصدارات Visual Studio 2019 التالية ، بما في ذلك الإصدار. لقد وجدت إشارات إلى هذه "الميزة" لا في الوثائق ولا في المدونة.
حسنًا ، تبدو الأمور الآن جيدة ويبدو أننا انتهينا من دعم Visual Studio 2019 أخيرًا. أثبت هذا الخطأ في اليوم التالي بعد إطلاق الإصدار 7.02 من برنامج PVS-Studio. كان وضع التحميل غير المتزامن مرة أخرى. عند فتح نافذة نتائج التحليل (أو بدء التحليل) ، ستظهر نافذة محلل "فارغة" للمستخدم - لا توجد أزرار ، ولا شبكة ، ولا شيء على الإطلاق.
هذه المشكلة في الواقع حدثت بين الحين والآخر أثناء التحليل. لكنه أثر على كمبيوتر واحد فقط ولم يظهر حتى يتم تحديث Visual Studio بأحد التكرارات الأولى من "المعاينة". نشك في أن شيئًا ما قد تم كسره أثناء التثبيت أو التحديث ومع ذلك ، فقد اختفت المشكلة في وقت لاحق ولن تحدث حتى على هذا الكمبيوتر المحدد ، لذلك اعتقدنا أنها "تم إصلاحها من تلقاء نفسها". لكن لا - كنا محظوظين فقط. أو سيئ الحظ ، لهذه المسألة.
كما اكتشفنا ، كان الترتيب الذي تم به تهيئة نافذة IDE نفسها (الفئة المشتقة من
ToolWindowPane ) ومحتوياتها (سيطرتنا على الشبكة والأزرار). في ظل ظروف معينة ، سيتم تهيئة عنصر التحكم قبل الجزء وعلى الرغم من أن الأمور تسير بشكل جيد وأن طريقة
FindToolWindowAsync (إنشاء النافذة عندما يتم الوصول إليها لأول مرة) قد أدت وظيفتها بشكل جيد ، ظل عنصر التحكم غير مرئي. تم إصلاح ذلك عن طريق إضافة تهيئة كسول من أجل سيطرتنا إلى رمز ملء الجزء.
دعم C # 8.0
هناك ميزة واحدة رائعة حول استخدام Roslyn كأساس للمحلل: لا يتعين عليك إضافة دعم للبنيات اللغوية الجديدة يدويًا - يتم إجراؤه تلقائيًا من خلال مكتبات تحليل الأكواد من Microsoft ، ونحن فقط نستخدم الحلول الجاهزة. وهذا يعني أن بناء الجملة الجديد مدعوم ببساطة عن طريق تحديث المكتبات.
أما بالنسبة للتحليل نفسه ، فقد اضطررنا إلى تعديل الأشياء بمفردنا ، بالطبع - على وجه الخصوص ، التعامل مع التراكيب اللغوية الجديدة. بالتأكيد ، كان لدينا شجرة بناء جملة جديدة يتم إنشاؤها تلقائيًا عن طريق تحديث Roslyn ببساطة ، ولكن لا يزال يتعين علينا أن نعلم المحلل كيف بالضبط تفسير ومعالجة عقد شجرة بناء الجملة الجديدة أو المعدلة.
ربما تكون أنواع المراجع الفارغة هي السمة الجديدة التي تمت مناقشتها على نطاق واسع في C # 8. لن أتحدث عنها الآن لأن موضوع كبير يستحق مقالة منفصلة (التي تتم كتابتها حاليًا). في الوقت الحالي ، لقد استقرنا على تجاهل التعليقات التوضيحية الفارغة في آلية تدفق البيانات لدينا (أي أننا نفهمها ونحللها ونتخطاها). تكمن الفكرة في أن المتغير ، حتى من النوع المرجعي غير القابل للإلغاء ، لا يزال من السهل جدًا (أو عن طريق الخطأ) تعيين القيمة
فارغة ، بحيث تنتهي بـ NRE عند محاولة إلغاء تحديد المرجع. يمكن للمحلل الخاص بنا اكتشاف مثل هذه الأخطاء والإبلاغ عن dereference فارغة محتملة (إذا وجد مثل هذا الواجب في الكود ، بالطبع) حتى لو كان المتغير من النوع non-nullable.
يمكّنك استخدام أنواع مرجعية nullable وبناء الجملة المقترن من كتابة رمز مثير للاهتمام. لقد أطلقنا عليها اسم "بناء الجملة العاطفي". هذا المقتطف متوافق تمامًا:
obj.Calculate(); obj?.Calculate(); obj.Calculate(); obj!?.Calculate(); obj!!!.Calculate();
بالمناسبة ، قادني تجاربي إلى اكتشاف بعض الحيل التي يمكنك استخدامها لتحطم "Visual Studio" باستخدام بناء الجملة الجديد. وهي تستند إلى حقيقة أنه يُسمح لك بكتابة أكبر عدد ممكن! الشخصيات كما تريد. هذا يعني أنك لا تستطيع كتابة رمز مثل هذا فقط:
object temp = null!
ولكن أيضا مثل هذا:
object temp = null!!!;
ودفعها إلى أبعد من ذلك ، يمكنك كتابة أشياء مجنونة مثل هذا:
object temp = null!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;
هذا الرمز قابل للترجمة ، ولكن إذا حاولت عرض شجرة بناء الجملة في Syntax Visualizer من .NET Compiler Platform SDK ، فسوف يتعطل Visual Studio.
يمكن سحب تقرير الفشل من "عارض الأحداث":
Faulting application name: devenv.exe, version: 16.0.28803.352, time stamp: 0x5cc37012 Faulting module name: WindowsBase.ni.dll, version: 4.8.3745.0, time stamp: 0x5c5bab63 Exception code: 0xc00000fd Fault offset: 0x000c9af4 Faulting process id: 0x3274 Faulting application start time: 0x01d5095e7259362e Faulting application path: C:\Program Files (x86)\ Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.exe Faulting module path: C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\ WindowsBase\4480dfedf0d7b4329838f4bbf953027d\WindowsBase.ni.dll Report Id: 66d41eb2-c658-486d-b417-02961d9c3e4f Faulting package full name: Faulting package-relative application ID:
إذا كنت أكثر جنونًا وقمت بإضافة عدة علامات تعجب ، فسوف يبدأ Visual Studio في الانهيار بنفسه ، دون أي مساعدة من Syntax Visualizer. لا يمكن لمكتبات Microsoft.CodeAnalysis ومترجم csc.exe التعامل مع مثل هذا الرمز أيضًا.
هذه الأمثلة مفتعلة ، بالطبع ، لكنني وجدت هذه الحيلة مضحكة.
مجموعة أدوات
كان من الواضح أن تحديث مجموعة الأدوات سيكون الجزء الأكثر صعوبة. على الأقل هذا ما بدا في البداية ، لكنني أميل الآن إلى الاعتقاد بأن دعم المكون الإضافي كان الجزء الأكثر صعوبة. لسبب واحد ، كان لدينا بالفعل مجموعة أدوات وآلية لتقييم مشاريع MSBuild ، والتي كانت جيدة كما كانت على الرغم من أنه لم يتم تمديدها بعد. حقيقة أننا لم نضطر إلى كتابة الخوارزميات من البداية جعلت الأمر أسهل. أثبتت استراتيجية الاعتماد على مجموعة أدوات "our" ، والتي فضلنا الالتزام بها عند دعم Visual Studio 2017 ، أنها صحيحة مرة أخرى.
بشكل تقليدي ، تبدأ العملية بتحديث حزم NuGet. تحتوي علامة التبويب لإدارة حزم NuGet للحل الحالي على الزر "تحديث" ... لكنه لا يساعد. تسبب تحديث جميع الحزم في وقت واحد في تعارضات متعددة في الإصدار ، ولم تكن محاولة حلها كلها فكرة جيدة. كانت الطريقة الأكثر إيلامًا والأكثر أمانًا هي تحديث الحزم المستهدفة من Microsoft.Build / Microsoft.CodeAnalysis بشكل انتقائي.
تم رصد أحد الاختلافات فورًا عند اختبار التشخيص: تم تغيير بنية شجرة بناء الجملة على عقدة موجودة. ليست مشكلة كبيرة نحن ثابت ذلك بسرعة.
اسمحوا لي أن أذكركم ، نحن نختبر محللينا (من أجل C # و C ++ و Java) في مشاريع مفتوحة المصدر. يسمح لنا هذا بإجراء اختبار شامل للتشخيصات - على سبيل المثال ، التحقق منها لإيجابيات كاذبة أو معرفة ما إذا كنا نفتقد أي حالات (لتقليل عدد السلبيات الخاطئة). تساعدنا هذه الاختبارات أيضًا في تتبع الانحدار المحتمل في الخطوة الأولية لتحديث المكتبات / مجموعة الأدوات. هذه المرة وقعوا في عدد من القضايا كذلك.
أحدها أن السلوك داخل مكتبات CodeAnalysis ازداد سوءًا. على وجه التحديد ، عند التحقق من بعض المشاريع ، بدأنا في الحصول على استثناءات من كود المكتبات في عمليات مختلفة مثل الحصول على المعلومات الدلالية ، وفتح المشروعات ، وما إلى ذلك.
يتذكر أولئك الذين قرأوا بعناية المقالة حول دعم Visual Studio 2017 أن توزيعنا يأتي مع دمية - ملف MSBuild.exe من 0 بايت.
الآن يجب علينا دفع هذه الممارسة إلى أبعد من ذلك وتشمل الدمى الفارغة للمترجمين csc.exe و vbc.exe و VBCSCompiler.exe. لماذا؟ لقد توصلنا إلى هذا الحل بعد تحليل أحد المشاريع من قاعدة الاختبار الخاصة بنا والحصول على تقارير فرق: لن ينتج الإصدار الجديد من المحلل بعض التحذيرات المتوقعة.
لقد وجدنا أن الأمر يتعلق برموز الترجمة الشرطية ، والتي لم يتم استخراج بعضها بشكل صحيح عند استخدام الإصدار الجديد من المحلل. من أجل الوصول إلى جذر المشكلة ، كان علينا أن نعمق في كود مكتبات Roslyn.
يتم تحليل رموز
الترجمة الشرطية باستخدام الأسلوب
GetDefineConstantsSwitch للفئة
Csc من مكتبة
Microsoft.Build.Tasks.CodeAnalysis . يتم إجراء التحليل باستخدام الأسلوب
String.Split على عدد من الفواصل:
string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' });
تعمل آلية التحليل هذه تمامًا ؛ يتم استخراج جميع رموز الترجمة الشرطية بشكل صحيح. حسنا ، دعنا نستمر في الحفر.
كانت النقطة الرئيسية التالية هي استدعاء الأسلوب
ComputePathToTool للفئة
ToolTask . تحسب هذه الطريقة المسار إلى الملف القابل للتنفيذ (
csc.exe ) وتحقق مما إذا كان هناك. إذا كان الأمر كذلك ، فتُرجع الطريقة المسار إليها أو تكون
فارغة .
رمز الاتصال:
.... string pathToTool = ComputePathToTool(); if (pathToTool == null) {
نظرًا لعدم وجود ملف
csc.exe (لماذا نحتاج إليه؟) ، يتم تعيين
PathToTool القيمة
الخالية في هذه المرحلة ، وتقوم الطريقة الحالية (
ToolTask.Execute ) بإرجاع
false . يتم تجاهل نتائج تنفيذ المهمة ، بما في ذلك رموز الترجمة الشرطية المستخرجة.
حسنًا ، دعنا نرى ما يحدث إذا وضعنا ملف
csc.exe حيث من المتوقع أن يكون.
الآن يقوم
pathToTool بتخزين المسار الفعلي للملف الحالي ،
وسيحتفظ ToolTask.Execute بالتنفيذ. النقطة الرئيسية التالية هي استدعاء الأسلوب
ManagedCompiler.ExecuteTool :
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { if (ProvideCommandLineArgs) { CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands) .Select(arg => new TaskItem(arg)).ToArray(); } if (SkipCompilerExecution) { return 0; } .... }
خاصية
SkipCompilerExecution صحيحة (منطقيًا بما فيه الكفاية لأننا لا نقوم
بالترجمة من أجل الحقيقي).
تتحقق طريقة الاستدعاء (
ToolTask.Execute المذكورة
سابقًا ) من أن قيمة الإرجاع لـ
ExecuteTool تساوي 0 وإذا كان الأمر كذلك ،
فتُرجع الدالة true . سواء كان
csc.exe الخاص بك
مترجمًا فعليًا أو "الحرب والسلام" من قِبل ليو تولستوي ، فلا يهم على الإطلاق.
لذلك ، تتعلق المشكلة بالترتيب الذي تم به تحديد الخطوات:
- تحقق من وجود مترجم
- تحقق مما إذا كان يجب إطلاق برنامج التحويل البرمجي ؛
ونتوقع ترتيب عكسي. لإصلاح هذا تمت إضافة الدمى للمترجمين.
حسنًا ، لكن كيف تمكنا من الحصول على رموز تجميع على الإطلاق ، مع عدم وجود ملف csc.exe (وتجاهل نتائج المهمة)؟
حسنًا ، هناك طريقة لهذه الحالة أيضًا:
CSharpCommandLineParser.ParseConditionalCompilationSymbols من مكتبة
Microsoft.CodeAnalysis.CSharp . يقوم أيضاً
بالتحليل عن طريق استدعاء الأسلوب
String.Split على عدد من الفواصل:
string[] values = value.Split(new char[] { ';', ',' } );
ترى كيف تختلف هذه المجموعة من الفواصل عن تلك التي يتم معالجتها بواسطة طريقة
Csc.GetDefineConstantsSwitch ؟ هنا ، الفضاء ليس فاصل. هذا يعني أن رموز الترجمة الشرطية مفصولة بمسافات لن يتم تحليلها بشكل صحيح بهذه الطريقة.
هذا ما حدث عندما تم التحقق من مشاريع المشكلة: استخدموا رموز التجميع الشرطي المفصولة ، وبالتالي ، تم تحليلها بنجاح بواسطة أسلوب
GetDefineConstantsSwitch ولكن ليس بطريقة
ParseConditionalCompilationSymbols .
المشكلة الأخرى التي ظهرت بعد تحديث المكتبات هي السلوك المكسور في بعض الحالات - وتحديداً ، في المشروعات التي لم يتم إنشاؤها. لقد أثر هذا على مكتبات
تحليل الشفرة في Microsoft ، وتجلّى على أنه استثناءات من جميع الأنواع:
ArgumentNullException (فشل التهيئة لبعض المسجل الداخلي) و
NullReferenceException وما إلى ذلك.
أود أن أخبركم عن خطأ خاص واحد وجدته مثيراً للاهتمام.
واجهناها عند التحقق من الإصدار الجديد من مشروع Roslyn: كانت إحدى المكتبات تقوم برمي
NullReferenceException . بفضل المعلومات التفصيلية حول مصدرها ، سرعان ما وجدنا رمز مصدر المشكلة وقررت - للتأكد من الفضول - التحقق مما إذا كان الخطأ سيستمر عند العمل في Visual Studio.
لقد نجحنا في إعادة إنتاجه في Visual Studio (الإصدار 16.0.3). للقيام بذلك ، تحتاج إلى تعريف فئة مثل هذا:
class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } }
ستحتاج أيضًا إلى Syntax Visualizer (يأتي مع .NET Compiler Platform SDK). ابحث عن
TypeSymbol (بالنقر فوق عنصر القائمة "عرض TypeSymbol (إن وجد))" من عقدة شجرة بناء الجملة من النوع
ConstantPatternSyntax (
خالية ). سيتم إعادة تشغيل Visual Studio ، وستتاح معلومات الاستثناء - على وجه التحديد ، تتبع المكدس - في "عارض الأحداث":
Application: devenv.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.NullReferenceException at Microsoft.CodeAnalysis.CSharp.ConversionsBase. ClassifyImplicitBuiltInConversionSlow( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.ConversionsBase.ClassifyBuiltInConversion( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoForNode( Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode) at Microsoft.CodeAnalysis.CSharp.MemberSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.SyntaxTreeSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfo( Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoFromNode( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoCore( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) ....
كما ترون ، سبب المشكلة هو dereference مرجع فارغة.
كما ذكرت بالفعل ، واجهنا مشكلة مماثلة عند اختبار محلل. إذا قمت
بإنشائه باستخدام مكتبات تصحيح الأخطاء من Microsoft.
Code Code ، يمكنك الوصول إلى موضع المشكلة مباشرةً من خلال البحث عن
TypeSymbol لعقدة شجرة بناء الجملة المقابلة.
سوف يأخذنا في النهاية إلى أسلوب
ClassifyImplicitBuiltInConversionSlow المذكور في تتبع المكدس أعلاه:
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; } Conversion conversion = ClassifyStandardImplicitConversion(source, destination, ref useSiteDiagnostics); if (conversion.Exists) { return conversion; } return Conversion.NoConversion; }
هنا ، المعلمة
الوجهة فارغة ، لذلك يؤدي استدعاء
الوجهة .SpecialType إلى رمي
NullReferenceException . نعم ، عملية dereference تسبقها
Debug.Assert ، لكنها لا تساعد لأنها في الواقع لا تحمي من أي شيء - فهي تتيح لك ببساطة اكتشاف المشكلة في إصدارات تصحيح الأخطاء في المكتبات. أم لا.
التغييرات في آلية تقييم مشاريع C ++
لم يكن هناك الكثير من الاهتمام في هذا الجزء: الخوارزميات الحالية لا تتطلب أي تعديلات كبيرة جديرة بالذكر ، ولكن قد ترغب في معرفة مشكلتين صغيرتين.
الأول هو أننا اضطررنا إلى تعديل الخوارزميات التي اعتمدت على القيمة العددية لـ ToolsVersion. دون الخوض في التفاصيل ، هناك حالات معينة تحتاج فيها إلى مقارنة مجموعات الأدوات واختيار ، على سبيل المثال ، أحدث إصدار. النسخة الجديدة ، بطبيعة الحال ، لها قيمة أكبر. توقعنا أن يكون لـ ToolsVersion لـ MSBuild / Visual Studio الجديد القيمة 16.0. نعم بالتأكيد! يوضح الجدول أدناه كيف تغيرت قيم الخصائص المختلفة خلال تاريخ تطوير Visual Studio:
أعلم أن النكتة حول أرقام الإصدارات الفاسدة لنظامي التشغيل Windows و Xbox قديمة ، ولكنها تثبت أنه لا يمكنك إجراء أي تنبؤات موثوقة بشأن القيم (سواء بالاسم أو الإصدار) لمنتجات Microsoft المستقبلية. :)
لقد حللنا ذلك بسهولة عن طريق إضافة تحديد الأولويات لمجموعة الأدوات (أي تحديد الأولوية ككيان منفصل).
تتضمن المشكلة الثانية مشاكل في العمل في Visual Studio 2017 أو البيئة ذات الصلة (على سبيل المثال ، عند تعيين متغير البيئة
VisualStudioVersion ). يحدث ذلك لأن معلمات الحوسبة اللازمة لتقييم مشروع C ++ تعد مهمة أكثر صعوبة من تقييم مشروع .NET. بالنسبة إلى .NET ، نستخدم مجموعة الأدوات الخاصة بنا والقيمة المقابلة لـ ToolsVersion. بالنسبة إلى C ++ ، يمكننا استخدام كل من مجموعة الأدوات الخاصة بنا والأدوات التي يوفرها النظام. بدءًا من Build Tools لـ Visual Studio 2017 ، يتم تعريف مجموعات الأدوات في الملف
MSBuild.exe.config بدلاً من التسجيل. لهذا السبب لم نتمكن من الحصول عليها من قائمة مجموعات الأدوات العالمية بعد الآن (باستخدام
Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets ، على سبيل المثال) بخلاف تلك المعرفة في السجل (على سبيل المثال ، Visual Studio 2015 والإصدارات السابقة).
كل هذا يمنعنا من تقييم مشروع باستخدام
ToolsVersion 15.0 لأن النظام لن يرى مجموعة الأدوات المطلوبة. ستظل أحدث مجموعة أدوات ،
الحالية ، متاحة لأنها مجموعة أدواتنا الخاصة ، وبالتالي ، لا توجد مشكلة من هذا القبيل في Visual Studio 2019. كان الحل بسيطًا للغاية وسمح لنا بإصلاح ذلك دون تغيير خوارزميات التقييم الحالية: نحن فقط كان لابد من تضمين مجموعة أدوات أخرى ،
15.0 ، في قائمة مجموعات الأدوات الخاصة بنا بالإضافة إلى
Current .
التغييرات في آلية تقييم مشاريع C # .NET Core
تضمنت هذه المهمة مسألتين مترابطتين:
- إضافة مجموعة الأدوات "الحالية" إلى تحليل مشاريع .NET Core في Visual Studio 2017 ؛
- لن يعمل التحليل لمشاريع .NET Core على الأنظمة دون تثبيت نسخة واحدة على الأقل من Visual Studio.
كانت كلتا المشكلتين تأتي من نفس المصدر: تم البحث عن بعض ملفات .targets / .props الأساسية في مسارات خاطئة. هذا يمنعنا من تقييم مشروع باستخدام مجموعة أدوات لدينا.
إذا لم يكن لديك مثيل Visual Studio مثبتًا ، فستحصل على الخطأ التالي (مع إصدار مجموعة الأدوات السابق ،
15.0 ):
The imported project "C:\Windows\Microsoft.NET\Framework64\ 15.0\Microsoft.Common.props" was not found.
عند تقييم مشروع C # .NET Core في Visual Studio 2017 ، ستحصل على الخطأ التالي (مع إصدار مجموعة الأدوات
الحالي ،
الحالي ):
The imported project "C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\MSBuild\Current\Microsoft.Common.props" was not found. ....
نظرًا لأن هذه المشكلات متشابهة (التي يبدو أنها موجودة) ، يمكننا محاولة قتل عصفورين بحجر واحد.
في الفقرات التالية ، سأشرح كيف أنجزنا ذلك ، دون الخوض في التفاصيل. ستكون هذه التفاصيل (حول كيفية تقييم مشاريع C # .NET Core وكذلك التغييرات في آلية التقييم في مجموعة الأدوات الخاصة بنا) موضوعًا لأحد مقالاتنا المستقبلية. بالمناسبة ، إذا كنت تقرأ هذا المقال بعناية ، فربما لاحظت أن هذا هو المرجع الثاني لمقالاتنا المستقبلية. :)
الآن ، كيف حلنا هذه المشكلة؟ لقد قمنا بتوسيع مجموعة الأدوات الخاصة بنا باستخدام ملفات .targets / .props الأساسية من .NET Core SDK (
Sdk.props ،
Sdk.targets ). وقد أتاح لنا ذلك مزيدًا من التحكم في الموقف والمزيد من المرونة في إدارة الاستيراد بالإضافة إلى تقييم مشاريع .NET Core بشكل عام. نعم ، لقد أصبحت مجموعة الأدوات الخاصة بنا أكبر قليلاً مرة أخرى ، وكان علينا أيضًا إضافة منطق لإعداد البيئة المطلوبة لتقييم مشاريع .NET Core ، ولكن يبدو أنه يستحق ذلك.
حتى ذلك الحين ، قمنا بتقييم مشاريع .NET Core ببساطة عن طريق طلب التقييم والاعتماد على MSBuild للقيام بالمهمة.
الآن بعد أن أصبح لدينا سيطرة أكبر على الموقف ، تغيرت الآلية قليلاً:
- إعداد البيئة المطلوبة لتقييم مشاريع .NET Core ؛
- التقييم:
- بدء التقييم باستخدام .targets / .props الملفات من مجموعة أدواتنا ؛
- مواصلة التقييم باستخدام الملفات الخارجية.
يشير هذا التسلسل إلى أن إعداد البيئة يسعى إلى تحقيق هدفين رئيسيين:
- بدء التقييم باستخدام .targets / .props الملفات من مجموعة أدواتنا ؛
- إعادة توجيه كافة العمليات اللاحقة إلى ملفات .targets / .props الخارجية.
مكتبة خاصة تستخدم Microsoft.DotNet.MSBuildSdkResolver للبحث عن ملفات .targets / .props الضرورية. من أجل الشروع في إعداد البيئة باستخدام ملفات من مجموعة أدواتنا ، استخدمنا متغير بيئة خاصًا تستخدمه تلك المكتبة حتى نتمكن من الإشارة في المصدر إلى مكان استيراد الملفات الضرورية من (أي مجموعة أدواتنا). نظرًا لأن المكتبة مضمنة في التوزيع لدينا ، فلا يوجد خطر من حدوث فشل منطقي مفاجئ.
الآن لدينا ملفات Sdk من مجموعة أدواتنا المستوردة أولاً ، وبما أننا نستطيع تغييرها بسهولة الآن ، فإننا نتحكم بشكل كامل في بقية منطق التقييم. وهذا يعني أنه يمكننا الآن تحديد أي الملفات ومن أي مكان لاستيراده. الأمر نفسه ينطبق على Microsoft.Common.props المذكورة أعلاه. نحن نستورد هذا والملفات الأساسية الأخرى من مجموعة أدواتنا حتى لا داعي للقلق بشأن وجودها أو محتوياتها.
بمجرد الانتهاء من جميع عمليات الاستيراد الضرورية وتحديد الخصائص ، فإننا نمرر عملية التقييم إلى .NET Core SDK الفعلي ، حيث يتم تنفيذ جميع العمليات المطلوبة المتبقية.
استنتاج
كان دعم Visual Studio 2019 عمومًا أسهل من دعم Visual Studio 2017 لعدة أسباب. أولاً ، لم تقم Microsoft بتغيير العديد من الأشياء التي كانت عليها عند التحديث من Visual Studio 2015 إلى Visual Studio 2017. نعم ، لقد قاموا بتغيير مجموعة الأدوات الأساسية وأجبروا إضافات Visual Studio على التحول إلى وضع التحميل غير المتزامن ، ولكن هذا التغيير لم يكن هذا جذري. ثانياً ، لقد كان لدينا بالفعل حل جاهز يتضمن مجموعة أدواتنا الخاصة وآلية تقييم المشروع ، ولم يكن علينا ببساطة أن نعمل كل شيء من البداية - فقط بناءً على ما لدينا بالفعل. كما أن العملية غير المؤلمة نسبيًا لدعم تحليل مشاريع .NET Core في ظل ظروف جديدة (وعلى أجهزة الكمبيوتر التي لم يتم تثبيت نسخ Visual Studio بها) من خلال توسيع نظام تقييم المشروع لدينا تمنحنا أيضًا الأمل في أننا اتخذنا الخيار الصحيح من خلال التحكم في بعض أيدينا.
ولكن أود أن أكرر الفكرة التي تم إيصالها في المقالة السابقة: في بعض الأحيان لا يكون استخدام الحلول الجاهزة بهذه السهولة التي قد تبدو عليها.