Dukungan Visual Studio 2019 di PVS-Studio


Dukungan Visual Studio 2019 di PVS-Studio memengaruhi sejumlah komponen: plugin itu sendiri, penganalisa baris perintah, inti dari penganalisa C ++ dan C #, dan beberapa utilitas. Pada artikel ini, saya akan menjelaskan secara singkat masalah apa yang kami temui ketika menerapkan dukungan IDE dan bagaimana kami mengatasinya.

Sebelum kita mulai, saya ingin melihat kembali sejarah mendukung versi Visual Studio sebelumnya di PVS-Studio sehingga Anda lebih memahami visi tugas dan solusi yang kami buat dalam setiap situasi.

Sejak versi pertama PVS-Studio yang dikirimkan bersama dengan plugin untuk Visual Studio (saat itu Visual Studio 2005), mendukung versi baru dari IDE ini merupakan tugas yang sangat sepele bagi kami, yang pada dasarnya turun untuk memperbarui proyek plugin. file dan dependensi dari berbagai ekstensi API Visual Studio. Sesekali kita harus menambahkan dukungan untuk fitur-fitur baru C ++, yang secara bertahap dipelajari oleh kompiler Visual C ++ untuk bekerja dengannya, tetapi umumnya itu bukan tugas yang sulit juga dan dapat dengan mudah dilakukan tepat sebelum rilis Visual Studio baru . Selain itu, PVS-Studio hanya memiliki satu alat analisa saat itu - untuk C / C ++.

Banyak hal berubah ketika Visual Studio 2017 dirilis. Selain perubahan besar pada banyak ekstensi API IDE, kami juga mengalami masalah dengan mempertahankan kompatibilitas mundur penganalisis C # baru yang ditambahkan sesaat sebelum itu (serta lapisan penganalisis baru untuk C ++ untuk bekerja dengan proyek MSBuild) dengan versi baru MSBuild \ Visual Studio.

Mempertimbangkan semua ini, saya sangat menyarankan Anda melihat artikel terkait tentang dukungan Visual Studio 2017, " Dukungan Visual Studio 2017 dan Roslyn 2.0 di PVS-Studio: kadang-kadang tidak mudah untuk menggunakan solusi yang sudah jadi karena tampaknya ", sebelum membaca. Artikel itu membahas masalah yang kami hadapi terakhir kali dan model interaksi antara berbagai komponen (seperti PVS-Studio, MSBuild, dan Roslyn). Mengetahui detail ini dapat membantu Anda lebih memahami artikel saat ini.

Mengatasi masalah tersebut pada akhirnya mengarah pada perubahan signifikan pada penganalisa, dan kami berharap bahwa pendekatan baru yang diterapkan kemudian akan membantu kami mendukung versi Visual Studio \ MSBuild yang akan datang lebih mudah dan lebih cepat. Harapan ini sudah mulai terbukti realistis karena banyak pembaruan Visual Studio 2017 dirilis. Apakah pendekatan baru membantu kami dalam mendukung Visual Studio 2019? Baca terus untuk mengetahui.

Plugin PVS-Studio untuk Visual Studio 2019


Awal mulanya tampak menjanjikan. Kami tidak butuh banyak upaya untuk port plugin ke Visual Studio 2019 dan membuatnya diluncurkan dan berjalan dengan baik. Tapi kami sudah menemui dua masalah sekaligus yang bisa membawa lebih banyak masalah nanti.

Yang pertama berkaitan dengan antarmuka IVsSolutionWorkspaceService yang digunakan untuk mendukung mode Beban Solusi Ringan (yang, bagaimanapun, telah dinonaktifkan di salah satu pembaruan sebelumnya, kembali di Visual Studio 2017). Itu dihiasi dengan atribut usang , yang saat ini hanya memicu peringatan saat membangun tetapi akan menjadi masalah besar di masa depan. Mode ini tidak bertahan lama memang ... Itu mudah diperbaiki - kami hanya berhenti menggunakan antarmuka ini.

Masalah kedua adalah pesan berikut yang kami dapatkan saat memuat Visual Studio dengan plugin diaktifkan: Visual Studio telah mendeteksi satu atau lebih ekstensi yang berisiko atau tidak berfungsi dalam pembaruan fitur VS.

Log peluncuran Visual Studio (file ActivityLog) membantu menjernihkannya:

Peringatan: Ekstensi 'PVS-Studio' menggunakan fitur 'auto-load sinkron' dari Visual Studio. Fitur ini tidak lagi didukung dalam pembaruan Visual Studio 2019 di masa depan, di mana ekstensi ini tidak akan berfungsi. Silakan hubungi vendor ekstensi untuk mendapatkan pembaruan.

Apa artinya bagi kami adalah bahwa kami harus beralih dari mode beban sinkron ke asinkron. Saya harap Anda tidak akan keberatan jika saya memberi Anda detail tentang bagaimana kami berinteraksi dengan antarmuka COM Visual Studio, dan hanya menjelaskan perubahan secara singkat.

Ada sebuah artikel oleh Microsoft tentang memuat plugin secara tidak serempak: " Cara: Gunakan AsyncPackage untuk memuat VSPackages di latar belakang ". Namun, sudah jelas bahwa ada lebih banyak perubahan yang akan terjadi.

Salah satu perubahan terbesar adalah pada mode pemuatan, atau lebih tepatnya mode inisialisasi. Pada versi sebelumnya, semua inisialisasi yang diperlukan dilakukan dengan menggunakan dua metode: Inisialisasi kelas kita yang diwarisi dari Package , dan OnShellPropertyChange . Yang terakhir harus ditambahkan karena ketika memuat secara bersamaan, Visual Studio itu sendiri mungkin masih dalam proses memuat dan inisialisasi, dan, oleh karena itu, beberapa tindakan yang diperlukan tidak mungkin dilakukan selama inisialisasi plugin. Salah satu cara untuk memperbaikinya adalah dengan menunda eksekusi tindakan tersebut hingga Visual Studio keluar dari status 'zombie'. Ini bagian dari logika yang kami pilih ke dalam metode OnShellPropertyChange dengan memeriksa status 'zombie'.

Metode Inisialisasi kelas abstrak AsyncPackage , yang secara asinkron memuat plugin, disegel , jadi inisialisasi harus dilakukan dalam metode yang diganti, InitializeAsync , yang persis seperti yang kami lakukan. Logika pemeriksaan 'zombie' juga harus diubah karena informasi status tidak lagi tersedia untuk plugin kami. Selain itu, kami masih harus melakukan tindakan-tindakan yang harus dilakukan setelah inisialisasi plugin. Kami memecahkan itu dengan menggunakan metode OnPackageLoaded dari antarmuka IVsPackageLoadEvents , yang merupakan tempat tindakan tertunda itu dilakukan.

Masalah lain yang dihasilkan dari asynchronous load adalah bahwa perintah plugin tidak dapat digunakan sampai setelah Visual Studio dimuat. Membuka log penganalisa dengan mengklik dua kali pada file manager (jika Anda perlu membukanya dari Visual Studio) menghasilkan meluncurkan versi yang sesuai dari devenv.exe dengan perintah untuk membuka log. Perintah peluncuran terlihat seperti ini:

"C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\Common7\IDE\devenv.exe" /command "PVSStudio.OpenAnalysisReport C:\Users\vasiliev\source\repos\ConsoleApp\ConsoleApp.plog" 

Bendera "/ perintah" digunakan di sini untuk menjalankan perintah yang terdaftar di Visual Studio. Pendekatan ini tidak berfungsi lagi karena perintah tidak lagi tersedia sampai setelah plugin dimuat. Solusi yang kami temukan adalah membuat perintah peluncuran devenv.exe diuraikan setelah plugin telah memuat dan menjalankan perintah log terbuka jika ditemukan dalam perintah peluncuran. Dengan demikian, membuang ide untuk menggunakan antarmuka "yang sesuai" untuk bekerja dengan perintah memungkinkan kami untuk menjaga fungsionalitas yang diperlukan, dengan pembukaan log yang tertunda setelah plugin sepenuhnya dimuat.

Fiuh, sepertinya kita akhirnya berhasil; plugin memuat dan terbuka seperti yang diharapkan, tanpa peringatan apa pun.

Dan di sinilah semuanya salah. Paul (Hai Paul!) Instal plugin di komputernya dan tanyakan mengapa kita masih belum beralih ke beban asinkron.

Untuk mengatakan bahwa kami terkejut akan menjadi pernyataan yang meremehkan. Itu tidak mungkin! Tapi ini nyata: inilah versi baru plugin, dan inilah pesan yang mengatakan bahwa paket memuat secara bersamaan. Alexander (Hai Alexander!) Dan saya mencoba versi yang sama di komputer masing-masing - ini berfungsi dengan baik. Bagaimana itu mungkin? Kemudian terpikir oleh kami untuk memeriksa versi pustaka PVS-Studio yang dimuat di Visual Studio - dan kami menemukan bahwa ini adalah pustaka untuk Visual Studio 2017, sedangkan paket VSIX berisi versi baru, mis. Untuk Visual Studio 2019.

Setelah bermain-main dengan VSIXInstaller untuk sementara waktu, kami berhasil mengetahui bahwa masalahnya ada pada cache paket. Teori ini juga didukung oleh fakta bahwa membatasi akses ke paket cache (C: \ ProgramData \ Microsoft \ VisualStudio \ Packages) yang disebabkan oleh VSIXInstaller untuk menampilkan pesan kesalahan dalam log. Anehnya, ketika kesalahan tidak terjadi, informasi tentang menginstal paket cache tidak muncul.

Catatan samping . Saat mempelajari perilaku Penginstal VSIX dan perpustakaan yang menyertainya, saya berpikir betapa kerennya bahwa Roslyn dan MSBuild adalah open-source, yang memungkinkan Anda untuk dengan mudah membaca dan men-debug kode mereka dan melacak logika kerjanya.

Jadi, inilah yang terjadi: ketika menginstal plugin, VSIX Installer melihat bahwa paket yang sesuai sudah di-cache (itu sebenarnya paket .vsix untuk Visual Studio 2017) dan menginstal paket itu bukan yang baru. Mengapa ia mengabaikan pembatasan / persyaratan yang ditentukan dalam file .vsixmanifest (yang, antara lain, pemasangan ekstensi yang dibatasi ke versi Visual Studio tertentu) adalah pertanyaan yang belum dijawab. Akibatnya, plugin yang dirancang untuk Visual Studio 2017 terinstal di Visual Studio 2019 - meskipun ada pembatasan yang ditentukan dalam file .vsixmanifest.

Terburuk dari semua, instalasi itu mematahkan grafik dependensi Visual Studio, dan meskipun IDE tampaknya berjalan dengan baik, semuanya benar-benar mengerikan. Anda tidak dapat menginstal atau menghapus ekstensi, memperbarui, dll. Proses "pemulihan" juga menyakitkan karena kami harus menghapus ekstensi (yaitu file yang berisi itu) secara manual dan - juga secara manual - mengedit file konfigurasi yang menyimpan informasi tentang paket yang diinstal. Dengan kata lain, itu sama sekali tidak menyenangkan.

Untuk memperbaikinya dan memastikan kami tidak mengalami situasi seperti itu di masa mendatang, kami memutuskan untuk membuat GUID kami sendiri untuk paket baru agar paket-paket untuk Visual Studio 2017 dan Visual Studio 2019 diisolasi dengan aman satu sama lain ( paket lama baik-baik saja; mereka selalu menggunakan GUID bersama).

Karena kami mulai berbicara tentang kejutan yang tidak menyenangkan, inilah yang lain: setelah memperbarui ke Pratinjau 2, menu PVS-Studio "pindah" ke tab "Ekstensi". Bukan masalah besar, tetapi itu membuat mengakses fungsi plugin menjadi kurang nyaman. Perilaku ini bertahan melalui versi Visual Studio 2019 berikutnya, termasuk rilis. Saya telah menemukan menyebutkan "fitur" ini baik di dokumentasi maupun di blog.

Oke, sekarang semuanya tampak baik-baik saja dan kami tampaknya telah selesai dengan dukungan Visual Studio 2019 pada akhirnya. Ini terbukti salah pada hari berikutnya setelah merilis PVS-Studio 7.02. Itu adalah mode muat asinkron lagi. Saat membuka jendela hasil analisis (atau memulai analisis), jendela analisa akan tampak "kosong" bagi pengguna - tanpa tombol, tanpa kisi, tidak ada sama sekali.

Masalah ini sebenarnya terjadi setiap saat selama analisis. Tapi itu mempengaruhi hanya satu komputer dan tidak muncul sampai Visual Studio diperbarui ke salah satu iterasi pertama 'Pratinjau'. Kami menduga ada sesuatu yang rusak selama instalasi atau pembaruan. Masalahnya, bagaimanapun, menghilang beberapa waktu kemudian dan tidak akan terjadi bahkan pada komputer tertentu, jadi kami pikir itu "diperbaiki sendiri." Tapi tidak - kami hanya beruntung. Atau sial, dalam hal ini.

Ketika kami temukan, itu adalah urutan di mana jendela IDE itu sendiri (kelas yang berasal dari ToolWindowPane ) dan isinya (kontrol kami dengan kisi-kisi dan tombol) diinisialisasi. Dalam kondisi tertentu, kontrol akan diinisialisasi sebelum panel dan meskipun semuanya berjalan dengan baik dan metode FindToolWindowAsync (membuat jendela saat diakses untuk pertama kali) melakukan tugasnya dengan baik, kontrol tetap tidak terlihat. Kami memperbaikinya dengan menambahkan inisialisasi malas untuk kontrol kami ke kode pane-filling.

Dukungan C # 8.0


Ada satu keuntungan besar tentang menggunakan Roslyn sebagai dasar untuk penganalisa: Anda tidak perlu menambahkan dukungan untuk konstruksi bahasa baru secara manual - itu dilakukan secara otomatis melalui perpustakaan Analisis Kode Microsoft, dan kami hanya menggunakan solusi yang sudah jadi. Ini berarti sintaks baru didukung dengan hanya memperbarui perpustakaan.

Adapun analisis itu sendiri, kami harus mengubah hal-hal kami sendiri, tentu saja - khususnya, menangani konstruksi bahasa baru. Tentu, kami memiliki pohon sintaksis baru yang dihasilkan secara otomatis hanya dengan memperbarui Roslyn, tetapi kami masih harus mengajar analisanya bagaimana tepatnya menafsirkan dan memproses simpul pohon sintaksis yang baru atau yang dimodifikasi.

Jenis referensi yang dapat dibatalkan mungkin merupakan fitur baru yang paling banyak dibahas dari C # 8. Saya tidak akan membicarakannya sekarang karena topik yang sebesar itu layak untuk artikel yang terpisah (yang saat ini sedang ditulis). Untuk saat ini, kami telah memutuskan untuk mengabaikan anotasi yang tidak dapat dibatalkan dalam mekanisme aliran data kami (yaitu, kami memahami, menguraikan, dan melewatkannya). Idenya adalah bahwa suatu variabel, bahkan dari tipe referensi yang tidak dapat dibatalkan , masih dapat dengan mudah (atau tidak sengaja) diberi nilai nol , diakhiri dengan NRE ketika mencoba melakukan dereferensi. Penganalisa kami dapat menemukan kesalahan tersebut dan melaporkan potensi null dereference (jika tentu saja menemukan penugasan tersebut dalam kode) bahkan jika variabelnya adalah tipe referensi yang tidak dapat dibatalkan.

Menggunakan jenis referensi yang dapat dibatalkan dan sintaks terkait memungkinkan Anda untuk menulis kode yang cukup menarik. Kami menjulukinya "sintaksis emosional". Cuplikan ini dapat dikompilasi dengan sempurna:

 obj.Calculate(); obj?.Calculate(); obj.Calculate(); obj!?.Calculate(); obj!!!.Calculate(); 

Omong-omong, eksperimen saya membuat saya menemukan beberapa trik yang dapat Anda gunakan untuk "crash" Visual Studio menggunakan sintaks baru. Mereka didasarkan pada kenyataan bahwa Anda diperbolehkan menulis sebanyak '!' karakter yang Anda inginkan. Ini berarti Anda tidak hanya dapat menulis kode seperti ini:

 object temp = null! 

tetapi juga seperti ini:

 object temp = null!!!; 

Dan, mendorongnya lebih jauh, Anda bisa menulis hal-hal gila seperti ini:

 object temp = null!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!; 

Kode ini dapat dikompilasi, tetapi jika Anda mencoba untuk melihat pohon sintaks di Sintaks Visualizer dari .NET Compiler Platform SDK, Visual Studio akan macet.

Laporan kegagalan dapat ditarik dari Peraga Peristiwa:

 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: 

Jika Anda menjadi lebih gila dan menambahkan beberapa kali lebih banyak tanda seru, Visual Studio akan mulai macet dengan sendirinya, tanpa bantuan dari Syntax Visualizer. Pustaka Microsoft.CodeAnalysis dan kompiler csc.exe juga tidak bisa mengatasi kode tersebut.

Contoh-contoh ini dibuat-buat, tentu saja, tetapi saya menemukan trik itu lucu.

Toolset


Jelas bahwa memperbarui toolset akan menjadi bagian yang paling sulit. Setidaknya seperti itulah awalnya, tetapi sekarang saya cenderung berpikir bahwa dukungan plugin adalah bagian yang paling sulit. Untuk satu hal, kami sudah memiliki perangkat dan mekanisme untuk mengevaluasi proyek-proyek MSBuild, yang bagus meskipun itu masih harus diperpanjang. Fakta bahwa kami tidak harus menulis algoritme dari awal membuatnya lebih mudah. Strategi mengandalkan toolset "kami", yang kami sukai untuk tetap bertahan saat mendukung Visual Studio 2017, sekali lagi terbukti benar.

Secara tradisional, proses dimulai dengan memperbarui paket NuGet. Tab untuk mengelola paket NuGet untuk solusi saat ini berisi tombol "Perbarui" ... tetapi tidak membantu. Memperbarui semua paket sekaligus menyebabkan banyak konflik versi, dan mencoba menyelesaikannya sepertinya bukan ide yang baik. Cara yang lebih menyakitkan namun mungkin lebih aman adalah dengan memperbarui paket target Microsoft secara selektif. Build / Microsoft.CodeAnalysis.

Satu perbedaan langsung terlihat ketika menguji diagnostik: struktur pohon sintaks berubah pada node yang ada. Bukan masalah besar; kami memperbaikinya dengan cepat.

Biarkan saya mengingatkan Anda, kami menguji analisis kami (untuk C #, C ++, Java) pada proyek sumber terbuka. Ini memungkinkan kami untuk menguji diagnostik secara menyeluruh - misalnya, memeriksanya untuk positif palsu atau melihat apakah kami melewatkan kasus apa pun (untuk mengurangi jumlah negatif palsu). Tes-tes ini juga membantu kami melacak kemungkinan kemunduran pada langkah awal memperbarui perpustakaan / toolset. Kali ini mereka menangkap sejumlah masalah juga.

Salah satunya adalah bahwa perilaku di dalam perpustakaan CodeAnalysis semakin buruk. Secara khusus, ketika memeriksa proyek tertentu, kami mulai mendapatkan pengecualian dari kode perpustakaan pada berbagai operasi seperti memperoleh informasi semantik, membuka proyek, dan sebagainya.

Anda yang telah membaca artikel tentang dukungan Visual Studio 2017 dengan hati-hati ingat bahwa distribusi kami disertai dengan dummy - file MSBuild.exe sebesar 0 byte.

Sekarang kami harus mendorong praktik ini lebih jauh dan menyertakan boneka kosong untuk kompiler csc.exe, vbc.exe, dan VBCSCompiler.exe. Mengapa Kami datang dengan solusi ini setelah menganalisis salah satu proyek dari basis pengujian kami dan mendapatkan laporan berbeda: versi baru dari penganalisa tidak akan menghasilkan beberapa peringatan yang diharapkan.

Kami menemukan bahwa itu ada hubungannya dengan simbol kompilasi bersyarat, beberapa di antaranya tidak diekstraksi dengan benar ketika menggunakan versi baru dari penganalisis. Untuk mencapai akar masalahnya, kami harus menggali lebih dalam kode perpustakaan Roslyn.

Simbol kompilasi bersyarat diuraikan menggunakan metode GetDefineConstantsSwitch dari Csc kelas dari perpustakaan Microsoft.Build.Tasks.CodeAnalysis . Penguraian dilakukan menggunakan metode String.Split pada sejumlah pemisah:

 string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' }); 

Mekanisme parsing ini bekerja dengan sempurna; semua simbol kompilasi bersyarat diekstraksi dengan benar. Oke, mari terus menggali.

Titik kunci berikutnya adalah panggilan metode ComputePathToTool dari ToolTask kelas. Metode ini menghitung jalur ke file yang dapat dieksekusi ( csc.exe ) dan memeriksa apakah ada di sana. Jika demikian, metode mengembalikan jalur ke sana atau nol sebaliknya.

Kode panggilan:

 .... string pathToTool = ComputePathToTool(); if (pathToTool == null) { // An appropriate error should have been logged already. return false; } .... 

Karena tidak ada file csc.exe (mengapa kita membutuhkannya?), PathToTool diberi nilai null pada titik ini, dan metode saat ini ( ToolTask.Execute ) mengembalikan false . Hasil dari mengeksekusi tugas, termasuk simbol kompilasi bersyarat yang diekstrak, diabaikan.

Oke, mari kita lihat apa yang terjadi jika kita meletakkan file csc.exe di tempat yang seharusnya.

Sekarang pathToTool menyimpan path sebenarnya ke file sekarang-sekarang, dan ToolTask.Execute terus mengeksekusi. Poin kunci berikutnya adalah panggilan metode 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; } .... } 

Properti SkipCompilerExecution benar (cukup logis karena kami tidak mengkompilasi nyata). Metode pemanggilan ( ToolTask.Execute yang telah disebutkan) memeriksa apakah nilai kembali untuk ExecuteTool adalah 0 dan, jika demikian, mengembalikan true . Apakah csc.exe Anda adalah kompiler yang sebenarnya atau "War and Peace" oleh Leo Tolstoy tidak masalah sama sekali.

Jadi, masalahnya berkaitan dengan urutan langkah-langkahnya:

  • periksa kompiler;
  • periksa apakah kompiler harus diluncurkan;

Dan kami mengharapkan pesanan terbalik. Ini untuk memperbaiki ini bahwa boneka untuk kompiler ditambahkan.

Oke, tapi bagaimana kita bisa mendapatkan simbol kompilasi sama sekali, dengan absennya file csc.exe (dan hasil tugas diabaikan)?

Nah, ada metode untuk kasus ini juga: CSharpCommandLineParser.ParseConditionalCompilationSymbols dari perpustakaan Microsoft.CodeAnalysis.CSharp . Itu juga parsing dengan memanggil metode String.Split pada sejumlah pemisah:

 string[] values = value.Split(new char[] { ';', ',' } /*, StringSplitOptions.RemoveEmptyEntries*/); 

Lihat bagaimana set pemisah ini berbeda dari yang ditangani oleh metode Csc.GetDefineConstantsSwitch ? Di sini, spasi bukan pemisah. Ini berarti bahwa simbol kompilasi bersyarat yang dipisahkan oleh spasi tidak akan diuraikan dengan benar dengan metode ini.

Itulah yang terjadi ketika kami memeriksa proyek masalah: mereka menggunakan simbol kompilasi bersyarat ruang-terpisah dan, oleh karena itu, berhasil diurai oleh metode GetDefineConstantsSwitch tetapi tidak dengan metode ParseConditionalCompilationSymbols .

Masalah lain yang muncul setelah memperbarui perpustakaan adalah perilaku yang rusak dalam kasus-kasus tertentu - khususnya, pada proyek-proyek yang tidak dibangun. Ini memengaruhi perpustakaan Analisis Kode Microsoft dan memanifestasikan dirinya sebagai pengecualian dari semua jenis: ArgumentNullException (gagal inisialisasi beberapa logger internal), NullReferenceException , dan sebagainya.

Saya ingin memberi tahu Anda tentang satu kesalahan tertentu yang menurut saya cukup menarik.

Kami menemukannya ketika memeriksa versi baru proyek Roslyn: salah satu perpustakaan melempar NullReferenceException . Berkat informasi terperinci tentang sumbernya, kami dengan cepat menemukan kode sumber masalah dan - hanya untuk rasa ingin tahu - memutuskan untuk memeriksa apakah kesalahan akan tetap ada ketika bekerja di Visual Studio.

Kami berhasil mereproduksi dalam Visual Studio (versi 16.0.3). Untuk melakukan itu, Anda memerlukan definisi kelas seperti ini:

 class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } } 

Anda juga akan memerlukan Sintaks Visualizer (dilengkapi dengan .NET Compiler Platform SDK). Cari TypeSymbol (dengan mengklik item menu "Lihat TypeSymbol (jika ada)") dari simpul pohon sintaks tipe ConstantPatternSyntax ( null ). Visual Studio akan memulai kembali, dan info pengecualian - khususnya, jejak tumpukan - akan tersedia di Peraga Peristiwa:

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

Seperti yang Anda lihat, masalahnya disebabkan oleh referensi referensi nol.

Seperti yang sudah saya sebutkan, kami mengalami masalah serupa saat menguji analyzer. Jika Anda membangunnya menggunakan debug perpustakaan dari Microsoft. Code Analysis , Anda bisa langsung ke tempat masalah dengan mencari TypeSymbol dari simpul pohon sintaksis yang sesuai.

Ini pada akhirnya akan membawa kita ke metode ClassifyImplicitBuiltInConversionSlow yang disebutkan dalam jejak tumpukan di atas:

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

Di sini, parameter tujuan adalah nol , sehingga memanggil tujuan. Khusus Jenis hasil melempar NullReferenceException . Ya, operasi dereference didahului oleh Debug.Assert , tetapi tidak membantu karena sebenarnya tidak melindungi dari apa pun - itu hanya memungkinkan Anda untuk menemukan masalah dalam versi debug perpustakaan. Atau tidak.

Perubahan pada mekanisme evaluasi proyek C ++


Tidak banyak yang menarik di bagian ini: algoritma yang ada tidak memerlukan modifikasi besar yang layak disebutkan, tetapi Anda mungkin ingin tahu tentang dua masalah kecil.

Yang pertama adalah kami harus memodifikasi algoritma yang bergantung pada nilai numerik ToolsVersion. Tanpa merinci, ada kasus-kasus tertentu ketika Anda perlu membandingkan toolets dan memilih, katakanlah, versi terbaru. Versi baru, tentu saja, memiliki nilai lebih besar. Kami berharap ToolsVersion untuk MSBuild / Visual Studio baru akan memiliki nilai 16.0. Ya tentu! Tabel di bawah ini menunjukkan bagaimana nilai properti yang berbeda berubah sepanjang sejarah pengembangan Visual Studio:
Nama produk studio visual
Nomor versi studio visual
Versi Alat
Versi PlatformToolset
Visual studio 2010
10.0
4.0
100
Visual studio 2012
11.0
4.0
110
Visual studio 2013
12.0
12.0
120
Visual studio 2015
14.0
14.0
140
Visual studio 2017
15.0
15.0
141
Visual studio 2019
16.0
Saat ini
142

Saya tahu bahwa lelucon tentang nomor versi Windows dan Xbox yang berantakan adalah yang lama, tetapi ini membuktikan bahwa Anda tidak dapat membuat prediksi yang dapat diandalkan tentang nilai-nilai (baik dalam nama atau versi) dari produk Microsoft di masa depan. :)

Kami menyelesaikannya dengan mudah dengan menambahkan memprioritaskan untuk toolet (yaitu memilih prioritas sebagai entitas terpisah).

Masalah kedua melibatkan masalah dengan bekerja di Visual Studio 2017 atau lingkungan terkait (misalnya, ketika variabel lingkungan VisualStudioVersion diatur). Itu terjadi karena parameter komputasi yang diperlukan untuk mengevaluasi proyek C ++ adalah tugas yang jauh lebih sulit daripada mengevaluasi proyek .NET. Untuk .NET, kami menggunakan perangkat kami sendiri dan nilai ToolsVersion yang sesuai. Untuk C ++, kita dapat menggunakan toolset kita sendiri dan yang disediakan oleh sistem. Dimulai dengan Alat Bangun untuk Visual Studio 2017, alat ditetapkan dalam file MSBuild.exe.config bukan registri. Itu sebabnya kami tidak bisa mendapatkannya lagi dari daftar global perangkat (menggunakan Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets , misalnya) tidak seperti yang didefinisikan dalam registri (yaitu untuk Visual Studio 2015 dan sebelumnya).

Semua ini mencegah kita mengevaluasi proyek menggunakan ToolsVersion 15.0 karena sistem tidak akan melihat set alat yang diperlukan. Toolset terbaru, Lancar , masih akan tersedia karena itu adalah toolset kami sendiri, dan, oleh karena itu, tidak ada masalah seperti itu di Visual Studio 2019. Solusinya cukup sederhana dan memungkinkan kami untuk memperbaikinya tanpa mengubah algoritma evaluasi yang ada: kami hanya harus memasukkan set alat lain, 15.0 , ke dalam daftar set alat kami sendiri selain Lancar .

Perubahan pada mekanisme evaluasi proyek Inti C # .NET


Tugas ini melibatkan dua masalah yang saling terkait:

  • menambahkan toolset 'Current' yang memecah analisis proyek .NET Core di Visual Studio 2017;
  • analisis tidak akan bekerja untuk proyek .NET Core pada sistem tanpa setidaknya satu salinan Visual Studio diinstal.

Kedua masalah tersebut berasal dari sumber yang sama: beberapa file .targets / .props dasar terlihat di jalur yang salah. Ini mencegah kami mengevaluasi proyek menggunakan perangkat kami.

Jika Anda tidak menginstal instance Visual Studio, Anda akan mendapatkan kesalahan berikut (dengan versi toolset sebelumnya, 15.0 ):

 The imported project "C:\Windows\Microsoft.NET\Framework64\ 15.0\Microsoft.Common.props" was not found. 

Saat mengevaluasi proyek C # .NET Core di Visual Studio 2017, Anda akan mendapatkan kesalahan berikut (dengan versi toolset saat ini, Lancar ):

 The imported project "C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\MSBuild\Current\Microsoft.Common.props" was not found. .... 

Karena masalah-masalah ini mirip (yang nampaknya memang demikian), kita dapat mencoba membunuh dua burung dengan satu batu.

Dalam paragraf berikutnya, saya akan menjelaskan bagaimana kami mencapai itu, tanpa merinci. Rincian ini (tentang bagaimana C # .NET Core proyek dievaluasi serta perubahan mekanisme evaluasi dalam perangkat kami) akan menjadi topik dari salah satu artikel kami di masa depan. Ngomong-ngomong, jika Anda membaca artikel ini dengan cermat, Anda mungkin memperhatikan bahwa ini adalah referensi kedua untuk artikel mendatang kami. :)

Sekarang, bagaimana kita mengatasi masalah itu? Kami memperluas toolset kami sendiri dengan file .targets / .props dasar dari .NET Core SDK ( Sdk.props , Sdk.targets ). Itu memberi kami lebih banyak kontrol atas situasi dan fleksibilitas dalam manajemen impor serta evaluasi proyek .NET Core secara umum. Ya, toolset kami menjadi sedikit lebih besar lagi, dan kami juga harus menambahkan logika untuk mengatur lingkungan yang diperlukan untuk evaluasi proyek .NET Core, tetapi tampaknya sepadan.

Sampai saat itu, kami telah mengevaluasi proyek .NET Core dengan hanya meminta evaluasi dan mengandalkan MSBuild untuk melakukan pekerjaan itu.

Sekarang kami memiliki kendali lebih besar atas situasi, mekanismenya berubah sedikit:

  • mengatur lingkungan yang diperlukan untuk mengevaluasi proyek .NET Core;
  • evaluasi:
    • mulai evaluasi menggunakan file .targets / .props dari perangkat kami;
    • lanjutkan evaluasi menggunakan file eksternal.

Urutan ini menunjukkan bahwa pengaturan lingkungan mengejar dua tujuan utama:

  • memulai evaluasi menggunakan file .targets / .props dari perangkat kami;
  • mengarahkan semua operasi selanjutnya ke file .targets / .props eksternal.

Perpustakaan khusus Microsoft.DotNet.MSBuildSdkResolver digunakan untuk mencari file .targets / .props yang diperlukan. Untuk memulai pengaturan lingkungan menggunakan file dari toolset kami, kami menggunakan variabel lingkungan khusus yang digunakan oleh perpustakaan itu sehingga kami dapat menunjuk pada sumber tempat mengimpor file yang diperlukan dari (yaitu toolset kami). Karena perpustakaan dimasukkan ke dalam distribusi kami, tidak ada risiko kegagalan logika tiba-tiba.

Sekarang kami memiliki file Sdk dari toolset kami yang diimpor terlebih dahulu, dan karena kami dapat dengan mudah mengubahnya sekarang, kami sepenuhnya mengendalikan sisa logika evaluasi. Ini berarti kita sekarang dapat memutuskan file mana dan dari lokasi mana yang akan diimpor. Hal yang sama berlaku untuk Microsoft.Common.props yang disebutkan di atas. Kami mengimpor ini dan file dasar lainnya dari perangkat kami sehingga kami tidak perlu khawatir tentang keberadaan atau kontennya.

Setelah semua impor yang diperlukan dilakukan dan properti ditetapkan, kami memberikan kontrol atas proses evaluasi ke .NET Core SDK yang sebenarnya, tempat semua operasi yang diperlukan dilakukan.

Kesimpulan


Mendukung Visual Studio 2019 umumnya lebih mudah daripada mendukung Visual Studio 2017 karena sejumlah alasan. Pertama, Microsoft tidak mengubah banyak hal seperti yang mereka miliki ketika memperbarui dari Visual Studio 2015 ke Visual Studio 2017. Ya, mereka memang mengubah toolset dasar dan memaksa plugin Visual Studio untuk beralih ke mode beban asinkron, tetapi perubahan ini tidak drastis itu. Kedua, kami sudah memiliki solusi siap pakai yang melibatkan alat kami sendiri dan mekanisme evaluasi proyek dan kami tidak perlu mengerjakan semuanya dari awal - hanya membangun apa yang sudah kami miliki. Proses analisis pendukung yang relatif tidak menyakitkan dari proyek .NET Core dalam kondisi baru (dan pada komputer tanpa salinan Visual Studio terpasang) dengan memperluas sistem evaluasi proyek kami juga memberi kami harapan bahwa kami telah membuat pilihan yang tepat dengan mengambil beberapa kendali dalam tangan kita.

Tetapi saya ingin mengulangi gagasan yang dikomunikasikan dalam artikel sebelumnya: kadang-kadang menggunakan solusi yang sudah jadi tidak semudah kelihatannya.

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


All Articles