Verifikasi kode sumber pustaka .NET Core oleh penganalisa statis PVS-Studio

Gambar 19

Pustaka .NET Core adalah salah satu proyek C # paling populer di GitHub. Tidak mengherankan, mengingat popularitas dan kegunaannya yang luas. Jauh lebih menarik untuk mencoba mencari tahu sudut-sudut gelap apa yang dapat ditemukan dalam kode sumber perpustakaan ini, yang akan kami coba lakukan menggunakan penganalisa statis PVS-Studio. Apakah Anda pikir Anda berhasil menemukan sesuatu yang menarik pada akhirnya?

Saya pergi ke artikel ini selama lebih dari satu setengah tahun. Pada titik tertentu, ada pikiran di benak saya bahwa perpustakaan .NET Core adalah berita menarik, dan memeriksanya akan menarik. Beberapa kali saya memeriksa proyek, analis menemukan semakin banyak tempat yang menarik, tetapi itu tidak melampaui bergulir cepat melalui daftar peringatan. Dan ini dia - selesai! Proyek diperiksa, artikelnya ada di depan Anda.

Lebih lanjut tentang proyek dan analisis


Jika Anda ingin terjun ke dalam analisis kode - Anda dapat melewati bagian ini, tetapi saya sangat ingin Anda membacanya - di sini saya berbicara sedikit lebih banyak tentang proyek dan penganalisa, serta sedikit tentang bagaimana saya menganalisis dan mereproduksi kesalahan.

Proyek yang Diaudit


Mungkin, akan mungkin untuk tidak memberi tahu apa itu CoreFX (.NET Core libraries), tetapi, jika Anda tidak mendengarnya, uraiannya ada di bawah ini. Saya tidak mengulanginya dan mengambilnya dari halaman proyek di GitHub , di mana Anda juga dapat mengunduh sumbernya.

Deskripsi: Repo ini berisi implementasi perpustakaan (disebut "CoreFX") untuk .NET Core. Ini termasuk System.Collections, System.IO, System.Xml, dan banyak komponen lainnya. Repo .NET Core Runtime yang sesuai (disebut "CoreCLR") berisi implementasi runtime untuk .NET Core. Ini termasuk RyuJIT, .NET GC, dan banyak komponen lainnya. Kode perpustakaan khusus-runtime (System.Private.CoreLib) hidup di repo CoreCLR. Itu perlu dibangun dan versi bersama-sama dengan runtime. Sisa CoreFX adalah agnostik implementasi runtime dan dapat dijalankan pada runtime .NET yang kompatibel (misalnya CoreRT) .

Alat analisis dan analisis yang digunakan


Saya memeriksa kode sumber menggunakan penganalisa statis PVS-Studio . Secara umum, PVS-Studio dapat menganalisis tidak hanya kode C #, tetapi juga C, C ++, Java. Analisis kode C # sejauh ini hanya berfungsi di bawah Windows, sedangkan kode di C, C ++, Java dapat Anda analisis di bawah Windows, Linux, macOS.

Saya biasanya menggunakan plugin PVS-Studio untuk Visual Studio untuk menguji proyek C # (versi 2010-2019 didukung), karena ini mungkin cara termudah dan paling mudah untuk menganalisis: membuka solusi, memulai analisis, bekerja dengan daftar peringatan. Namun, dengan CoreFX, segalanya menjadi sedikit lebih rumit.

Faktanya adalah bahwa proyek tidak memiliki file .sln tunggal, oleh karena itu, membukanya di Visual Studio dan melakukan analisis penuh menggunakan plug-in PVS-Studio akan gagal. Mungkin, itu bagus - saya tidak benar-benar tahu bagaimana Visual Studio akan berurusan dengan solusi sebesar ini.

Namun, tidak ada masalah dengan analisis, karena kit distribusi PVS-Studio menyertakan versi baris perintah dari penganalisa untuk proyek MSBuild (dan, pada kenyataannya, .sln). Semua yang diminta dari saya adalah menulis skrip kecil yang akan menjalankan "PVS-Studio_Cmd.exe" untuk masing-masing .sln di direktori CoreFX dan menempatkan hasil analisis dalam direktori terpisah (ditunjukkan oleh bendera peluncuran penganalisis).

Voila! - di pintu keluar saya memiliki satu set log, yang memiliki banyak hal menarik. Jika diinginkan, log dapat dikombinasikan menggunakan utilitas PlogConverter, yang dilengkapi dengan kit distribusi. Tetapi lebih nyaman bagi saya untuk bekerja dengan log individual, jadi saya tidak mulai menggabungkannya.

Ketika menjelaskan beberapa kesalahan, saya merujuk pada dokumentasi dari docs.microsoft.com dan paket NuGet yang tersedia untuk diunduh dari nuget.org. Saya akui bahwa kode yang dijelaskan dalam dokumentasi / ditemukan dalam paket mungkin sedikit berbeda dari yang dianalisis. Namun demikian, akan sangat aneh jika, misalnya, dalam dokumentasi tidak ada deskripsi pengecualian yang dihasilkan untuk sejumlah data input, dan dalam versi baru paket mereka akan muncul - setuju, ini akan menjadi kejutan yang meragukan. Reproduksi kesalahan dalam paket dari NuGet pada data input yang sama yang digunakan untuk debugging perpustakaan menunjukkan bahwa masalahnya bukan hal baru, dan, yang lebih penting, bahwa hal itu dapat "disentuh" ​​tanpa membangun proyek dari sumber.

Jadi, dengan asumsi kemungkinan beberapa kode teoritis salah-sinkronisasi, saya merasa diizinkan untuk merujuk pada deskripsi metode yang sesuai di docs.microsoft.com dan untuk mereproduksi masalah pada paket dari nuget.org.

Saya juga mencatat bahwa uraian tautan yang disediakan, serta informasi (komentar) dalam paket (dalam versi lain) dapat berubah selama penulisan artikel.

Proyek terbukti lainnya


Omong-omong, ini bukan artikel unik, kami menulis artikel lain tentang memeriksa proyek, daftar yang dapat ditemukan di sini . Selain itu, di situs kami telah mengumpulkan tidak hanya artikel tentang analisis proyek, tetapi juga berbagai artikel teknis tentang C, C ++, C #, Java, serta hanya catatan menarik. Anda dapat menemukan semua ini di blog .

Rekan saya sebelumnya menguji pustaka .NET Core pada tahun 2015. Hasil analisis sebelumnya dapat ditemukan di artikel terkait: " Tahun Baru memeriksa .NET Core Libraries (CoreFX) ."

Kesalahan ditemukan, tempat mencurigakan dan menarik


Seperti biasa, untuk minat yang lebih besar, saya sarankan Anda terlebih dahulu mencari kesalahan pada fragmen Anda sendiri, dan hanya kemudian membaca peringatan penganalisa dan deskripsi masalah.

Untuk kenyamanan, saya secara eksplisit membagi fragmen yang sedang dipertimbangkan satu sama lain menggunakan label formulir Issue N - lebih mudah untuk memahami di mana deskripsi satu kesalahan berakhir dan analisis yang lain dimulai. Ya, dan merujuk pada fragmen tertentu juga lebih mudah.

Edisi 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); } .... } .... } 

Peringatan PVS-Studio : V3095 Objek 'konteks' digunakan sebelum diverifikasi terhadap nol. Periksa baris: 340, 346. Principal.cs 340

Pengembang secara eksplisit menunjukkan bahwa nilai nol untuk parameter konteks tidak valid dan ingin menekankan ini dengan pengecualian tipe InvalidOperationException . Namun, sedikit lebih tinggi, dalam kondisi sebelumnya, ada dereference tanpa syarat dari tautan konteks - context.ContextType . Akibatnya, jika nilai konteksnya nol , pengecualian tipe NullReferenceException akan dilempar alih-alih InvalidOperationExcetion yang diharapkan.

Mari kita coba mereproduksi masalahnya. Hubungkan pustaka yang sesuai ( System.DirectoryServices.AccountManagement ) ke proyek dan jalankan kode berikut:

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

GroupPrincipal adalah penerus Principal kelas abstrak, yang berisi implementasi metode Simpan yang kita butuhkan. Kami menjalankan kode untuk dieksekusi dan melihat apa yang harus dibuktikan.

Gambar 1


Untuk bersenang-senang, Anda dapat mencoba mengunduh paket yang sesuai dari NuGet dan mencoba mengulangi masalahnya dengan cara yang sama. Saya menginstal paket versi 4.5.0 dan mendapatkan hasil yang diharapkan.

Gambar 2


Edisi 2

 private SearchResultCollection FindAll(bool findMoreThanOne) { searchResult = null; DirectoryEntry clonedRoot = null; if (_assertDefaultNamingContext == null) { clonedRoot = SearchRoot.CloneBrowsable(); } else { clonedRoot = SearchRoot.CloneBrowsable(); } .... } 

PVS-Studio Warning : V3004 Pernyataan 'then' setara dengan pernyataan 'else'. DirectorySearcher.cs 629

Terlepas dari kebenaran kondisi _assertDefaultNamingContext == null , tindakan yang sama akan dilakukan, sejak saat itu dan cabang - cabang pernyataan if memiliki badan yang sama. Entah harus ada tindakan lain di beberapa cabang, atau Anda dapat menghilangkan pernyataan if agar tidak membingungkan para programmer dan penganalisa.

Edisi 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 Warning : V3095 Objek 'propertyNames' digunakan sebelum diverifikasi dengan null. Periksa baris: 990, 1004. DirectoryEntry.cs 990

Sekali lagi kita melihat prosedur aneh. Metode ini memiliki propertyNames! = Cek kosong , mis. pengembang memastikan sendiri bahwa metode mengembalikan nol . Berikut ini tepat di atas Anda dapat mengamati beberapa akses ke referensi yang berpotensi nol ini - propertyNames. Panjang dan propertyNames [i] . Hasilnya cukup dapat diprediksi - pengecualian tipe NullReferenceExcepption akan terjadi jika referensi nol dilewatkan ke metode.

Kebetulan RefreshCache adalah metode publik di kelas publik. Coba ulangi masalahnya? Untuk melakukan ini, sambungkan ke proyek perpustakaan yang diinginkan - System.DirectoryServices - dan tulis kode seperti ini:

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

Jalankan kode untuk dieksekusi dan lihat gambar yang diharapkan.

Gambar 3


Untuk bersenang-senang, Anda dapat mencoba mereproduksi masalah pada versi rilis paket NuGet. Kami menghubungkan paket System.DirectoryServices ke proyek NuGet (saya menggunakan versi 4.5.0) dan menjalankan kode yang sudah dikenal untuk eksekusi. Hasilnya lebih rendah.

Gambar 4


Masalah 4

Sekarang kita akan pergi dari kebalikannya - pertama coba tulis kode yang menggunakan instance kelas, lalu lihat ke dalam. Mari kita lihat struktur System.Drawing.CharacterRange dari perpustakaan System.Drawing.Common dan paket NuGet dengan nama yang sama.

Kode yang digunakan adalah sebagai berikut:

 CharacterRange range = new CharacterRange(); bool eq = range.Equals(null); Console.WriteLine(eq); 

Untuk berjaga-jaga, untuk menyegarkan memori, kita akan beralih ke docs.microsoft.com untuk mengingat kembali nilai pengembalian yang diharapkan dari objek ekspresi. Sama dengan (nol) :

Pernyataan berikut harus benar untuk semua implementasi metode Persamaan (Objek) . Dalam daftar, x, y, dan z mewakili referensi objek yang bukan nol.

....

x.Equals (null) mengembalikan false.

Apakah Anda pikir teks "Salah" akan ditampilkan di konsol? Tentu saja tidak, itu terlalu mudah. :) Kami mengeksekusi kode dan melihat hasilnya.

Gambar 5

Ini adalah kesimpulan ketika mengeksekusi kode di atas menggunakan paket NuGet System.Drawing.Common versi 4.5.1. Kami menjalankan kode yang sama dengan versi debug pustaka dan melihat yang berikut:

Gambar 6


Sekarang mari kita lihat kode sumber - implementasi metode Persamaan dalam struktur CharacterRange dan peringatan penganalisa:

 public override bool Equals(object obj) { if (obj.GetType() != typeof(CharacterRange)) return false; CharacterRange cr = (CharacterRange)obj; return ((_first == cr.First) && (_length == cr.Length)); } 

Peringatan PVS-Studio : V3115 Melewati metode 'null' ke 'Equals' tidak boleh menghasilkan 'NullReferenceException'. CharacterRange.cs 56

Kami melihat apa yang perlu kami buktikan - parameter obj diproses secara tidak akurat, karena itu pengecualian tipe NullReferenceException terjadi ketika memanggil metode instance GetType dalam ekspresi kondisional.

Edisi 5

Saat menjelajahi perpustakaan ini, pertimbangkan tempat lain yang menarik - metode Icon.Save . Sebelum belajar, lihat deskripsi metodenya.

Tidak ada deskripsi metode ini:

Gambar 7

Kami beralih ke docs.microsoft.com - " Icon.Save (Stream) Method ". Namun, tidak ada batasan pada nilai input dan tidak ada informasi tentang pengecualian yang dihasilkan.

Sekarang mari kita beralih ke penelitian kode.

 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"); .... } } .... } 

Peringatan PVS-Studio : V3095 Objek 'outputStream' digunakan sebelum diverifikasi dengan null. Periksa baris: 654, 672. Icon.Windows.cs 654

Lagi-lagi, cerita yang sudah kita ketahui adalah kemungkinan referensi referensi nol, karena parameter metode didereferensi tanpa memeriksa nol . Sekali lagi, kombinasi keadaan yang baik - baik kelas dan metodenya - bersifat publik, yang berarti Anda dapat mencoba mereproduksi masalah.

Tugasnya sederhana - untuk membawa eksekusi kode ke ekspresi outputStream.Write (_iconData, 0, _iconData.Length); sambil mempertahankan nilai variabel outputStream - null . Untuk melakukan ini, cukup bahwa kondisi _iconData! = Null puas.

Mari kita lihat konstruktor publik paling sederhana:

 public Icon(string fileName) : this(fileName, 0, 0) { } 

Dia hanya mendelegasikan pekerjaan ke konstruktor lain. Nah, lihat lebih jauh - konstruktor yang digunakan di sini.

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

Ini dia, apa yang kamu butuhkan. Setelah memanggil konstruktor ini, jika kita berhasil membaca data dari file dan jika tidak ada crash terjadi dalam metode Inisialisasi , bidang _iconData akan berisi tautan ke beberapa objek, yang merupakan apa yang kita butuhkan.

Ternyata untuk mereproduksi masalah, Anda perlu membuat turunan dari kelas Ikon dengan ikon yang sebenarnya dan kemudian memanggil metode Simpan , melewati nol sebagai argumen, yang akan kami lakukan. Kode tersebut dapat terlihat, misalnya, sebagai berikut:

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

Hasil eksekusi diharapkan.

Gambar 8

Edisi 6

Kami melanjutkan ulasan dan pergi ke perpustakaan System.Management . Cobalah untuk menemukan 3 perbedaan antara tindakan yang dilakukan dalam kasus CimType.UInt32 dan yang lainnya.

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

Tentu saja, tidak ada perbedaan, yang diperingatkan oleh penganalisa.

Peringatan PVS-Studio : V3139 Dua atau lebih cabang kasus melakukan tindakan yang sama. WMIGenerator.cs 5220

Gaya kode ini tidak terlalu jelas bagi saya secara pribadi. Jika tidak ada kesalahan di sini, saya pikir tidak ada gunanya menyebarkan logika yang sama ke berbagai kasus.

Edisi 7

Perpustakaan Microsoft.CSharp .

 private static IList<KeyValuePair<string, object>> QueryDynamicObject(object obj) { .... List<string> names = new List<string>(mo.GetDynamicMemberNames()); names.Sort(); if (names != null) { .... } .... } 

Peringatan PVS-Studio : V3022 Ekspresi 'nama! = Null' selalu benar. DynamicDebuggerProxy.cs 426

Saya mungkin bisa mengabaikan peringatan ini bersama dengan banyak peringatan serupa yang dikeluarkan oleh diagnostik V3022 dan V3063 . Ada banyak (sangat banyak) cek aneh, tetapi ini entah bagaimana tenggelam ke dalam jiwaku. Ada kemungkinan bahwa sebelum membandingkan nama variabel lokal dengan nol ke variabel ini, tidak hanya referensi ke objek yang baru dibuat ditulis, itu juga memanggil metode instance Sort . Ini bukan kesalahan, tentu saja, tetapi tempat ini menarik, seperti bagi saya.

Edisi 8

Ini potongan kode lain yang menarik.

 private static void InsertChildNoGrow(Symbol child) { .... while (sym?.nextSameName != null) { sym = sym.nextSameName; } Debug.Assert(sym != null && sym.nextSameName == null); sym.nextSameName = child; .... } 

Peringatan PVS-Studio : V3042 Kemungkinan NullReferenceException. '?.' dan '.' operator digunakan untuk mengakses anggota objek 'sym' SymbolStore.cs 56

Lihat apa yang ada di sini. Siklus berakhir ketika salah satu dari dua kondisi terpenuhi:

  • sym == null ;
  • sym.nextSameName == null .

Tidak ada masalah dengan kondisi kedua, yang tidak dapat dikatakan tentang yang pertama, karena di bawah ini adalah panggilan tanpa syarat ke bidang instance nextSameName dan, jika sym adalah nol , pengecualian dari tipe NullReferenceException akan dilemparkan selama panggilan .

"Apakah kamu buta? Ada juga panggilan ke Debug.Assert , di mana dicentang bahwa sym! = Null "- seseorang mungkin keberatan. Tapi itu semua garam! Saat bekerja di versi Rilis Debug.Assert, tidak ada yang akan membantu, dan dengan keadaan yang dijelaskan di atas, semua yang kita dapatkan adalah NullReferenceException . Selain itu, saya sudah melihat kesalahan serupa di proyek lain dari Microsoft - Roslyn , di mana ada situasi yang sangat mirip dengan Debug.Assert . Sedikit terganggu oleh Roslyn dengan izin Anda.

Masalahnya dapat direproduksi baik menggunakan perpustakaan Microsoft.CodeAnalysis , atau langsung di Visual Studio menggunakan Syntax Visualizer. Pada Visual Studio versi 16.1.6 + Sintaks Visualizer 1.0, masalah ini masih mereproduksi.

Untuk mereproduksi, kode tersebut cukup:

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

Selanjutnya, dalam Sintaks Visualizer, Anda perlu menemukan simpul pohon sintaks tipe ConstantPatternSyntax , yang sesuai dengan nol dalam kode, dan meminta TypeSymbol untuk itu.

Gambar 9

Setelah itu, Visual Studio akan reboot. Jika kita masuk ke Peraga Peristiwa, kita akan menemukan informasi tentang masalah di perpustakaan:

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

Dan tentang masalah dengan 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 .... 

Memiliki versi debug pustaka Roslyn, Anda dapat menemukan tempat di mana pengecualian terjadi:

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

Di sini, seperti dalam kode di atas dari perpustakaan .NET Core, ada juga pemeriksaan melalui Debug.Assert , yang, bagaimanapun, tidak membantu dengan cara apa pun ketika menggunakan versi rilis dari perpustakaan.

Edisi 9

Sedikit terganggu - dan itu sudah cukup, kembali ke perpustakaan .NET Core. Paket System.IO.IsolatedStorage berisi kode menarik berikut.

 private bool ContainsUnknownFiles(string directory) { .... return (files.Length > 2 || ( (!IsIdFile(files[0]) && !IsInfoFile(files[0]))) || (files.Length == 2 && !IsIdFile(files[1]) && !IsInfoFile(files[1])) ); } 

Peringatan PVS-Studio : V3088 Ekspresi tertutup oleh tanda kurung dua kali: ((ekspresi)). Sepasang tanda kurung tidak perlu atau salah cetak ada. IsolatedStorageFile.cs 839

Mengatakan bahwa pemformatan kode membingungkan adalah tidak mengatakan apa-apa. Melirik sekilas kode ini, saya akan mengatakan bahwa operan kiri dari operator pertama || - file. Panjang> 2 , yang benar adalah bahwa dalam tanda kurung. Setidaknya kodenya diformat seperti ini. Melihat sedikit lebih dekat, Anda dapat memahami bahwa ini tidak benar. Bahkan, operan yang tepat adalah ((! IsIdFile (file [0]) &&! IsInfoFile (file [0])))) . Menurut pendapat saya, kode ini cukup membingungkan.

Edisi 10

Dalam rilis PVS-Studio 7.03, aturan diagnostik V3138 ditambahkan, yang mencari kesalahan dalam baris interpolasi. Lebih tepatnya, di baris yang paling mungkin diinterpolasi, tetapi karena karakter $ yang hilang, mereka tidak. Pustaka System.Net menemukan beberapa respons menarik terhadap aturan diagnostik ini.

 internal static void CacheCredential(SafeFreeCredentials newHandle) { try { .... } catch (Exception e) { if (!ExceptionCheck.IsFatal(e)) { NetEventSource.Fail(null, "Attempted to throw: {e}"); } } } 

Peringatan PVS-Studio : V3138 String literal berisi ekspresi potensial yang diinterpolasi. Pertimbangkan untuk memeriksa: e. SSPIHandleCache.cs 42

Sangat mungkin bahwa argumen kedua ke metode Gagal harus berupa string yang diinterpolasi, di mana representasi string dari pengecualian e akan diganti. Namun, karena karakter $ yang hilang, tidak ada representasi string dari pengecualian dilemparkan.

Edisi 11

Saya bertemu kasus serupa lainnya.

 public static async Task<string> GetDigestTokenForCredential(....) { .... if (NetEventSource.IsEnabled) NetEventSource.Error(digestResponse, "Algorithm not supported: {algorithm}"); .... } 

Peringatan PVS-Studio : V3138 String literal berisi ekspresi potensial yang diinterpolasi. Pertimbangkan untuk memeriksa: algoritma. AuthenticationHelper.Digest.cs 58

Situasinya mirip dengan yang dijelaskan di atas, simbol $ dilewati lagi - baris yang salah menuju ke metode Kesalahan .

Edisi 12

Paket System.Net.Mail . Metode ini kecil, saya akan membawanya secara keseluruhan sehingga kesalahannya sedikit lebih menarik.

 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 Warning : V3008 Variabel '_streamSet' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 123, 119. MimePart.cs 123

Penugasan ganda dari nilai variabel _streamSet terlihat aneh (pertama, di bawah kondisi; lalu di luar). Kisah yang sama dengan mem -zeroing variabel _stream . Akibatnya, _stream akan tetap disetel ke streaming , dan _streamSet ke true .

Masalah 13

Tempat yang menarik dari pustaka System.Linq.Expressions , yang mana penganalisa segera mengeluarkan 2 peringatan. Dalam hal ini, ini lebih merupakan fitur daripada bug, tetapi metode ini sangat menarik ...

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

Peringatan PVS-Studio :

  • V3010 Nilai balik fungsi 'GetType' harus digunakan. Instruction.cs 36
  • V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'o'. Instruction.cs 36

Mungkin tidak ada komentar.

Gambar 20

Edisi 14

Mari kita lihat kasus lain yang dengannya kita akan bekerja "dari luar." Pertama, kami akan menulis kode, mengidentifikasi masalah, dan kemudian mencari ke dalam. Untuk belajar, ambil perpustakaan System.Configuration.ConfigurationManager dan paket NuGet dengan nama yang sama. Saya menggunakan paket versi 4.5.0. Kami akan bekerja dengan kelas System.Configuration.CommaDelimitedStringCollection .

Mari kita lakukan sesuatu yang tidak terlalu rumit. Misalnya, buat objek, ekstrak representasi stringnya, dapatkan panjang string ini dan cetaklah. Kode yang relevan:

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

Untuk berjaga-jaga, lihat deskripsi metode ToString :

Gambar 11

Tidak ada yang aneh - representasi string dari objek hanya dikembalikan. Untuk berjaga-jaga, saya juga melihat docs.microsoft.com - " CommaDelimitedStringCollection.ToString Method ". Sepertinya tidak ada yang istimewa juga.

Oke, jalankan kode untuk dieksekusi, ii ...

Gambar 12

Hmm, tanpa diduga. Baiklah, mari kita coba menambahkan elemen ke koleksi, dan kemudian dapatkan representasi string-nya. "Benar-benar tidak sengaja" kami akan menambahkan string kosong :). Kode akan berubah dan terlihat seperti ini:

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

Kami meluncurkannya dan melihat ...

Gambar 13

Apa lagi? Baiklah, mari kita lihat implementasi metode ToString dari kelas CommaDelimitedStringCollection . Kode disajikan di bawah ini:

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

Peringatan PVS-Studio :

  • V3108 Tidak disarankan untuk mengembalikan 'null' dari metode 'ToSting ()'. StringAttributeCollection.cs 57
  • V3108 Tidak disarankan untuk mengembalikan 'null' dari metode 'ToSting ()'. StringAttributeCollection.cs 71

Di sini kita melihat 2 tempat di mana implementasi ToString saat ini dapat mengembalikan nol . Ingat apa yang disarankan Microsoft ketika menerapkan metode ToString , dan kami kembali ke docs.microsoft.com - " Object.ToString Method ":

Notes to Inheritors .... Overrides dari metode ToString () harus mengikuti panduan ini:
  • ....
  • Penggantian ToString () Anda tidak boleh mengembalikan string Kosong atau null .
  • ....

Sebenarnya, inilah yang diperingatkan PVS-Studio. Dua cuplikan kode di atas yang kami tulis untuk mereproduksi masalah mencapai titik keluar yang berbeda - tempat pertama dan kedua tempat null kembali, masing-masing. Menggali lebih dalam.

Kasus pertama. Hitung - properti StringCollection kelas dasar. Karena tidak ada elemen yang ditambahkan, Hitung == 0 , kondisi Hitung <= 0 terpenuhi, null dikembalikan.

Dalam kasus kedua, kami menambahkan elemen menggunakan metode instance CommaDelimitedStringCollection.Add untuk ini.

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

Cek dalam metode ThrowIf ... berhasil dilakukan dan item ditambahkan ke koleksi dasar. Dengan demikian, nilai Hitung menjadi sama dengan 1. Sekarang kita kembali ke metode ToString . Nilai dari ekspresi Count <= 0 adalah false , oleh karena itu, tidak ada jalan keluar dari metode dan kode terus dieksekusi. Traversal koleksi internal dimulai, dan 2 elemen ditambahkan ke StringBuilder - string kosong dan koma. Akibatnya, ternyata sb hanya berisi koma, nilai properti Panjang , masing-masing, sama dengan satu. Nilai dari ekspresi sb.Length> 0 adalah true , kurangi dan tuliskan ke sb.Length dilakukan , sekarang nilai sb.Length adalah 0. Ini mengarah pada fakta bahwa metode mengembalikan nol lagi.

Edisi 15

Secara tak terduga, saya ingin menggunakan kelas System.Configuration.ConfigurationProperty . Ambil konstruktor dengan parameter terbanyak:

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

Mari kita lihat deskripsi parameter terakhir:

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

Deskripsi konstruktor di docs.microsoft.com mengatakan hal yang sama. Baiklah, mari kita lihat bagaimana parameter ini digunakan dalam tubuh konstruktor:

 public ConfigurationProperty(...., string description) { ConstructorInit(name, type, options, validator, typeConverter); SetDefaultValue(defaultValue); } 

Dan parameternya tidak digunakan.

Peringatan PVS-Studio : V3117 'Deskripsi' parameter konstruktor tidak digunakan. ConfigurationProperty.cs 62

Mereka mungkin tidak menggunakannya dengan sengaja, tetapi deskripsi parameter yang sesuai membingungkan.

Edisi 16

Saya bertemu tempat serupa lainnya. Coba cari kesalahan sendiri, kode konstruktornya ada di bawah.

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

Peringatan PVS-Studio : Parameter konstruktor V3117 'configSourceStreamVersion' tidak digunakan. SectionXmlInfo.cs 16

Ada properti yang sesuai, meskipun terlihat agak aneh:

 internal object ConfigSourceStreamVersion { set { } } 

Secara umum, kode tersebut terlihat mencurigakan. Mungkin parameter / properti dibiarkan untuk kompatibilitas, tetapi ini hanya dugaan saya.

Masalah 17

Mari kita lihat apa yang menarik ditemukan dalam kode System.Runtime.WindowsRuntime.UI.Xaml library dan paket NuGet dengan nama yang sama.
 public struct RepeatBehavior : IFormattable { .... public override string ToString() { return InternalToString(null, null); } .... } 

Peringatan PVS-Studio : V3108 Tidak disarankan untuk mengembalikan 'null' dari metode 'ToSting ()'. RepeatBehavior.cs 113

Kisah yang sudah dikenal yang telah berlalu - metode ToString dapat mengembalikan nol . Karena itu, penulis kode panggilan, dengan asumsi bahwa RepeatBehavior.ToString selalu mengembalikan referensi yang tidak nol, mungkin akan terkejut pada beberapa titik. Dan lagi, ini adalah penyimpangan dari pedoman Microsoft.

Tentu saja, hanya dari metode ini tidak jelas bahwa ToString dapat mengembalikan nol - Anda perlu menggali lebih dalam dan melihat ke metode 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; } } 

Penganalisa menemukan bahwa jika cabang default dieksekusi di switch , InternalToString akan mengembalikan nol , oleh karena itu, null akan mengembalikan ToString . RepeatBehavior adalah struktur publik, dan ToString adalah metode publik, sehingga Anda dapat mencoba mereproduksi masalah dalam praktik. Untuk melakukan ini, buat instance RepeatBehavior , panggil metode ToString di atasnya , tetapi ingat bahwa _Type tidak boleh sama dengan RepeatBehaviorType.Forever , RepeatBehaviorType.Count atau

UlangiBehaviorTipe . Durasi .

_Type adalah bidang pribadi yang dapat ditetapkan melalui properti publik:

 public struct RepeatBehavior : IFormattable { .... private RepeatBehaviorType _Type; .... public RepeatBehaviorType Type { get { return _Type; } set { _Type = value; } } .... } 

Sejauh ini, semuanya terlihat bagus. Silakan dan lihat apa tipe RepeatBehaviorType .

 public enum RepeatBehaviorType { Count, Duration, Forever } 

Seperti yang Anda lihat, RepeatBehaviorType adalah enumerasi yang berisi ketiga elemen. Selain itu, ketiga elemen ini tercakup dalam ekspresi switch yang diperlukan . Namun, ini tidak berarti bahwa cabang default tidak dapat dijangkau.

Untuk mereproduksi masalah, hubungkan paket System.Runtime.WindowsRuntime.UI.Xaml (saya menggunakan versi 4.3.0) ke proyek dan jalankan kode berikut.

 RepeatBehavior behavior = new RepeatBehavior() { Type = (RepeatBehaviorType)666 }; Console.WriteLine(behavior.ToString() is null); 

Benar diharapkan menjadi output ke konsol , yang berarti ToString dikembalikan nol , karena _Type tidak sama dengan nilai apa pun di cabang case , dan kontrol diteruskan ke cabang default . Apa yang sebenarnya kita cari.

Saya juga ingin mencatat bahwa baik dalam komentar pada metode ini, maupun pada docs.microsoft.com , diindikasikan bahwa metode tersebut dapat mengembalikan nol .

Edisi 18

Selanjutnya, kami akan memeriksa beberapa peringatan dari 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, .... }; 

Peringatan PVS-Studio :

  • V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' operator. XmlUTF8TextReader.cs 56
  • V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' operator. XmlUTF8TextReader.cs 58
  • V3001 There are identical sub-expressions 'CharType.Comment' to the left and to the right of the '|' operator. XmlUTF8TextReader.cs 64

Penganalisa menemukan penggunaan ekspresi CharType.Comment menjadi curiga | CharType.Comment . Terlihat agak aneh sejak (CharType.Comment | CharType.Comment) == CharType.Comment . Saat menginisialisasi elemen lain dari array yang menggunakan CharType.Comment , tidak ada duplikasi seperti itu.

Edisi 19

Lanjutan. Mari kita lihat informasi tentang nilai yang dikembalikan dari metode XmlBinaryWriterSession.TryAdd dalam deskripsi metode dan pada docs.microsoft.com - " XmlBinaryWriterSession.TryAdd (XmlDictionaryString, Int32) Metode ": Pengembalian: true jika string dapat ditambahkan; jika tidak, salah.

Sekarang mari kita melihat isi dari metode ini:

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

Peringatan PVS-Studio : V3009 Aneh bahwa metode ini selalu mengembalikan nilai yang sama dan 'benar'. XmlBinaryWriterSession.cs 29

Tampaknya aneh bahwa metode ini mengembalikan true atau melempar pengecualian, tetapi tidak pernah mengembalikan false .

Edisi 20 saya

bertemu kode dengan masalah yang sama, tapi sekarang sebaliknya - selalu mengembalikan false :

 internal virtual bool OnHandleReference(....) { if (xmlWriter.depth < depthToCheckCyclicReference) return false; if (canContainCyclicReference) { if (_byValObjectsInScope.Contains(obj)) throw ....; _byValObjectsInScope.Push(obj); } return false; } 

Peringatan PVS-Studio : V3009 Aneh bahwa metode ini selalu mengembalikan satu dan nilai 'false' yang sama. XmlObjectSerializerWriteContext.cs 415

Jadi, kita telah menempuh perjalanan panjang! Karena itu, sebelum melanjutkan, saya mengusulkan untuk istirahat sejenak - untuk meregangkan otot Anda, berjalan sedikit, istirahatkan mata Anda, lihat ke luar jendela ...

Gambar 21

Saya berharap bahwa pada titik ini Anda lagi penuh energi, jadi mari kita lanjutkan. :)

Edisi 21

Mari kita lihat tempat-tempat menarik dalam proyek 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; } } 

Peringatan PVS-Studio : V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'hasher'. PKCS1MaskGenerationMethod.cs 37

Penganalisis memperingatkan bahwa ketika mengevaluasi ekspresi hasher.TransformBlock, nilai variabel hasher bisa nol , dalam hal ini NullReferenceException akan dilempar . Kemunculan peringatan ini dimungkinkan berkat analisis antar-prosedur. Jadi, untuk memahami jika hasher dalam kasus ini bisa nol , Anda harus turun ke metode CreateFromName .



 public static object CreateFromName(string name) { return CreateFromName(name, null); } 

Sejauh ini, tidak ada - kita melangkah lebih dalam. Tubuh dari CreateFromName versi overload dengan dua parameter cukup besar, jadi saya membawa versi singkat.

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

Seperti yang Anda lihat, ada beberapa titik keluar dalam metode di mana null dikembalikan secara eksplisit . Oleh karena itu, setidaknya secara teoritis, dalam metode yang disebutkan sebelumnya, di mana peringatan itu dikeluarkan, pengecualian tipe NullReferenceException dapat terjadi .

Teori itu baik, tetapi mari kita coba mereproduksi masalah dalam praktik. Untuk melakukan ini, lihat lagi metode asli dan catat poin-poin penting. Kode yang tidak relevan dari metode ini dapat direduksi.

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

Mari kita bahas poin-poin penting lebih terinci:

1, 3 . Kelas dan metode memiliki pengubah akses publik . Oleh karena itu, antarmuka ini tersedia saat menghubungkan perpustakaan - Anda dapat mencoba mengulangi masalahnya.

2 . Kelas - contoh non-abstrak, memiliki konstruktor publik - harus mudah untuk membuat contoh yang dengannya kita akan bekerja. Dalam beberapa kasus yang saya teliti, kelasnya abstrak, jadi untuk mengulangi masalah saya masih harus mencari ahli waris dan cara mendapatkannya.

4 . CreateFromName tidak boleh membuang pengecualian dan harus mengembalikan nol - poin terpenting, kami akan kembali lagi nanti.

5, 6 . Nilai CbReturnharus> 0 (dan, tentu saja, dalam batas yang memadai untuk pembuatan array yang berhasil). Pemenuhan kondisi cbReturn> 0 diperlukan untuk memenuhi kondisi lebih lanjut ib <rgbT.Panjang dan masukkan loop body.

7 . Helpres.ConvertIntToByteArray harus berfungsi tanpa kecuali.

Untuk memenuhi kondisi yang bergantung pada parameter metode, cukup dengan memberikan argumen yang memadai, misalnya:

  • rgbCeed - byte baru [] {0, 1, 2, 3};
  • cbReturn - 42.

Untuk “berkompromi” dengan metode CryptoConfig.CreateFromName , Anda harus dapat mengubah nilai bidang _hashNameValue . Untungnya bagi kami, itu ada, karena properti wrapper didefinisikan di kelas di atas bidang ini:
 public string HashName { get { return _hashNameValue; } set { _hashNameValue = value ?? DefaultHash; } } 

Dengan menetapkan nilai 'sintetis' untuk HashName (lebih tepatnya, _hashNameValue ), Anda bisa mendapatkan null dari metode CreateFromName di titik pengembalian pertama yang kami tandai. Saya tidak akan masuk ke rincian parsing metode ini (saya harap Anda akan memaafkan saya untuk ini), karena ini cukup lama.

Akibatnya, kode yang akan membuang pengecualian dari tipe NullReferenceException mungkin terlihat seperti ini:

 PKCS1MaskGenerationMethod tempObj = new PKCS1MaskGenerationMethod(); tempObj.HashName = "Dummy"; tempObj.GenerateMask(new byte[] { 1, 2, 3 }, 42); 

Kami menghubungkan perpustakaan debug ke proyek, menjalankannya dan mendapatkan hasil yang diharapkan:

Gambar 10


Demi kepentingan, saya mencoba untuk mengeksekusi kode yang sama pada paket NuGet versi 4.3.1.

Gambar 14


Informasi tentang pengecualian yang dihasilkan, batasan pada parameter output dalam deskripsi metode, tidak dijelaskan pada docs.microsoft.com - " PKCS1MaskGenerationMethod.GenerateMask (Byte [], Int32) Method ".

Ngomong-ngomong, sudah selama penulisan artikel dan deskripsi dari urutan pemutaran masalah, saya menemukan 2 cara lagi untuk "memecah" metode ini:

  • berikan nilai terlalu besar sebagai argumen ke cbReturn ;
  • lulus sebagai rgbSeed nilai nol .

Dalam kasus pertama, kami mendapatkan pengecualian dari tipe OutOfMemoryException .

Gambar 15

Dalam kasus kedua, kita mendapatkan pengecualian dari tipe NullReferenceException ketika ekspresi rgbSeed.Length dieksekusi . Dalam hal ini, penting bahwa hasher memiliki nilai bukan nol, jika tidak, utas eksekusi tidak akan mencapai rgbSeed . Panjang .

Edisi 22

Bertemu pasangan lebih banyak tempat yang serupa.

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

Peringatan PVS-Studio :

  • V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'item'. SignatureDescription.cs 31
  • V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'item'. SignatureDescription.cs 38

Sekali lagi, kita bisa menulis nilai ke properti FormatterAlgorithm dan DeformatterAlgorithm yang mana metode CryptoConfig.CreateFromName mengembalikan null dalam metode CreateDeformatter dan CreateFormatter . Selanjutnya, saat memanggil metode instance SetKey, NullReferenceException akan dibuang . Masalahnya, sekali lagi, mudah direproduksi dalam praktik:

 SignatureDescription signature = new SignatureDescription() { DeformatterAlgorithm = "Dummy", FormatterAlgorithm = "Dummy" }; signature.CreateDeformatter(null); // NRE signature.CreateFormatter(null); // NRE 

Dalam hal ini, saat memanggil CreateDeformatter , dan saat memanggil CreateFormatter, pengecualian tipe NullReferenceException dilemparkan .

Masalah 23

Mari kita lihat tempat-tempat menarik dari proyek 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 Warning : V3004 Pernyataan 'then' setara dengan pernyataan 'else'. QueryOutputWriterV1.cs 242

terlihat aneh bahwa kemudian dan lain Operator cabang jika mengandung kode yang sama. Entah ada kesalahan dan tindakan lain seharusnya di salah satu cabang, atau pernyataan jika bisa dihilangkan.

Edisi 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)) { .... } .... } 

Peringatan PVS-Studio : V3003 Penggunaan pola 'if (A) {...} else if (A) {...}' terdeteksi. Ada kemungkinan kehadiran kesalahan logis. Periksa baris: 381, 396. ImportContext.cs 381

Urutan if-else-if memiliki dua ekspresi kondisional yang identik - konten adalah XmlSchemaSimpleTypeRestriction . Yang lebih menarik - tubuh maka -branches dari operator yang sesuai mengandung yang berbeda ekspresi. Dengan satu atau lain cara, tubuh cabang pertama yang bersesuaian kemudian akan dieksekusi (jika ekspresi kondisional benar), atau tidak satu atau yang lain jika ekspresi yang sesuai salah.

Edisi 25

Untuk membuatnya lebih menarik untuk mencari kesalahan dalam metode berikut, saya membawa seluruh tubuhnya.

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

Jika Anda berhasil, selamat!
Jika tidak, peringatan PVS-Studio akan membantu: V3102 Akses mencurigakan ke elemen objek 'seq' dengan indeks konstan di dalam sebuah loop. XmlQueryRuntime.cs 738

A untuk loop dijalankan , menggunakan ekspresi i <seq.Count sebagai kondisi keluar . Menyarankan bahwa mereka ingin memotong urutan seq . Tetapi hanya dalam loop mereka mengakses elemen-elemen dari urutan tidak menggunakan penghitung ( seq [i] ), tetapi menggunakan literal numerik - nol ( seq [0] ).

Masalah 26

Kesalahan berikut cocok dalam sepotong kecil kode, tetapi karena itu tidak kalah menarik.

 public override void WriteValue(string value) { WriteValue(value); } 

PVS-Studio Warning : V3110 Kemungkinan rekursi tak terbatas di dalam metode 'WriteValue'. XmlAttributeCache.cs 166

Metode ini memanggil dirinya sendiri, sehingga membentuk rekursi tanpa syarat untuk keluar.

Edisi 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 Warning : V3095 Objek 'seq' digunakan sebelum diverifikasi dengan null. Periksa baris: 880, 884. XmlQueryRuntime.cs 880

Sebagai argumen, metode ini dapat menerima nilai nol , itulah sebabnya ketika memanggil properti Count pengecualian dari tipe NullReferenceException akan dibuang . Di bawah ini kami memeriksa variabel nodeSeq yang diperoleh sebagai hasil dari pengecoran seq secara eksplisit , tetapi mengapa ada tidak begitu jelas. Jika seq adalah nol , utas tidak akan mencapai pemeriksaan ini karena pengecualian. Jika nilai seq bukan nol , maka:

  • pengecualian tipe InvalidCastException akan dilemparkan jika para pemain gagal;
  • jika pemeran berhasil, nodeSeq pasti memiliki nilai selain nol .

Edisi 28

Met 4 desainer di mana ada parameter yang tidak digunakan. Mereka mungkin dibiarkan kompatibilitas, tetapi saya belum menemukan komentar mengenai parameter yang tidak digunakan ini.

Peringatan PVS-Studio :

  • Parameter Constructor V3117 'securityUrl' tidak digunakan. XmlSecureResolver.cs 15
  • Parameter konstruktor 'strdata' V3117 tidak digunakan. XmlEntity.cs 18
  • 'Lokasi' parameter konstruktor V3117 tidak digunakan. Compilation.cs 58
  • 'Akses' parameter konstruktor V3117 tidak digunakan. XmlSerializationILGen.cs 38

Yang pertama paling menarik minat saya (setidaknya dia yang saya tulis di daftar peringatan untuk artikel). Apa?Tidak yakin Mungkin namanya.

 public XmlSecureResolver(XmlResolver resolver, string securityUrl) { _resolver = resolver; } 

Demi menarik, saya pergi untuk melihat apa yang mereka tulis di docs.microsoft.com - " XmlSecureResolver Constructors " tentang parameter securityUrl :

URL yang digunakan untuk membuat PermissionSet yang akan diterapkan pada XmlResolver yang mendasarinya. XmlSecureResolver memanggil PermitOnly () pada PermissionSet yang dibuat sebelum memanggil GetEntity (Uri, String, Type) pada XmlResolver yang mendasarinya.

Masalah 29

Dalam paket System.Private.Uri, saya menemukan metode yang menimbulkan sedikit konflik dengan pedoman Microsoft untuk mengganti metode ToString . Ingat kembali salah satu tips dari halaman Object.ToString Method : Override ToString Anda () seharusnya tidak memberikan pengecualian .

Metode yang diganti sendiri terlihat seperti ini:

 public override string ToString() { if (_username.Length == 0 && _password.Length > 0) { throw new UriFormatException(SR.net_uri_BadUserPassword); } .... } 

Peringatan PVS-Studio : V3108 Tidak disarankan untuk melempar pengecualian dari metode 'ToSting ()'. UriBuilder.cs 406

Kode yang menetapkan string kosong untuk bidang _username dan baris tidak kosong untuk bidang _password , masing-masing, melalui properti publik UserName dan Kata Sandi , dan kemudian memanggil metode ToString , akan mendapatkan pengecualian. Contoh kode seperti itu:

 UriBuilder uriBuilder = new UriBuilder() { UserName = String.Empty, Password = "Dummy" }; String stringRepresentation = uriBuilder.ToString(); Console.WriteLine(stringRepresentation); 

Tetapi dalam kasus ini, para pengembang dengan jujur ​​memperingatkan bahwa pengecualian dapat dilemparkan ketika dipanggil - ini dijelaskan baik dalam komentar pada metode dan pada docs.microsoft.com - " UriBuilder.ToString Method ".

Masalah 30

Lihatlah peringatan yang dikeluarkan pada kode proyek System.Data.Common .

 private ArrayList _tables; private DataTable GetTable(string tableName, string ns) { .... if (_tables.Count == 0) return (DataTable)_tables[0]; .... } 

PVS-Studio Warning : V3106 Kemungkinan indeks di luar batas. Indeks '0' menunjuk di luar batas '_tables'. XMLDiffLoader.cs 277 Apakah

kode ini terlihat tidak biasa? Menurutmu ini apa? Cara yang bagus untuk melempar pengecualian tipe ArgumentOutOfRangeException ? Saya mungkin tidak akan terkejut dengan pendekatan ini. Secara umum, kode ini aneh dan mencurigakan.

Edisi 31

 internal XmlNodeOrder ComparePosition(XPathNodePointer other) { RealFoliate(); other.RealFoliate(); Debug.Assert(other != null); .... } 

Peringatan PVS-Studio : V3095 Objek 'lain' digunakan sebelum diverifikasi terhadap nol. Periksa baris: 1095, 1096. XPathNodePointer.cs 1095

Expression lain = null! Sebagai argumen untuk Debug.Assert jelas petunjuk bahwa sebagai argumen ComparePosition bisa mendapatkan nilai nol . Setidaknya, mereka ingin menangkap kasus seperti itu. Tetapi pada saat yang sama, baris di atas yang lain memanggil metode instance RealFoliate . Akibatnya, jika yang lain adalah nol , pengecualian tipe NullReferenceException akan dibuangsebelum memeriksa melalui Assert .

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

Peringatan PVS-Studio : V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'attr'. DbConnectionStringBuilder.cs 534

Ekspresi bersyarat pernyataan if terlihat agak mencurigakan. Match adalah metode instan. Menilai dengan memeriksa attr == null , null adalah nilai yang valid (diharapkan) untuk variabel ini. Oleh karena itu, jika utas eksekusi mencapai operan yang tepat dari operator || asalkan attr adalah null , kita mendapatkan pengecualian dari tipe NullReferenceException .

Dengan demikian, ketentuan pengecualian adalah sebagai berikut:

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

Peringatan PVS-Studio : V3021 Ada dua pernyataan 'jika' dengan ekspresi kondisional yang identik. Pernyataan 'jika' pertama berisi pengembalian metode. Ini berarti bahwa pernyataan 'jika' yang kedua adalah XMLDiffLoader.cs 301 yang tidak masuk akal.

Ada dua pernyataan if yang berisi ekspresi kondisional yang sama - tabel == null . Selain itu, kemudian cabang dari operator ini berisi tindakan yang berbeda - dalam satu kasus, metode ini keluar dengan nilai -1, di kedua, pengecualian dilemparkan. Di antara pemeriksaan, variabel tabel tidak berubah. Karenanya, pengecualian yang dimaksud tidak akan dibuang.

Masalah 34

Lihatlah metode yang menarik dari proyek System.ComponentModel.TypeConverter. Lebih tepatnya, pertama lihat komentar yang menggambarkannya:

Menghapus karakter terakhir dari string yang diformat. (Hapus karakter terakhir dalam string virtual). Saat keluar, param keluar berisi posisi di mana operasi sebenarnya dilakukan. Posisi ini relatif terhadap string uji. Parameter MaskedTextResultHint out memberikan informasi lebih lanjut tentang hasil operasi. Mengembalikan nilai true pada kesuksesan, false sebaliknya.

Poin kunci tentang nilai kembali: jika operasi berhasil, metode mengembalikan true , jika tidak palsu . Mari kita lihat apa yang sebenarnya terjadi.

 public bool Remove(out int testPosition, out MaskedTextResultHint resultHint) { .... if (lastAssignedPos == INVALID_INDEX) { .... return true; // nothing to remove. } .... return true; } 

Peringatan PVS-Studio : V3009 Aneh bahwa metode ini selalu mengembalikan nilai yang sama dan 'benar'. MaskedTextProvider.cs 1529

Bahkan, ternyata satu-satunya nilai pengembalian metode ini benar .

Edisi 35

 public void Clear() { if (_table != null) { .... } if (_table.fInitInProgress && _delayLoadingConstraints != null) { .... } .... } 

Peringatan PVS-Studio : V3125 Objek '_table' digunakan setelah diverifikasi dengan null. Periksa baris: 437, 423. ConstraintCollection.cs 437

Memeriksa _table! = Null berbicara sendiri - variabel _table bisa nol . Setidaknya dalam kasus ini, direasuransikan. Namun, di bawah ini mereka mengakses bidang instance melalui _table tanpa memeriksa null - _table .fInitInProgress .

Masalah 36

Sekarang, mari kita lihat beberapa peringatan yang dikeluarkan pada kode proyek untuk proyek 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 Warning : V3038 Argumen dilewatkan ke metode beberapa kali. Ada kemungkinan bahwa argumen lain harus disahkan. BinaryObjectWriter.cs 262

Penganalisis bingung panggilan terakhir ke _serWriter.WriteObjectEnd dengan dua argumen yang identik - typeNameInfo . Tampaknya seperti kesalahan ketik, tetapi tentu saja tidak bisa dikatakan. Saya memutuskan untuk melihat apa yang disebut metode WriteObjectEnd .

 internal void WriteObjectEnd(NameInfo memberNameInfo, NameInfo typeNameInfo) { } 

Baiklah ... lanjutkan. :)

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

Melihat kode ini, Anda tidak akan langsung mengatakan bahwa itu salah di sini atau terlihat mencurigakan. Tetapi penganalisa itu mungkin mengatakan bahwa itu mengingatkannya.

Peringatan PVS-Studio : V3066 Kemungkinan urutan argumen yang salah diteruskan ke konstruktor 'SerializationHeaderRecord': 'minorVersion' dan 'majorVersion'. BinaryFormatterWriter.cs 111

Mari kita lihat konstruktor dari kelas SerializationHeaderRecord yang dipanggil .

 internal SerializationHeaderRecord( BinaryHeaderEnum binaryHeaderEnum, int topId, int headerId, int majorVersion, int minorVersion) { _binaryHeaderEnum = binaryHeaderEnum; _topId = topId; _headerId = headerId; _majorVersion = majorVersion; _minorVersion = minorVersion; } 

Seperti yang Anda lihat, parameter konstruktor mengikuti urutan majorVersion , minorVersion ; ketika memanggil konstruktor, mereka ditransfer dalam urutan minorVersion , majorVersion . Kedengarannya seperti kesalahan ketik. Jika ini direncanakan (dan tiba-tiba?) - Saya pikir komentar penjelasan harus dibiarkan.

Masalah 38

 internal ObjectManager( ISurrogateSelector selector, StreamingContext context, bool checkSecurity, bool isCrossAppDomain) { _objects = new ObjectHolder[DefaultInitialSize]; _selector = selector; _context = context; _isCrossAppDomain = isCrossAppDomain; } 

Peringatan PVS-Studio : Parameter konstruktor 'checkSecurity' V3117 tidak digunakan. ObjectManager.cs 33

Parameter konstruktor checkSecurity tidak digunakan dengan cara apa pun. Tidak ada komentar tentang itu. Saya akan berasumsi bahwa itu dibiarkan untuk kompatibilitas, tetapi satu atau lain cara, dalam konteks diskusi tentang keamanan dalam beberapa tahun terakhir, ini terlihat menarik.

Edisi 39

Kode lain muncul yang sepertinya tidak biasa bagi saya. Pola terlihat 1 dalam 1 dalam ketiga kasus yang terdeteksi dan ditemukan dalam metode dengan nama dan nama variabel yang sama. Oleh karena itu:

  • entah saya tidak cukup tercerahkan dan tidak mengerti arti duplikasi seperti itu;
  • baik kesalahan disebarkan melalui kode menggunakan metode salin-tempel.

Kode itu sendiri:

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

Peringatan PVS-Studio :

  • Ekspresi V3022 'newLength == int.MaxValue' selalu salah. ObjectManager.cs 1423
  • Ekspresi V3022 'newLength == int.MaxValue' selalu salah. ObjectManager.cs 1511
  • Ekspresi V3022 'newLength == int.MaxValue' selalu salah. ObjectManager.cs 1558

Semua yang berbeda dalam metode lain adalah tipe elemen dari array temp (bukan FixupHolder , tetapi panjang atau objek ). Jadi saya masih memiliki kecurigaan tentang copy-paste ...

Masalah 40

Kode dari proyek System.Data.Odbc .

 public string UnquoteIdentifier(....) { .... if (!string.IsNullOrEmpty(quotePrefix) || quotePrefix != " ") { .... } .... } 

Peringatan PVS-Studio : Ekspresi V3022 '! String.IsNullOrEmpty (quotePrefix) || quotePrefix! = "" 'selalu benar. OdbcCommandBuilder.cs 338

Analyzer menganggap bahwa ungkapan di atas selalu benar . Dan memang benar. Dan tidak masalah nilai apa yang terkandung dalam quotePrefix - kondisi itu sendiri tidak ditulis dengan benar. Mari kita perbaiki.

Kami memiliki operator ||, oleh karena itu, nilai ekspresi akan benar jika operan kiri atau kanan (atau keduanya) benar . Semuanya jelas dengan kiri. Operan kanan hanya akan dievaluasi jika operan kiri salah. Oleh karena itu, jika ekspresi dikomposisikan sehingga nilai operan kanan selalu benar , ketika nilai kiri salah , hasil dari seluruh ekspresi akan selalu benar .

Dari kode di atas, kita tahu bahwa jika operan yang tepat dihitung, maka nilai string.IsNullOrEmpty (quotePrefix) ekspresi benar , oleh karena itu, salah satu pernyataan itu benar :

  • quotePrefix == null ;
  • quotePrefix.Length == 0 .

Jika salah satu dari pernyataan ini benar, ungkapan quotePrefix! = "" Akan juga benar , yang ingin kami buktikan. Oleh karena itu, nilai seluruh ekspresi selalu benar , terlepas dari konten quotePrefix .

Masalah 41

Kembali ke percakapan tentang konstruktor dengan parameter yang tidak digunakan:

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

Peringatan PVS-Studio : V3117 Parameter konstruktor 'userOptions' tidak digunakan. DbConnectionPool.cs 26

Dari kode analisa dan peringatan, jelas bahwa hanya satu parameter konstruktor yang digunakan - userOptions , sementara yang lain digunakan untuk menginisialisasi properti dengan nama yang sama. Sepertinya mereka lupa menginisialisasi salah satu properti.

Edisi 42

Ada kode mencurigakan yang bertemu dalam proyek ini 2 kali. Polanya sama.

 private DataTable ExecuteCommand(....) { .... foreach (DataRow row in schemaTable.Rows) { resultTable.Columns .Add(row["ColumnName"] as string, (Type)row["DataType"] as Type); } .... } 

Peringatan PVS-Studio :

  • V3051 Pemain tipe berlebihan. Objek sudah dari tipe 'Type'. DbMetaDataFactory.cs 176
  • V3051 Pemain tipe berlebihan. Objek sudah dari tipe 'Type'. OdbcMetaDataFactory.cs 1109

Ekspresi (Tipe) baris ["Tipe Data "] sebagai Tipe tampak mencurigakan . Pemain yang eksplisit akan dilakukan terlebih dahulu, kemudian pemain melalui operator sebagai . Jika nilai baris ["DataType"] adalah nol , itu akan berhasil melewati kedua gips dan pergi sebagai argumen ke metode Tambahkan . Jika baris ["DataType"] mengembalikan nilai yang tidak dapat dilemparkan ke Tipe , pemain eksplisit akan melempar InvalidCastException . Pada akhirnya, mengapa ada dua hantu? Pertanyaannya terbuka.

Masalah 43

Mari kita lihat potongan yang mencurigakan dari 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('+'); .... } .... } .... } } 

Peringatan PVS-Studio : V3105 Variabel 'versiString' digunakan setelah ditugaskan melalui operator kondisional nol. NullReferenceException dimungkinkan. RuntimeInformation.cs 29

Penganalisis memperingatkan tentang kemungkinan pengecualian dari tipe NullReferenceException ketika metode IndexOf dipanggil untuk variabel versiString . Saat mengambil nilai untuk variabel, pembuat kode menggunakan Operator '?' Untuk tidak mendapatkan NullReferenceException ketika mengakses properti InfromationalVersion . Leluconnya adalah jika panggilan ke GetCustomAttribute <...> mengembalikan nol, pengecualian masih akan dilempar, tetapi lebih rendah - saat memanggil metode IndexOf , karena versiString akan menjadi nol .

Masalah 44

Kami beralih ke proyek System.ComponentModel.Composition dan melihat beberapa peringatan. Segera 2 peringatan dikeluarkan untuk kode berikut:

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

Peringatan PVS-Studio :

  • V3125 Objek 'genericParameterConstraints' digunakan setelah diverifikasi terhadap nol. Periksa baris: 603, 589. GenericSpecializationPartCreationInfo.cs 603
  • V3125 Objek 'genericParameterAttributes' digunakan setelah diverifikasi terhadap null. Periksa baris: 604, 594. GenericSpecializationPartCreationInfo.cs 604

Ada genericParameterAttributes! = Null dan genericParameterConstraints! = Null memeriksa kode . Oleh karena itu, nilai null -valid untuk variabel-variabel ini, kami memperhitungkan. Jika kedua variabel nol , keluar dari metode - tidak ada pertanyaan. Tetapi bagaimana jika ada variabel yang nol , tetapi metode ini tidak keluar? Jika keadaan seperti itu dimungkinkan, dan eksekusi mencapai loop, kami mendapatkan pengecualian dari tipe NullReferenceException .

Masalah 45

Mari kita lihat peringatan menarik lainnya dari proyek yang sama. Dan meskipun, mari kita lakukan secara berbeda - pertama kita menggunakan kelas lagi, dan kemudian melihat kodenya. Kami akan menghubungkan paket NuGet dari versi pra-rilis terakhir yang tersedia ke proyek (saya menginstal versi paket 4.6.0-preview6.19303.8). Mari kita menulis kode sederhana, misalnya, ini:

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

Metode Equals tidak dikomentari, di docs.microsoft.com Saya tidak menemukan deskripsi metode ini untuk .NET Core, hanya untuk .NET Framework. Jika Anda melihatnya (" LazyMemberInfo.Equals (Object) Method ") - tidak ada yang tidak terlihat - mengembalikan benar atau salah , tidak ada informasi tentang pengecualian yang dihasilkan.

Kami menjalankan kode untuk eksekusi dan melihat:

Gambar 16

Kita bisa memutarbalikkan sedikit, menulis kode berikut dan juga mendapatkan kesimpulan yang menarik:

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

Hasil eksekusi kode.

Gambar 17

Menariknya, kedua pengecualian ini dihasilkan dalam ekspresi yang sama. Mari kita lihat ke dalam metode Equals .

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

Peringatan PVS-Studio : V3115 Melewati metode 'null' ke 'Equals' tidak boleh menghasilkan 'NullReferenceException'. LazyMemberInfo.cs 116

Bahkan, di sini analis memberikan sedikit ayunan, karena mengeluarkan peringatan pada ekspresi that._memberType . Pengecualian, bagaimanapun, terjadi di atas - ketika menjalankan ekspresi (LazyMemberInfo) obj . Sudah diperhatikan.

Dengan InvalidCastException , saya pikir semuanya jelas. Mengapa NullReferenceException dimunculkan ? Faktanya adalah bahwa LazyMemberInfo adalah struktur, oleh karena itu, pembongkaran dilakukan. Dan membongkar nilai nol hanya mengarah ke pengecualian tipeNullReferenceException . Nah, inilah beberapa kesalahan ketik dalam komentar juga, yang juga dapat diperbaiki pada saat yang sama. Yah, pelontaran eksplisit pengecualian juga tetap pada hati nurani penulis.

Masalah 46

Sesuatu yang serupa, by the way, ditemukan di System.Drawing.Common , dalam struktur TriState .

 public override bool Equals(object o) { TriState state = (TriState)o; return _value == state._value; } 

Peringatan PVS-Studio : V3115 Melewati metode 'null' ke 'Equals' tidak boleh menghasilkan 'NullReferenceException'. TriState.cs 53 Masalahnya

sama seperti dalam kasus yang dijelaskan di atas.

Masalah 47

Pertimbangkan beberapa cuplikan dari System.Text.Json .

Ingat, kami mengatakan bahwa ToString tidak boleh mengembalikan nol ? Kencangkan.

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

Pada pandangan pertama, metode ini adalah nol dan tidak kembali, tetapi penganalisa mengklaim sebaliknya.

Peringatan PVS-Studio : V3108 Tidak disarankan untuk mengembalikan 'null' dari metode 'ToSting ()'. JsonElement.cs 1460

Analyzer menunjuk ke garis dengan panggilan ke metode GetString () . Mari kita melihatnya:

 public string GetString() { CheckValidInstance(); return _parent.GetString(_idx, JsonTokenType.String); } 

Menyelam lebih dalam - ke versi metode GetString yang kelebihan beban :
 internal string GetString(int index, JsonTokenType expectedType) { .... if (tokenType == JsonTokenType.Null) { return null; } .... } 

Dan di sini kita sudah melihat kondisi di mana null akan dikembalikan - baik dari metode ini dan dari ToString yang awalnya dianggap .

Edisi 48

Tempat menarik lainnya:

 internal JsonPropertyInfo CreatePolymorphicProperty(....) { JsonPropertyInfo runtimeProperty = CreateProperty(property.DeclaredPropertyType, runtimePropertyType, property.ImplementedPropertyType, property?.PropertyInfo, Type, options); property.CopyRuntimeSettingsTo(runtimeProperty); return runtimeProperty; } 

Peringatan PVS-Studio : V3042 Kemungkinan NullReferenceException. '?.' dan '.' operator digunakan untuk mengakses anggota objek 'properti' JsonClassInfo.AddProperty.cs 179

Saat memanggil metode CreateProperty , mereka mengakses properti beberapa kali melalui properti variabel : property.DeclaredPropertyType , property.ImplementedPropertyType , properti? .PropertyInfo . Seperti yang Anda lihat, dalam satu kasus mereka menggunakan Operator '?.' Jika tidak berlebihan di sini dan properti bisa nol , operator ini tidak akan membantu dengan cara apa pun, karena dengan akses langsung pengecualian dari jenis akan dihasilkanNullReferenceException .

Masalah 49

Lokasi mencurigakan berikut ditemukan dalam proyek System.Security.Cryptography.Xml dan mereka berpasangan, seperti yang terjadi beberapa kali dengan peringatan lainnya. Kode ini terlihat seperti salin-tempel lagi, bandingkan sendiri.

Fragmen pertama:

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

Cuplikan kedua:

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

Peringatan PVS-Studio :

  • V3061 Parameter 'docPos' selalu ditulis ulang dalam tubuh metode sebelum digunakan. CanonicalXmlDocument.cs 37
  • V3061 Parameter 'docPos' selalu ditulis ulang dalam tubuh metode sebelum digunakan. CanonicalXmlDocument.cs 54

Dalam kedua metode ini, parameter docPos ditimpa sebelum nilainya digunakan, oleh karena itu, nilai yang digunakan sebagai argumen untuk metode diabaikan.

Masalah 50

Pertimbangkan beberapa peringatan tentang kode proyek 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 Warning : V3095 Objek 'value' digunakan sebelum diverifikasi dengan null. Periksa baris: 8696, 8708. TdsParser.cs 8696

Penganalisis bingung dengan memeriksa nilai! = Null di salah satu kondisi. Tampaknya dia tersesat di sana selama refactoring, karena nilai di atas mengalami penurunan nilai berkali-kali. Jika nilai bisa nol , semuanya buruk.

Edisi 51

Kesalahan berikutnya dari tes, tetapi tampaknya menarik bagi saya, jadi saya memutuskan untuk membawanya.

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

Peringatan PVS-Studio : V3053 Ekspresi yang berlebihan. Periksa substring 'nama' dan 'db_name'. QueryEngine.cs 151

Faktanya adalah bahwa dalam kasus ini, kombinasi dari lowerBatchText.Contains ("name") dan lowerBatchText.Contains ("db_name") redup . Bahkan, jika string yang diperiksa berisi substring "db_name" , maka itu juga akan berisi substring "nama" . Jika string tidak mengandung "nama" , maka string itu tidak akan mengandung "db_name" . Ternyata memeriksa lowerBatchText.Contains ("nama")berlebihan. Kecuali itu dapat mengurangi jumlah ekspresi yang dievaluasi jika string yang diperiksa tidak mengandung "nama" .

Masalah 52

Cuplikan kode yang mencurigakan dari proyek 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") .... .... } 

Peringatan PVS-Studio : V3125 Objek 'entri' digunakan setelah diverifikasi terhadap nol. Periksa baris: 270, 227. FtpControlStream.cs 270

Ketika mengkompilasi baris interpolasi, entri ekspresi ? Perintah dan tanggapan? Deskripsi digunakan . Alih-alih Operator '.' gunakan '?.' untuk tidak mendapatkan pengecualian dari tipe NullReferenceException jika salah satu parameter yang sesuai adalah nol . Dalam hal ini, teknik ini berfungsi. Selanjutnya, seperti yang Anda lihat dari kode, nilai nol yang mungkin untuk respons terpotong (keluar dari metode jika respons == null ), tetapi untuk entritidak seperti itu. Akibatnya, jika entri adalah nol , pengecualian akan dilemparkan saat memanggil entri.Komand (sudah menggunakan '.' Daripada '?.').

Selanjutnya, kita akan kembali mempelajari kode secara cukup rinci, jadi saya sarankan istirahat lagi - sedikit gangguan, menyeduh teh atau membuat kopi, setelah itu saya akan senang melihat Anda di sini lagi.

Gambar 23

Apakah kamu kembali? Kalau begitu mari kita lanjutkan. :)

Masalah 53

Sekarang mari kita lihat sesuatu yang menarik dari proyek System.Collections.Immutable . Kali ini, mari kita bereksperimen sedikit dengan struktur System.Collections.Immutable.ImmutableArray <T> . Kami tertarik dengan metode IStructuralEquatable.Equals dan IStructuralComparable.CompareTo .

Mari kita mulai dengan metode IStructuralEquatable.Equals . Kode diberikan di bawah ini, saya mengusulkan untuk mencoba memahami sendiri apa yang salah dengannya:

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

Apakah kamu berhasil? Jika demikian, sekali lagi, selamat. :)

PVS-Studio Warning : V3125 Objek 'ours' digunakan setelah diverifikasi dengan null. Periksa baris: 1212, 1204. ImmutableArray_1.cs 1212

Analyzer mengacaukan panggilan metode instance Equals melalui variabel kita yang terletak di pernyataan pengembalian terakhir , karena mengasumsikan bahwa pengecualian tipe NullReferenceException dimungkinkan di sini . Bagaimana dia sampai pada kesimpulan ini? Untuk membuatnya lebih mudah dijelaskan, berikut adalah kode yang disederhanakan dari metode yang sama.

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

Dari ekspresi terakhir, kita melihat bahwa nilai kita berasal dari self.array . Di atas, self.array == null check dilakukan beberapa kali . Oleh karena itu, self.array , dan karena itu milik kita , bisa nol . Setidaknya secara teori. Apakah kondisi seperti itu dapat dicapai dalam praktik? Mari kita coba mencari tahu. Untuk melakukan ini, sekali lagi saya membawa tubuh metode dengan poin-poin kunci terpisah.

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

Poin kunci 1 . self.array == this.array (karena self = ini ). Oleh karena itu, sebelum memanggil metode, perlu untuk mencapai keadaan this.array == null .

Poin kunci 2 . Kita dapat mengabaikan ini jika , yang akan menjadi pilihan termudah untuk mencapai tujuan, atau masuk ke dalam. Untuk mengabaikan ini jika , cukup bahwa jenis variabel lainnya menjadi tipe Array atau turunan dari itu dan lainnya tidak nol . Kemudian setelah menggunakan operator as di OtherArrayreferensi non-nol akan ditulis, dan kami akan mengabaikan pernyataan if pertama .

Poin kunci 3 . Titik ini menyiratkan jalan yang lebih rumit. Kita pasti perlu keluar pada pernyataan if kedua (yang dengan ekspresi kondisional milik mereka! = Null ). Jika ini tidak terjadi dan pelaksanaan cabang kemudian dimulai , kami dijamin tidak mencapai titik 5 yang kami butuhkan, asalkan self.array == null karena titik kunci 4. Agar tidak masuk ke pernyataan if dari poin kunci 3, Anda harus melakukan salah satu dari ketentuan:

  • untuk nilai lainnya adalah nol ;
  • sehingga tipe aktual lainnya tidak mengimplementasikan antarmuka IImmutableArray .

Poin kunci 5 . Jika kita mencapai titik ini dengan nilai self.array == null , maka kita telah mencapai tujuan kita, dan pengecualian tipe NullReferenceException akan dilemparkan ke dalam metode .

Kami mendapatkan set data berikut yang akan menuntun kami ke tujuan.

Pertama: this.array adalah null .

Yang kedua adalah salah satu dari yang berikut:

  • lainnya - null ;
  • lainnya adalah tipe Array atau turunannya;
  • lainnya tidak memiliki tipe Array atau turunannya, juga tidak mengimplementasikan antarmuka IImmutableArray .

array adalah bidang yang dideklarasikan sebagai berikut:

 internal T[] array; 

Karena ImmutableArray <T> adalah struktur, ia memiliki konstruktor default (tanpa argumen), sebagai akibatnya bidang array akan default, yaitu nol . Dan inilah yang kami butuhkan.

Yah, kita tidak akan lupa bahwa kita menyelidiki implementasi eksplisit dari metode antarmuka, oleh karena itu, sebelum panggilan, kita perlu melakukan pemeran.

Sekarang kami memiliki semua kartu untuk mencapai pengecualian dalam tiga cara. Kami menghubungkan versi debug perpustakaan, menulis kode, mengeksekusi, mengamati.

Cuplikan kode 1 .

 var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(null, comparer); 

Cuplikan kode 2 .

 var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(new string[] { }, comparer); 

Cuplikan kode 3 .

 var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(typeof(Object), comparer); 

Hasil dari eksekusi ketiga fragmen kode akan sama, hanya saja akan tercapai karena data input yang berbeda dan jalur eksekusi yang berbeda.

Gambar 18

Edisi 54

Jika Anda belum lupa, kami memiliki metode lain yang harus Anda coba kompromi. :) Hanya saja kali ini kami tidak akan membongkar secara detail. Selain itu, kami sudah mengetahui beberapa informasi dari contoh sebelumnya.

 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 Warning : V3125 Objek 'ours' digunakan setelah diverifikasi dengan null. Periksa baris: 1265, 1251. ImmutableArray_1.cs 1265

Seperti yang Anda lihat, situasinya sangat mirip dengan contoh yang dipertimbangkan sebelumnya.

Kami akan menulis kode berikut:

 Object other = ....; var comparer = Comparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralComparable)immutableArray).CompareTo(other, comparer); 

Mari kita coba untuk memilih nilai input sehingga mencapai titik di mana pengecualian tipe NullReferenceException terjadi .

Nilai : lainnya - String baru [] {} ;

Hasil:

Gambar 22

Dengan demikian, kami kembali berhasil memilih satu set data input yang pengecualiannya terjadi dalam metode.

Masalah 55

Ada beberapa tempat di proyek System.Net.HttpListener , tidak hanya mencurigakan, tetapi sangat mirip satu sama lain. Dan lagi, keraguan samar tentang creep-paste masuk. Karena polanya sama, pertimbangkan satu contoh kode, untuk kasus lain, saya akan memberikan peringatan penganalisa:

 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 Warning : V3095 Objek 'buffer' digunakan sebelum diverifikasi dengan null. Periksa baris: 51, 53. HttpRequestStream.cs 51

Meningkatkan pengecualian tipe ArgumentNullException dengan buffer == null secara eksplisit mengisyaratkan bahwa null adalah nilai yang tidak valid untuk variabel ini. Namun, jika nilai ekspresi NetEventSource.IsEnabled - ke benar , dan pada saat yang sama penyangga ' - nol , akan melemparkan pengecualian jenis NullReferenceException saat menghitung ekspresi buffer.length . Oleh karena itu, sebelum memeriksa buffer == nulldalam hal ini, masalahnya bahkan tidak mencapai.

Peringatan PVS-Studio dikeluarkan pada metode lain dengan pola yang persis sama:

  • V3095 Objek 'buffer' digunakan sebelum diverifikasi terhadap null. Periksa baris: 49, 51. HttpResponseStream.cs 49
  • V3095 Objek 'buffer' digunakan sebelum diverifikasi terhadap null. Periksa baris: 74, 75. HttpResponseStream.cs 74

Masalah 56

Kode serupa ditemukan di proyek 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); } .... } 

Peringatan PVS-Studio : V3095 Objek 'tx' digunakan sebelum diverifikasi dengan null. Periksa baris: 3282, 3285. TransactionState.cs 3282

Dalam kondisi tertentu, mereka ingin melempar pengecualian tipe InvalidOperationException . Ketika metode ini dipanggil, parameter tx digunakan untuk membuat objek pengecualian , dan diperiksa untuk null agar tidak membuang pengecualian dari tipe NullReferenceException saat mengevaluasi ekspresi tx.DistributedTxId . Ironisnya adalah bahwa jika tx - null cek ini tidak akan membantu, karena ketika menghitung kondisi pernyataan ifbidang contoh diakses melalui variabel tx - tx._outcomeSource._isoLevel .

Masalah 57

Kode dari proyek 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; } .... } 

Peringatan PVS-Studio : Ekspresi V3022 'cacheMemoryLimit! = 0 && _memoryLimit! = 0' selalu salah. CacheMemoryMonitor.cs 250

Jika Anda memperhatikan kode ini, Anda akan melihat bahwa salah satu ekspresi - cacheMemoryLimit! = 0 && _memoryLimit! = 0 - akan selalu salah . Karena _memoryLimit memiliki nilai 0 ( ditempatkan sebelum pernyataan if ), operan kanan operator && salah , oleh karena itu, hasil keseluruhan ekspresi juga salah .

Masalah 58

Berikut ini adalah potongan kode yang mencurigakan dari proyek System.Diagnostics.TraceSource .

 public override object Pop() { StackNode n = _stack.Value; if (n == null) { base.Pop(); } _stack.Value = n.Prev; return n.Value; } 

Peringatan PVS-Studio : V3125 Objek 'n' digunakan setelah diverifikasi terhadap null. Periksa baris: 115, 111. CorrelationManager.cs 115

kode yang menarik. Karena memeriksa n == null, saya akan menganggap bahwa null adalah nilai yang mungkin untuk variabel lokal ini. Jika demikian, pengecualian dari tipe NullReferenceException akan dilemparkan ketika mengakses properti instance - n.Prev . Jika n dalam hal ini tidak pernah bisa menjadi nol , maka memanggil base.Pop () dalam konteks ini tidak akan pernah dieksekusi.

Edisi 59

Tempat menarik ditemukan di proyek System.Drawing.Primitive. Dan lagi, saya mengusulkan untuk mencari masalah sendiri. Ini kodenya:

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

Oke, oke, lagi ini lelucon saya ... Atau bisakah Anda? Bagaimanapun, kami akan mempersingkat kode untuk lebih jelas menunjukkan masalah.

Ini adalah versi singkat dari kode:

 switch (c.ToKnownColor()) { .... case KnownColor.Control: colorString = "buttonface"; break; case KnownColor.ControlLight: colorString = "buttonface"; break; .... } 

Peringatan PVS-Studio : V3139 Dua atau lebih cabang kasus melakukan tindakan yang sama. ColorTranslator.cs 302

Saya tidak bisa mengatakan dengan pasti, tapi saya pikir ini masih sebuah kesalahan. Dalam kasus lain, ketika mereka ingin mengembalikan nilai yang sama untuk beberapa elemen enumerasi, mereka menggunakan beberapa kasus untuk saling mengikuti. Dan untuk membuat kesalahan dengan copy-paste cukup mudah, seperti yang menurut saya.

Menggali lebih dalam. Untuk mendapatkan metode output analit ToHtml nilai «buttonface» , input dapat ditularkan dari berikut ini (diharapkan)

  • SystemColors.Control ;
  • SystemColors.ControlLight .

Jika Anda melihat nilai ARGB untuk masing-masing warna ini, Anda dapat melihat yang berikut:

  • SystemColors.Control - (255, 240, 240, 240) ;
  • SystemColors.ControlLight - (255, 227, 227, 227) .

Jika Anda memanggil nilai yang diterima ( "tombol" ) metode konversi terbalik - FromHtml , kami mendapatkan Kontrol warna (255, 240, 240, 240) . Bisakah saya mendapatkan warna ControlLight dari FromHtml ? YaDalam metode ini, ada tabel warna berdasarkan yang (dalam kasus tertentu) warna diperoleh. Inisialisasi tabel memiliki baris berikut:

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

Dengan demikian, FromHtml mengembalikan warna ControlLight (255, 227, 227, 227) untuk nilai "threedhighlight" . Saya pikir itu adalah nilai ini yang seharusnya digunakan dalam kasus KnowedColor.ControlLight .

Edisi 60

Mari kita lihat beberapa peringatan menarik dari proyek 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 Warning : V3137 Variabel 'tersisa' ditugaskan tetapi tidak digunakan pada akhir fungsi. RegexRunner.cs 612

Nilai ditulis untuk variabel lokal yang tersisa , tetapi tidak lagi digunakan sampai akhir metode. Mungkin beberapa kode yang menggunakannya dihapus, tetapi mereka lupa tentang variabel itu sendiri. Atau di sini ada kesalahan yang lebih serius dan variabel ini harus digunakan entah bagaimana.

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

Peringatan PVS-Studio : V3063 Bagian dari ekspresi kondisional selalu benar jika dievaluasi: _rangelist.Count> 0. RegexCharClass.cs 523

Penganalisa dengan tepat mencatat bahwa bagian dari ekspresi - _rangelist.Count> 0 - akan selalu benar jika kode ini akan dieksekusi. Bahkan jika daftar yang ditunjuk oleh _rangelist kosong, setelah menambahkan elemen _rangelist.Add (....) , itu tidak akan menjadi seperti itu lagi.

Masalah 62

Mari kita lihat pemicu aturan diagnostik V3128 di System.Drawing.Common dan System.Transactions.Local proyek .

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

Peringatan PVS-Studio : V3128 Kolom '_index' digunakan sebelum diinisialisasi dalam konstruktor. PrinterSettings.Windows.cs 1679

Saat menginisialisasi bidang _endIndex , bidang lain digunakan - _index , yang pada saat digunakan memiliki nilai standar - default (int) , yaitu, 0 . Di bawah bidang _index sudah diinisialisasi. Jika tiba-tiba ini bukan kesalahan - variabel _index harus dihilangkan dalam ekspresi ini agar tidak membingungkan.

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

Peringatan PVS-Studio : V3128 Kolom '_timerInterval' digunakan sebelum diinisialisasi dalam konstruktor. TransactionTable.cs 151

Situasinya mirip dengan yang dijelaskan di atas. Pertama, gunakan nilai bidang _timerInterval (saat ini masih sama dengan default (int) ) untuk menginisialisasi _timer , dan baru kemudian menginisialisasi bidang _timerInterval itu sendiri .

Edisi 64

Peringatan berikut diterima oleh aturan diagnostik yang masih dalam pengembangan. Dia belum memiliki dokumentasi dan laporan akhir, tetapi dengan bantuannya dia sudah berhasil menemukan beberapa tempat menarik. Lagi-lagi tempat-tempat ini terlihat seperti salin-tempel , jadi kami hanya akan mempertimbangkan satu kode.

 private bool ProcessNotifyConnection(....) { .... WeakReference reference = (WeakReference)( LdapConnection.s_handleTable[referralFromConnection]); if ( reference != null && reference.IsAlive && null != ((LdapConnection)reference.Target)._ldapHandle) { .... } .... } 

PVS-Studio warning (stub) : VXXXX TODO_MESSAGE. LdapSessionOptions.cs 974

Bahaya di sini adalah jika setelah memeriksa referensi. Hidup pengumpulan sampah dilakukan dan objek yang dirujuk oleh WeakReference berada di bawahnya , referensi . Target akan mengembalikan nol . Akibatnya, saat mengakses bidang instance _ldapHandle, pengecualian tipe NullReferenceException akan dilempar . Sebenarnya, Microsoft sendiri juga memperingatkan tentang jebakan ini dengan verifikasi IsAlive. Kutipan dari docs.microsoft.com - " WeakReference.IsAlive Property ":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.


Apakah semua kesalahan dan tempat menarik ini ditemukan sebagai hasil analisis? Tentu tidak! Mulai melihat hasil analisis, saya hati-hati melihat peringatan. Ketika jumlah mereka bertambah dan menjadi jelas bahwa akan ada peringatan pada artikel tersebut, saya membolak-balik hasilnya, mencoba untuk memilih hanya apa yang tampaknya paling menarik bagi saya. Ketika saya sampai pada yang terakhir (log terbesar), yang tersisa bagi saya adalah membalik-balik semua peringatan sampai mata saya menangkap sesuatu yang tidak biasa. Jadi, jika Anda menyelidiki hal itu, saya yakin Anda dapat menemukan lebih banyak tempat menarik.

Sebagai contoh, saya mengabaikan hampir semua peringatan V3022 dan V3063 . Secara relatif, jika ada kode bentuk:

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

Saya melewatkannya, karena ada tempat-tempat menarik yang ingin saya gambarkan. Ada pemicu pada kunci tidak aman menggunakan pernyataan kunci dengan kunci ini , dll. - V3090 ; panggilan acara tidak aman - V3083 ; objek yang tipenya menerapkan IDisposable , tetapi yang Buang / Tutup tidak disebut - V3072 dan diagnostik serupa; dan masih banyak lagi.

Saya tidak menulis (setidaknya saya mencoba, saya tidak sengaja bisa menulis sesuatu). Saya menemukan masalah yang ditemukan dalam tes. Selain beberapa tempat yang tampaknya cukup menarik bagi saya untuk menarik perhatian mereka. Tetapi kode pengujian juga bisa mengandung kesalahan, itulah sebabnya tes tidak akan berfungsi dengan benar.

Secara umum, masih ada sesuatu yang harus dipelajari - tetapi saya tidak menetapkan tujuan untuk menuliskan secara mutlak semua masalah yang ditemukan .

Kualitas kodenya tampak tidak merata. Beberapa proyek benar-benar bersih, sementara yang lain berisi tempat yang mencurigakan. Mungkin kemurnian proyek diharapkan, terutama dalam kasus kelas perpustakaan yang paling umum digunakan.

Jika untuk menggeneralisasi - mungkin, kita dapat mengatakan bahwa kode tersebut cukup berkualitas mengingat fakta bahwa jumlah kode telah dianalisis. Tetapi, sebagai berikut dari artikel ini, ada beberapa sudut gelap.

Ngomong-ngomong, proyek sebesar ini juga merupakan tes yang bagus untuk penganalisa. Saya berhasil menemukan serangkaian peringatan palsu / aneh yang saya pilih untuk diperiksa dan diperbaiki. Jadi, sebagai hasil dari analisis, kami berhasil menemukan titik-titik di mana ada baiknya bekerja pada PVS-Studio itu sendiri.

Kesimpulan


Jika Anda sampai di tempat ini setelah membaca artikel lengkap - biarkan saya berjabat tangan! Saya harap saya bisa menunjukkan kesalahan yang menarik dan menunjukkan manfaat analisis statis. Jika Anda juga belajar sesuatu yang baru untuk diri sendiri yang akan memungkinkan Anda untuk menulis kode yang lebih baik - saya akan sangat senang.

Gambar 24

Dengan satu atau lain cara, bantuan analisis statis tidak akan berlebihan, jadi saya sarankan mencoba PVS-Studio pada proyek Anda dan melihat tempat menarik apa yang dapat Anda temukan dengannya. Jika Anda memiliki pertanyaan atau hanya ingin berbagi tempat menarik yang ditemukan - jangan ragu untuk menulis ke support@viva64.com . :)

Semua yang terbaik!

PS Menghubungi .NET Core Library Developers


Terima kasih banyak atas apa yang Anda lakukan! Anda adalah orang-orang hebat. Saya harap artikel ini membantu membuat kode sedikit lebih baik. Ingatlah bahwa saya tidak menuliskan semua tempat yang mencurigakan dalam artikel, dan lebih baik untuk memeriksa proyek secara mandiri menggunakan penganalisa - dengan cara ini Anda dapat mempelajari semua peringatan secara lebih rinci, dan akan lebih mudah untuk bekerja daripada dengan log teks sederhana / daftar kesalahan ( saya menulis sedikit lebih banyak tentang ini di sini ).



Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Sergey Vasiliev. Memeriksa .NET Core Libraries Source Code oleh PVS-Studio Static Analyzer

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


All Articles