.NET Core libraries adalah salah satu proyek C # paling populer di GitHub. Ini tidak mengherankan, karena dikenal dan digunakan secara luas. Karena ini, upaya untuk mengungkap sudut-sudut gelap dari kode sumber menjadi lebih menawan. Jadi ini yang akan kami coba lakukan dengan bantuan analisa statis PVS-Studio. Apa yang Anda pikirkan - pada akhirnya kami akan menemukan sesuatu yang menarik?
Saya telah berjalan menuju artikel ini selama lebih dari satu setengah tahun. Pada titik tertentu, saya punya ide di kepala saya bahwa perpustakaan .NET Core adalah berita gembira, dan ceknya sangat menjanjikan. Saya telah memeriksa proyek beberapa kali, alat analisis terus menemukan semakin banyak fragmen kode yang menarik, tetapi tidak lebih dari sekadar menggulir daftar peringatan. Dan ini dia - akhirnya terjadi! Proyek diperiksa, artikelnya tepat di depan Anda.
Rincian tentang Proyek dan Periksa
Jika Anda berupaya menyelami investigasi kode - Anda dapat mengabaikan bagian ini. Namun, saya sangat ingin Anda membacanya, karena di sini saya bercerita lebih banyak tentang proyek dan penganalisa, serta tentang melakukan analisis dan mereproduksi kesalahan.
Proyek di bawah cek
Mungkin, saya bisa saja tidak mengatakan apa itu CoreFX (.NET Core Libraries), tetapi jika Anda belum pernah mendengarnya, uraiannya diberikan di bawah ini. Itu sama dengan di
halaman proyek di GitHub , di mana Anda juga dapat mengunduh kode sumber.
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) .
Penganalisis Digunakan dan Metode Analisis
Saya memeriksa kode menggunakan
analisa 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 C, C ++, Java dapat dianalisis di bawah Windows, Linux, macOS.
Biasanya untuk memeriksa proyek C # saya menggunakan plugin PVS-Studio untuk Visual Studio (mendukung versi 2010-2019), karena mungkin itu skenario analisis yang paling sederhana dan nyaman dalam kasus ini: solusi terbuka, jalankan analisis, tangani daftar peringatan. Namun, itu keluar sedikit lebih rumit dengan CoreFX.
Bagian yang sulit adalah bahwa proyek tidak memiliki file .sln tunggal, oleh karena itu membukanya di Visual Studio dan melakukan analisis penuh, menggunakan plugin PVS-Studio, tidak mungkin. Itu mungkin hal yang baik - saya tidak benar-benar tahu bagaimana Visual Studio akan mengatasi solusi sebesar ini.
Namun, tidak ada masalah dengan analisis, karena distribusi PVS-Studio termasuk versi baris perintah analyzer untuk proyek MSBuild (dan .sln). Semua yang harus saya lakukan adalah menulis skrip kecil, yang akan menjalankan "PVS-Studio_Cmd.exe" untuk masing-masing .sln di direktori CoreFX dan menyimpan hasil dalam direktori terpisah (ditentukan oleh bendera baris perintah penganalisis) .
Presto! Akibatnya, saya memiliki kotak Pandora dengan satu set laporan yang menyimpan beberapa hal menarik. Jika diinginkan, log ini dapat dikombinasikan dengan utilitas PlogConverter, yang merupakan bagian dari distributif. Bagi saya, itu lebih nyaman untuk bekerja dengan log terpisah, jadi saya tidak menggabungkannya.
Ketika menjelaskan beberapa kesalahan, saya merujuk pada dokumentasi dari paket docs.microsoft.com dan NuGet, tersedia untuk diunduh dari nuget.org. Saya berasumsi bahwa kode yang dijelaskan dalam dokumentasi / paket mungkin sedikit berbeda dari kode yang dianalisis. Namun, akan sangat aneh jika, misalnya, dokumentasi tidak menjelaskan pengecualian yang dihasilkan ketika memiliki dataset input tertentu, tetapi versi paket baru akan menyertakannya. Anda harus mengakui itu akan menjadi kejutan yang meragukan. Mereproduksi kesalahan dalam paket dari NuGet menggunakan data input yang sama yang digunakan untuk debugging perpustakaan menunjukkan bahwa masalah ini bukan hal baru. Yang paling penting, Anda dapat 'menyentuhnya' tanpa membangun proyek dari sumber.
Dengan demikian, memungkinkan untuk beberapa desinkronisasi teoritis kode, saya merasa dapat diterima untuk merujuk pada deskripsi metode yang relevan di docs.microsoft.com dan untuk mereproduksi masalah menggunakan paket dari nuget.org.
Selain itu, saya ingin mencatat bahwa deskripsi oleh tautan yang diberikan, informasi (komentar) dalam paket (dalam versi lain) dapat diubah selama penulisan artikel.
Proyek Lain yang Diperiksa
Omong-omong, artikel ini tidak unik dari jenisnya. Kami menulis artikel lain tentang cek proyek. Dengan tautan ini Anda dapat menemukan
daftar proyek yang diperiksa . Selain itu, di situs kami Anda tidak hanya akan menemukan artikel pemeriksaan proyek, tetapi juga berbagai artikel teknis tentang C, C ++, C #, Java, serta beberapa catatan menarik. Anda dapat menemukan semua ini di
blog .
Rekan saya sebelumnya telah memeriksa perpustakaan .NET Core pada tahun 2015. Hasil analisis sebelumnya dapat ditemukan di artikel yang relevan: "
Analisis Natal Perpustakaan NET Core. (CoreFX) ."
Kesalahan yang Terdeteksi, Bagian yang Mencurigakan dan Menarik
Seperti biasa, untuk minat yang lebih besar, saya sarankan Anda mencari sendiri kesalahan dalam fragmen yang diberikan, dan baru kemudian membaca pesan penganalisa dan deskripsi masalahnya.
Untuk kenyamanan, saya telah dengan jelas memisahkan masing-masing bagian menggunakan label
Issue N - dengan cara ini lebih mudah untuk mengetahui di mana deskripsi satu kesalahan berakhir, diikuti oleh yang berikutnya. Selain itu, lebih mudah merujuk pada fragmen tertentu.
Edisi 1abstract 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 dengan jelas menyatakan bahwa nilai
nol untuk parameter
konteks tidak valid, mereka ingin menekankan ini dengan menggunakan pengecualian dari tipe
InvalidOperationException . Namun, tepat di atas dalam kondisi sebelumnya kita dapat melihat referensi tanpa syarat dari
konteks referensi -
context.ContextType . Akibatnya, jika nilai
konteksnya nol, pengecualian tipe
NullReferenceException akan dihasilkan alih-alih
InvalidOperationExcetion yang diharapkan
.Mari kita coba mereproduksi masalahnya. Kami akan menambahkan referensi ke pustaka
System.DirectoryServices.AccountManagement ke proyek dan menjalankan kode berikut:
GroupPrincipal groupPrincipal = new GroupPrincipal(new PrincipalContext(ContextType.Machine)); groupPrincipal.Save(null);
GroupPrincipal mewarisi dari kelas abstrak
Kepala Sekolah yang mengimplementasikan metode
Simpan yang kami minati. Jadi kami mengeksekusi kode dan melihat apa yang diperlukan untuk membuktikan.
Untuk kepentingan, Anda dapat mencoba mengunduh paket yang sesuai dari NuGet dan mengulangi masalah dengan cara yang sama. Saya menginstal paket 4.5.0 dan memperoleh hasil yang diharapkan.
Edisi 2 private SearchResultCollection FindAll(bool findMoreThanOne) { searchResult = null; DirectoryEntry clonedRoot = null; if (_assertDefaultNamingContext == null) { clonedRoot = SearchRoot.CloneBrowsable(); } else { clonedRoot = SearchRoot.CloneBrowsable(); } .... }
Peringatan PVS-Studio: V3004 Pernyataan 'then' setara dengan pernyataan 'else'. DirectorySearcher.cs 629
Terlepas dari apakah kondisi
_assertDefaultNamingContext == null benar atau salah, tindakan yang sama akan diambil, saat
itu dan cabang-cabang
lain dari pernyataan
if memiliki badan yang sama. Entah harus ada tindakan lain di cabang, atau Anda bisa menghilangkan pernyataan
if untuk tidak membingungkan pengembang dan analis.
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) .... .... } .... }
Peringatan PVS-Studio: V3095 Objek 'propertyNames' digunakan sebelum diverifikasi dengan null. Periksa baris: 990, 1004. DirectoryEntry.cs 990
Sekali lagi, kita melihat urutan tindakan yang aneh. Dalam metode ini, ada check
propertyNames! = Tidak , pengembang menutup basis mereka dari
nol yang masuk ke metode. Tetapi di atas Anda dapat melihat beberapa operasi akses dengan referensi yang berpotensi nol ini -
propertyNames. Panjang dan
propertyNames [i] . Hasilnya cukup dapat diprediksi - terjadinya pengecualian dari tipe
NullReferenceExcepption jika referensi nol dilewatkan ke metode.
Kebetulan sekali!
RefreshCache adalah metode publik di kelas publik. Bagaimana dengan mencoba mereproduksi masalah? Untuk melakukan ini, kami akan menyertakan Sistem perpustakaan yang diperlukan.
Layanan Direktori ke proyek dan kami akan menulis kode seperti ini:
DirectoryEntry de = new DirectoryEntry(); de.RefreshCache(null);
Setelah kami mengeksekusi kode, kami dapat melihat apa yang kami harapkan.
Hanya untuk iseng, Anda dapat mencoba mereproduksi masalah pada versi rilis paket NuGet. Selanjutnya, kita menambahkan referensi ke paket
System.DirectoryServices (saya menggunakan versi 4.5.0) ke proyek dan menjalankan kode yang sudah akrab. Hasilnya di bawah.
Masalah 4Sekarang kita akan pergi dari kebalikan - pertama kita akan mencoba untuk menulis kode, yang menggunakan contoh kelas, dan kemudian kita akan melihat ke dalam. Mari kita merujuk ke struktur
System.Drawing.CharacterRange dari perpustakaan
System.Drawing.Common dan paket NuGet dengan nama yang sama.
Kami akan menggunakan kode ini:
CharacterRange range = new CharacterRange(); bool eq = range.Equals(null); Console.WriteLine(eq);
Untuk berjaga-jaga, untuk sekadar
menguras ingatan kita, kita akan membahas
docs.microsoft.com untuk mengingat kembali nilai yang diharapkan dari ekspresi objek.
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 menurut Anda teks "Salah" akan ditampilkan di konsol? Tentu saja tidak. Itu akan terlalu mudah. :) Oleh karena itu, kami mengeksekusi kode dan melihat hasilnya.
Itu adalah output dari kode di atas menggunakan paket NuGet
System.Drawing.Common versi 4.5.1. Langkah selanjutnya adalah menjalankan kode yang sama dengan versi pustaka debugging. Inilah yang kami lihat:
Sekarang mari kita lihat kode sumber, khususnya, penerapan 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
Kita dapat mengamati, apa yang harus dibuktikan - parameter
obj tidak ditangani dengan benar. Karena ini, pengecualian
NullReferenceException terjadi dalam ekspresi bersyarat saat memanggil metode instance
GetType.Edisi 5Saat kami menjelajahi pustaka ini, mari kita pertimbangkan bagian lain yang menarik - metode
Ikon . Simpan . Sebelum penelitian, mari kita lihat deskripsi metode.
Tidak ada deskripsi metode ini:
Mari kita membahas docs.microsoft.com - "
Icon.Save (Stream) Method ". Namun, tidak ada batasan pada input atau informasi tentang pengecualian yang dihasilkan.
Sekarang mari kita beralih ke inspeksi 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 terhadap nol. Periksa baris: 654, 672. Icon.Windows.cs 654
Sekali lagi, ini adalah cerita yang sudah kita ketahui - kemungkinan referensi referensi nol, karena parameter metode ini direferensikan tanpa memeriksa
nol . Sekali lagi, suatu kebetulan keadaan yang berhasil - baik kelas dan metodenya bersifat publik, sehingga kami dapat mencoba mereproduksi masalahnya.
Tugas kami sederhana - untuk membawa eksekusi kode ke ekspresi
outputStream.Write (_iconData, 0, _iconData.Length); dan pada saat yang sama menyimpan nilai variabel
outputStream -
null . Memenuhi persyaratan
_iconData! = Null sudah cukup untuk ini.
Mari kita lihat konstruktor publik paling sederhana:
public Icon(string fileName) : this(fileName, 0, 0) { }
Itu hanya mendelegasikan pekerjaan ke konstruktor lain.
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); }
Itu dia, itu yang kita butuhkan. Setelah memanggil konstruktor ini, jika kami berhasil membaca data dari file dan tidak ada crash dalam metode
Initialize , bidang
_iconData akan berisi referensi ke objek, ini yang kami butuhkan.
Ternyata kita harus membuat turunan dari kelas
Ikon dan menentukan file ikon yang sebenarnya untuk mereproduksi masalah. Setelah ini kita perlu memanggil metode
Simpan , setelah melewati nilai
nol sebagai argumen, itulah yang kami lakukan. Kode mungkin terlihat seperti ini, misalnya:
Icon icon = new Icon(@"D:\document.ico"); icon.Save(null);
Hasil eksekusi diharapkan.
Edisi 6Kami melanjutkan ulasan dan melanjutkan. Cobalah untuk menemukan 3 perbedaan antara tindakan, dieksekusi dalam
case CimType.UInt32 dan
case 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, karena penganalisa memperingatkan kita tentang hal itu.
Peringatan PVS-Studio: V3139 Dua atau lebih cabang kasus melakukan tindakan yang sama. WMIGenerator.cs 5220
Secara pribadi, gaya kode ini tidak begitu jelas. Jika tidak ada kesalahan, saya pikir, logika yang sama seharusnya tidak diterapkan pada kasus yang berbeda.
Edisi 7Perpustakaan
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 (banyak) cek aneh, tetapi yang ini entah bagaimana masuk ke dalam jiwaku. Mungkin, alasannya terletak pada apa yang terjadi sebelum membandingkan variabel
nama lokal dengan
nol. Referensi tidak hanya disimpan dalam variabel
nama untuk objek yang baru dibuat, tetapi metode
Urutkan contoh juga disebut. Tentu, ini bukan kesalahan tetapi, bagi saya, patut diperhatikan.
Edisi 8Sepotong kode menarik lainnya:
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 masalahnya. Loop berakhir setelah kepatuhan setidaknya satu dari dua kondisi:
- sym == null ;
- sym.nextSameName == null .
Tidak ada masalah dengan kondisi kedua, yang tidak dapat dikatakan tentang kondisi pertama. Karena bidang instance
names diakses tanpa syarat di bawah ini dan jika
sym -
null , pengecualian dari tipe
NullReferenceException akan muncul.
Apakah kamu buta? Ada panggilan
Debug.Assert , di mana ia memeriksa
sym itu! = Null ”- seseorang mungkin berdebat. Justru sebaliknya, itu intinya! Saat bekerja dalam versi Rilis,
Debug.Assert tidak akan membantu apa pun dan dengan kondisi di atas, semua yang akan kita dapatkan adalah
NullReferenceException . Selain itu, saya sudah melihat kesalahan serupa di proyek lain dari Microsoft -
Roslyn , di mana situasi serupa dengan
Debug.Assert terjadi. Biarkan aku menyingkir sejenak untuk Roslyn.
Masalahnya dapat direproduksi baik ketika menggunakan perpustakaan
Microsoft.CodeAnalysis , atau tepat di Visual Studio saat menggunakan Syntax Visualizer. Di Visual Studio 16.1.6 + Sintaks Visualizer 1.0 masalah ini masih dapat direproduksi.
Kode ini cukup untuknya:
class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } }
Lebih lanjut, dalam Sintaks Visualizer kita perlu menemukan simpul pohon sintaks tipe
ConstantPatternSyntax , yang sesuai dengan
nol dalam kode dan meminta
TypeSymbol untuk itu.
Setelah itu, Visual Studio akan restart. Jika kita pergi ke Event Viewer, kita akan menemukan beberapa 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() ....
Adapun 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 ....
Dengan versi debug perpustakaan Roslyn, Anda dapat menemukan tempat di mana ada pengecualian:
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, sama seperti dalam kode dari .NET Core libraries yang dipertimbangkan di atas, ada pemeriksaan
Debug.Assert yang tidak akan membantu ketika menggunakan versi rilis libraries.
Edisi 9Kami punya sedikit titik di sini, jadi mari kita 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 cara lain untuk tidak mengatakan apa pun. Setelah melihat sekilas kode ini, saya akan mengatakan bahwa operan kiri yang pertama | | Operator yang saya temui adalah
file. Panjang> 2 , yang benar adalah yang ada dalam tanda kurung. Setidaknya kodenya diformat seperti ini. Setelah melihat sedikit lebih hati-hati, Anda bisa mengerti bahwa bukan itu masalahnya. Bahkan, operan yang tepat -
((! IsIdFile (file [0]) &&! IsInfoFile (file [0])))) . Saya pikir kode ini cukup membingungkan.
Edisi 10PVS-Studio 7.03 memperkenalkan aturan diagnostik
V3138 , yang mencari kesalahan dalam string interpolasi. Lebih tepatnya, dalam string yang kemungkinan besar harus diinterpolasi, tetapi karena simbol
$ yang terlewat, mereka tidak
. Di perpustakaan
System.Net saya menemukan beberapa kejadian menarik dari 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 dari metode
Fail harus berupa string yang diinterpolasi, di mana representasi string dari pengecualian
e akan diganti. Namun, karena simbol
$ yang terlewat, tidak ada representasi string yang diganti.
Edisi 11Ini 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 di atas, sekali lagi simbol
$ terjawab, menghasilkan string yang salah, masuk ke metode
Kesalahan .Edisi 12Paket
System.Net.Mail . Metode ini kecil, saya akan mengutipnya secara keseluruhan untuk membuat mencari bug 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; }
Peringatan PVS-Studio: V3008 Variabel '_streamSet' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 123, 119. MimePart.cs 123
Penugasan nilai ganda ke variabel
_streamSet terlihat aneh (pertama - di bawah kondisi, kemudian - di luar). Kisah yang sama dengan mengatur ulang variabel
aliran . Akibatnya,
_stream akan tetap memiliki value
stream , dan
_streamSet akan menjadi
true.Masalah 13Fragmen kode yang menarik dari pustaka
System.Linq.Expressions yang memicu 2 peringatan penganalisa sekaligus. Dalam hal ini, lebih seperti fitur, daripada bug. Namun, metode ini sangat tidak biasa ...
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 di sini.
Edisi 14Mari kita pertimbangkan kasus lain, yang akan kita tangani "dari luar". Pertama, kita akan menulis kode, mendeteksi masalah, dan kemudian kita akan melihat ke dalam. Kami akan mengambil pustaka
System.Configuration.ConfigurationManager dan paket NuGet dengan nama yang sama untuk ditinjau. Saya menggunakan paket versi 4.5.0. Kami akan berurusan dengan kelas
System.Configuration.CommaDelimitedStringCollection .
Mari kita lakukan sesuatu yang tidak canggih. Misalnya, kita akan membuat objek, mengekstrak representasi stringnya dan mendapatkan panjang string ini, lalu mencetaknya. Kode yang relevan:
CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); Console.WriteLine(collection.ToString().Length);
Untuk berjaga-jaga, kami akan memeriksa deskripsi metode
ToString :
Tidak ada yang istimewa - representasi string dari suatu objek dikembalikan. Untuk berjaga-jaga, saya akan memeriksa docs.microsoft.com - "
CommaDelimitedStringCollection.ToString Method ". Sepertinya tidak ada yang istimewa di sini.
Oke, mari kita jalankan kodenya, aaand ...
Hmm, kejutan. Baiklah, mari kita coba menambahkan item ke koleksi dan kemudian mendapatkan representasi string-nya. Selanjutnya, kita akan "benar-benar tidak sengaja" menambahkan string kosong :). Kode akan berubah dan terlihat seperti ini:
CommaDelimitedStringCollection collection = new CommaDelimitedStringCollection(); collection.Add(String.Empty); Console.WriteLine(collection.ToString().Length);
Jalankan dan lihat ...
Apa lagi? Baiklah, mari kita akhirnya membahas implementasi metode
ToString dari kelas
CommaDelimitedStringCollection . Kode di bawah ini:
public override string ToString() { if (Count <= 0) return null; StringBuilder sb = new StringBuilder(); foreach (string str in this) { ThrowIfContainsDelimiter(str);
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 dapat melihat 2 fragmen, di mana implementasi
ToString saat ini dapat mengembalikan
nol. Pada titik ini, kita akan mengingat rekomendasi oleh Microsoft tentang implementasi metode
ToString . Jadi mari kita lihat docs.microsoft.com - "
Metode Object.ToString ":
Notes to Inheritors .... Overrides dari metode ToString () harus mengikuti panduan ini:- ....
- Penggantian ToString () Anda tidak boleh mengembalikan string Kosong atau null .
- ....
Inilah yang diperingatkan PVS-Studio. Dua fragmen kode yang diberikan di atas yang kami tulis untuk mereproduksi masalah mendapatkan titik keluar yang berbeda - masing-masing poin pengembalian
nol pertama dan kedua. Mari menggali lebih dalam.
Kasus pertama.
Count adalah properti dari kelas
StringCollection dasar. Karena tidak ada elemen yang ditambahkan,
Hitung == 0 , kondisi
Hitung <= 0 benar, nilai
nol dikembalikan.
Dalam kasus kedua kami menambahkan elemen, menggunakan metode instance
CommaDelimitedStringCollection.Add untuk itu.
public new void Add(string value) { ThrowIfReadOnly(); ThrowIfContainsDelimiter(value); _modified = true; base.Add(value.Trim()); }
Cek berhasil dalam metode
ThrowIf ... dan elemen ditambahkan dalam koleksi dasar. Dengan demikian, nilai
Hitung menjadi 1. Sekarang mari kita kembali ke metode
ToString . Nilai ekspresi
Count <= 0 -
false , oleh karena itu metode tidak kembali dan eksekusi kode berlanjut. Koleksi internal akan dilintasi, 2 elemen ditambahkan ke instance dari tipe
StringBuilder - string kosong dan koma. Akibatnya, ternyata bahwa
sb hanya mengandung koma, nilai properti
Panjang masing-masing sama dengan 1. Nilai dari ekspresi
sb. Panjang> 0 benar , kurangi dan tuliskan dalam
sb. Panjang dilakukan, sekarang nilainya dari
sb.Length adalah 0. Ini mengarah pada fakta bahwa nilai
nol dikembalikan lagi dari metode.
Edisi 15Tiba-tiba, saya punya keinginan untuk menggunakan
System.Configuration.ConfigurationProperty kelas. Mari kita mengambil konstruktor dengan jumlah parameter terbesar:
public ConfigurationProperty( string name, Type type, object defaultValue, TypeConverter typeConverter, ConfigurationValidatorBase validator, ConfigurationPropertyOptions options, string description);
Mari kita lihat deskripsi parameter terakhir:
Hal yang sama ditulis dalam deskripsi konstruktor di docs.microsoft.com. Baiklah, mari kita lihat bagaimana parameter ini digunakan dalam tubuh konstruktor:
public ConfigurationProperty(...., string description) { ConstructorInit(name, type, options, validator, typeConverter); SetDefaultValue(defaultValue); }
Percaya atau tidak, parameter tidak digunakan.
Peringatan PVS-Studio: V3117 'Deskripsi' parameter konstruktor tidak digunakan. ConfigurationProperty.cs 62
Mungkin, pembuat kode sengaja tidak menggunakannya, tetapi deskripsi parameter yang relevan sangat membingungkan.
Edisi 16Berikut ini adalah bagian yang serupa: coba cari kesalahan sendiri, saya berikan kode konstruktor di bawah ini.
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, tetapi terus terang, ini terlihat agak aneh:
internal object ConfigSourceStreamVersion { set { } }
Secara umum, kode tersebut terlihat mencurigakan. Mungkin parameter / properti dibiarkan untuk kompatibilitas, tapi itu hanya dugaan saya.
Edisi 17Mari kita lihat hal-hal menarik di perpustakaan
System.Runtime.WindowsRuntime.UI.Xaml dan kode paket 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
Cerita yang sudah kita kenal - metode
ToString dapat mengembalikan nilai
nol . Karena hal ini, penulis kode pemanggil, yang mengasumsikan bahwa
RepeatBehavior.ToString selalu mengembalikan referensi yang tidak nol, mungkin akan terkejut pada beberapa titik. Sekali lagi, itu bertentangan dengan pedoman Microsoft.
Baiklah, tetapi metode ini tidak menjelaskan bahwa
ToString dapat mengembalikan
nol - kita harus masuk lebih dalam dan mengintip ke dalam 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; } }
Analiser telah mendeteksi bahwa jika cabang
default dijalankan dalam
switch ,
InternalToString akan mengembalikan nilai
nol . Oleh karena itu,
ToString akan mengembalikan
nol juga.
RepeatBehavior adalah struktur publik, dan
ToString adalah metode publik, jadi kami dapat mencoba mereproduksi masalah dalam praktik. Untuk melakukannya, kita akan membuat instance
RepeatBehavior , panggil metode
ToString darinya dan sambil melakukan itu kita tidak boleh ketinggalan bahwa
_Type tidak boleh sama dengan
RepeatBehaviorType.Forever ,
RepeatBehaviorType.Count atau
RepeatBehaviorType.Duration .
_Type adalah bidang pribadi, yang dapat ditugaskan melalui properti publik:
public struct RepeatBehavior : IFormattable { .... private RepeatBehaviorType _Type; .... public RepeatBehaviorType Type { get { return _Type; } set { _Type = value; } } .... }
Sejauh ini, sangat bagus. Mari kita lanjutkan dan lihat apa jenis
RepeatBehaviorType .
public enum RepeatBehaviorType { Count, Duration, Forever }
Seperti yang bisa kita lihat,
RepeatBehaviorType adalah enumerasi, yang mengandung ketiga elemen. Bersamaan dengan ini, ketiga elemen ini tercakup dalam ekspresi
switch yang kami minati. Namun, ini tidak berarti bahwa cabang default tidak dapat dijangkau.
Untuk mereproduksi masalah kita akan menambahkan referensi ke paket
System.Runtime.WindowsRuntime.UI.Xaml ke proyek (saya menggunakan versi 4.3.0) dan menjalankan kode berikut.
RepeatBehavior behavior = new RepeatBehavior() { Type = (RepeatBehaviorType)666 }; Console.WriteLine(behavior.ToString() is null);
True ditampilkan di konsol seperti yang diharapkan, yang berarti
ToString mengembalikan
nol , karena
_Type tidak sama dengan nilai apa pun dalam cabang
kasus , dan cabang
default menerima kontrol. Itulah yang kami coba lakukan.
Saya juga ingin mencatat bahwa tidak ada komentar untuk metode ini, atau
docs.microsoft.com menentukan bahwa metode tersebut dapat mengembalikan nilai
nol .
Edisi 18Selanjutnya, kita 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, CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace| CharType.Text| CharType.SpecialWhitespace, CharType.None, CharType.None, CharType.None| CharType.Comment| CharType.Comment| CharType.Whitespace, CharType.None, .... };
Peringatan PVS-Studio:- V3001 Ada sub-ekspresi identik 'CharType.Comment' di sebelah kiri dan di kanan '|' operator. XmlUTF8TextReader.cs 56
- V3001 Ada sub-ekspresi identik 'CharType.Comment' di sebelah kiri dan di kanan '|' operator. XmlUTF8TextReader.cs 58
- V3001 Ada sub-ekspresi identik 'CharType.Comment' ke kiri dan ke kanan '|' operator. XmlUTF8TextReader.cs 64
Penganalisa menemukan penggunaan ekspresi
CharType.Comment | CharType.Comment mencurigakan. Terlihat agak aneh, seperti
(CharType.Comment | CharType.Comment) == CharType.Comment . Saat menginisialisasi elemen array lain, yang menggunakan
CharType.Comment , tidak ada duplikasi seperti itu.
Edisi 19Mari kita lanjutkan. Mari kita periksa informasi pada nilai
pengembalian metode
XmlBinaryWriterSession.TryAdd dalam deskripsi metode dan di 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) {
Peringatan PVS-Studio: V3009 Aneh bahwa metode ini selalu mengembalikan satu dan nilai 'benar' yang sama. XmlBinaryWriterSession.cs 29
Tampaknya aneh bahwa metode mengembalikan
true atau melempar pengecualian, tetapi nilai
false tidak pernah dikembalikan.
Edisi 20Saya menemukan kode dengan masalah yang sama, tetapi dalam kasus ini, sebaliknya - metode 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 yang sama dari 'salah'. XmlObjectSerializerWriteContext.cs 415
Yah, kita sudah datang jauh! Jadi sebelum melanjutkan, saya sarankan Anda beristirahat sebentar: aduk otot, jalan-jalan, istirahatkan mata, lihat ke luar jendela ...
Saya harap pada titik ini Anda penuh energi lagi, jadi mari kita lanjutkan. :)
Edisi 21Mari kita tinjau beberapa fragmen yang menarik dari proyek
System.ecurity.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;) {
Peringatan PVS-Studio: V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'hasher'. PKCS1MaskGenerationMethod.cs 37
Penganalisis memperingatkan bahwa nilai variabel
hasher bisa menjadi
nol ketika mengevaluasi
hasher. Ekspresi
TransformBlock menghasilkan pengecualian dari tipe
NullReferenceException . Kejadian peringatan ini menjadi mungkin karena analisis antar-prosedur.
Jadi untuk mengetahui apakah
hasher dapat mengambil nilai
nol dalam kasus ini, kita perlu mencelupkan ke dalam metode
CreateFromName .
public static object CreateFromName(string name) { return CreateFromName(name, null); }
Sejauh ini tidak ada - mari kita masuk lebih dalam. Badan versi
CreateFromName yang kelebihan beban dengan dua parameter cukup besar, jadi saya mengutip versi pendeknya.
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 nilai nol dikembalikan secara eksplisit. Oleh karena itu, setidaknya secara teoritis, dalam metode di atas, yang memicu peringatan, pengecualian tipe NullReferenceException mungkin terjadi.Teori itu hebat, tetapi mari kita coba mereproduksi masalah dalam praktik. Untuk melakukan ini, kita akan melihat lagi pada metode asli dan perhatikan poin-poin utama. Kami juga akan mengurangi kode yang tidak relevan dari metode. public class PKCS1MaskGenerationMethod : ....
Mari kita lihat lebih dekat poin-poin utama:1, 3 . Kelas dan metode memiliki pengubah akses publik . Karenanya, antarmuka ini tersedia saat menambahkan referensi ke perpustakaan - kami dapat mencoba mereproduksi masalah ini.2 . Kelas adalah contoh non-abstrak, memiliki konstruktor publik. Pasti mudah untuk membuat contoh, yang akan kami kerjakan. Dalam beberapa kasus, yang saya pertimbangkan, kelas adalah abstrak, jadi untuk mereproduksi masalah saya harus mencari pewaris dan cara untuk mendapatkannya.4 . CreateFromName tidak boleh menghasilkan pengecualian dan harus mengembalikan nol - poin paling penting, kami akan kembali lagi nanti.5, 6 . The cbReturnnilai harus> 0 (tetapi, tentu saja, dalam batas yang memadai untuk pembuatan array yang berhasil). Kesesuaian kondisi cbReturn> 0 diperlukan untuk memenuhi kondisi selanjutnya ib <rgbT. Panjang dan masuk ke loop body.7 . Helpres.ConvertIntToByteArray harus berfungsi tanpa pengecualian.Untuk memenuhi kondisi yang bergantung pada parameter metode, cukup dengan memberikan argumen yang sesuai, misalnya:- rgbCeed - byte baru [] {0, 1, 2, 3};
- cbReturn - 42.
Untuk "mendiskreditkan" metode CryptoConfig.CreateFromName , kita harus dapat mengubah nilai bidang _hashNameValue . Untungnya, kami memilikinya, karena kelas mendefinisikan properti wrapper untuk bidang ini: public string HashName { get { return _hashNameValue; } set { _hashNameValue = value ?? DefaultHash; } }
Dengan menetapkan nilai 'sintetis' untuk HashName (yaitu _hashNameValue), kita bisa mendapatkan nilai nol dari metode CreateFromName di titik keluar pertama dari yang kita tandai. Saya tidak akan masuk ke detail menganalisis metode ini (harap Anda akan memaafkan saya untuk ini), karena metode ini cukup besar.Akibatnya, kode yang akan menyebabkan pengecualian dari tipe NullReferenceException , mungkin terlihat sebagai berikut: PKCS1MaskGenerationMethod tempObj = new PKCS1MaskGenerationMethod(); tempObj.HashName = "Dummy"; tempObj.GenerateMask(new byte[] { 1, 2, 3 }, 42);
Sekarang kita tambahkan referensi ke pustaka debugging, jalankan kodenya dan dapatkan hasil yang diharapkan:Hanya untuk bersenang-senang, saya mencoba menjalankan kode yang sama menggunakan paket NuGet versi 4.3.1.Tidak ada informasi tentang pengecualian yang dihasilkan, batasan parameter output dalam deskripsi metode. Docs.microsoft.com PKCS1MaskGenerationMethod.GenerateMask (Byte [], Int32) Metode "tidak menentukannya juga.Ngomong-ngomong, tepat ketika menulis artikel dan menggambarkan urutan tindakan untuk mereproduksi masalah, saya menemukan 2 cara lain untuk "Hancurkan" metode ini:- berikan nilai yang terlalu besar sebagai argumen cbReturn ;
- lulus nilai nol sebagai rgbSeed.
Dalam kasus pertama, kita akan mendapatkan pengecualian dari tipe OutOfMemoryException .Dalam kasus kedua, kita akan mendapatkan pengecualian dari tipe NullReferenceException saat mengeksekusi ekspresi rgbSeed.Length . Dalam hal ini, penting, bahwa hasher memiliki nilai bukan nol. Kalau tidak, aliran kontrol tidak akan sampai ke rgbSeed.Length .Edisi 22saya menemukan beberapa 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);
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, dalam properti FormatterAlgorithm dan DeformatterAlgorithm kita dapat menulis nilai-nilai tersebut, dimana metode CryptoConfig.CreateFromName mengembalikan nilai nol dalam metode CreateDeformatter dan CreateFormatter . Lebih lanjut, saat memanggil metode instance SetKey , pengecualian NullReferenceException akan dihasilkan. Masalahnya, sekali lagi, mudah direproduksi dalam praktik: SignatureDescription signature = new SignatureDescription() { DeformatterAlgorithm = "Dummy", FormatterAlgorithm = "Dummy" }; signature.CreateDeformatter(null);
Dalam hal ini, saat memanggil CreateDeformatter serta memanggil CreateFormatter , pengecualian dari tipe NullReferenceException dilemparkan.Edisi 23Mari kita tinjau fragmen 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); }
Peringatan PVS-Studio: V3004 Pernyataan 'then' setara dengan pernyataan 'else'. QueryOutputWriterV1.cs 242Kelihatannya aneh bahwa kemudian dan lain cabang jika pernyataan berisi kode yang sama. Entah ada kesalahan di sini dan tindakan lain harus dilakukan di salah satu cabang, atau pernyataan if 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 381Dalam urutan if-else-if ada dua ekspresi kondisional yang sama - konten adalah XmlSchemaSimpleTypeRestriction . Terlebih lagi, badan cabang kemudian dari pernyataan masing-masing berisi serangkaian ekspresi yang berbeda. Bagaimanapun, baik badan dari cabang pertama yang relevan maka akan dieksekusi (jika ekspresi kondisional benar), atau tidak ada dari mereka dalam kasus jika ekspresi yang relevan salah.Edisi 25Untuk membuatnya lebih menarik untuk mencari kesalahan pada metode selanjutnya, saya akan mengutip seluruh tubuh. 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 sudah berhasil - selamat!Jika tidak - PVS-Studio untuk penyelamatan: V3102 Akses mencurigakan ke elemen objek 'seq' dengan indeks konstan di dalam sebuah loop. XmlQueryRuntime.cs 738The untuk loop dieksekusi, ekspresi i <seq.Count adalah Digunakan sebagai dengan sebuah kondisi keluar. Ini menunjukkan gagasan bahwa pengembang ingin memotong urutan seq . Tetapi dalam loop, penulis mengakses elemen urutan bukan dengan menggunakan counter - seq [i] , tetapi sejumlah literal - nol ( seq [0] ).Edisi 26Kesalahan berikutnya cocok dengan sepotong kecil kode, tetapi tidak kalah menarik. public override void WriteValue(string value) { WriteValue(value); }
Peringatan PVS-Studio: V3110 Kemungkinan rekursi tak terbatas di dalam metode 'WriteValue'. XmlAttributeCache.cs 166Metode ini memanggil dirinya sendiri, membentuk rekursi tanpa kondisi 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); }
Peringatan PVS-Studio: V3095 Objek 'seq' digunakan sebelum diverifikasi dengan null. Periksa baris: 880, 884. XmlQueryRuntime.cs 880Metode ini bisa mendapatkan nilai nol sebagai argumen. Karena ini, ketika mengakses properti Hitung , pengecualian dari tipe NullReferenceException akan dihasilkan. Di bawah variabel nodeSeq diperiksa. nodeSeq diperoleh sebagai hasil dari seq casting eksplisit , masih belum jelas mengapa pemeriksaan dilakukan. Jika nilai seq adalah nol , aliran kontrol tidak akan sampai ke pemeriksaan ini karena pengecualian. Jika nilai seq tidaknull , lalu:- jika casting gagal, pengecualian dari tipe InvalidCastException akan dihasilkan;
- jika casting berhasil, nodeSeq jelas bukan nol .
Edisi 28saya menemukan 4 konstruktor, berisi parameter yang tidak digunakan. Mungkin, mereka dibiarkan untuk kompatibilitas, tetapi saya tidak menemukan komentar tambahan pada 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, itu masuk ke daftar peringatan untuk artikel). Apa yang spesial? Tidak yakin Mungkin namanya. public XmlSecureResolver(XmlResolver resolver, string securityUrl) { _resolver = resolver; }
Demi kepentingan, saya memeriksa apa yang ditulis 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 29Dalam paket System.Private.Uri saya menemukan metode, yang tidak mengikuti persis pedoman Microsoft pada metode ToString menimpa. Di sini kita perlu mengingat 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 membuang pengecualian dari metode 'ToSting ()'. UriBuilder.cs 406Kode pertama-tama menetapkan string kosong untuk bidang _username dan nonempty untuk bidang _password masing-masing melalui properti publik UserName dan Kata Sandi. Setelah itu memanggil metode ToString . Akhirnya kode ini akan mendapatkan pengecualian. Contoh kode tersebut: UriBuilder uriBuilder = new UriBuilder() { UserName = String.Empty, Password = "Dummy" }; String stringRepresentation = uriBuilder.ToString(); Console.WriteLine(stringRepresentation);
Tetapi dalam hal ini pengembang dengan jujur memperingatkan bahwa panggilan mungkin menghasilkan pengecualian. Ini dijelaskan dalam komentar untuk metode ini dan di docs.microsoft.com - " UriBuilder.ToString Method ".Masalah 30Lihatlah 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]; .... }
Peringatan PVS-Studio: V3106 Kemungkinan indeks sudah di luar batas. Indeks '0' menunjuk di luar batas '_tables'. XMLDiffLoader.cs 277Apakah kode ini terlihat tidak biasa? Anda pikir apa itu? Cara yang tidak biasa untuk menghasilkan pengecualian dari tipe ArgumentOutOfRangeException ? Saya tidak akan terkejut dengan pendekatan ini. Secara keseluruhan, ini kode yang sangat 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 1095dari Ekspresi LAINNYA = null! Seperti dengan argumen dari Debug.Assert metode Mengusulkan, Bahwa ComparePosition metode BISA Mendapatkan nol dari nilai seperti dengan argumen itu. Setidaknya, tujuannya adalah untuk menangkap kasus-kasus seperti itu. Tetapi pada saat yang sama, garis di atas yang lain. Metode instance realealo dipanggil. Akibatnya, jika yang lain memiliki nilai nol , pengecualian dari tipe NullReferenceException akan dihasilkan sebelum 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 534Ekspresi bersyarat pernyataan if terlihat cukup mencurigakan. Match adalah metode instan. Menurut tanda centang attr == null , nol adalah nilai yang dapat diterima (diharapkan) untuk variabel ini. Oleh karena itu, jika aliran kontrol sampai ke operan kanan || operator (jika attr - null ), kita akan mendapatkan pengecualian dari tipe NullReferenceException .Dengan demikian, kondisi terjadinya pengecualian adalah sebagai berikut:- The value of attr — null . The right operand of the && operator is evaluated.
- The value of !attribute.IsDefaultAttribute() — false . The overall result of the expression with the && operator — false .
- Since the left operand of the || operator is of the false value, the right operand is evaluated.
- Since attr — null , when calling the Match method, an exception is generated.
Issue 33 private int ReadOldRowData( DataSet ds, ref DataTable table, ref int pos, XmlReader row) { .... if (table == null) { row.Skip();
Peringatan PVS-Studio: V3021 Ada dua pernyataan 'jika' dengan ekspresi kondisional yang identik. Pernyataan 'jika' pertama berisi pengembalian metode. Ini berarti bahwa kedua 'jika' pernyataan adalah XMLDiffLoader.cs tidak masuk akal 301Ada dua dua jika pernyataan, yang Mengandung ekspresi yang sama - Tabel == null . Dengan itu, maka cabang dari pernyataan ini berisi tindakan yang berbeda - dalam kasus pertama, metode keluar dengan nilai -1, dalam yang kedua - pengecualian dihasilkan. The Tabel variabel tidak berubah untuk Antara pemeriksaan. Dengan demikian, pengecualian yang dipertimbangkan tidak akan dihasilkan.Masalah 34Lihatlah metode menarik dari System.ComponentModel.Typeconverterproyek. Baiklah, pertama mari kita baca komentar, dengan 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.Titik kunci pada nilai kembali: jika operasi berhasil, metode mengembalikan benar , jika tidak - salah . Mari kita lihat apa yang sebenarnya terjadi. public bool Remove(out int testPosition, out MaskedTextResultHint resultHint) { .... if (lastAssignedPos == INVALID_INDEX) { .... return true;
Peringatan PVS-Studio: V3009 Aneh bahwa metode ini selalu mengembalikan satu dan nilai 'benar' yang sama. MaskedTextProvider.cs 1529Bahkan, 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 terhadap null. Periksa baris: 437, 423. ConstraintCollection.cs 437The _table! = Cek kosong berbicara untuk dirinya sendiri - variabel _table dapat memiliki nilai nol . Setidaknya, dalam hal ini penulis kode direasuransikan. Namun, di bawah ini mereka menangani bidang instance melalui _table tetapi tanpa memeriksa null - _table .fInitInProgress .Masalah 36Sekarang mari kita pertimbangkan beberapa peringatan, yang dikeluarkan untuk kode 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); } }
Peringatan PVS-Studio: V3038 Argumen dilewatkan ke metode beberapa kali. Ada kemungkinan bahwa argumen lain harus disahkan. BinaryObjectWriter.cs 262Penganalisis bingung dengan panggilan terakhir _serWriter.WriteObjectEnd dengan dua argumen yang sama - typeNameInfo . Sepertinya kesalahan ketik, tapi saya tidak bisa mengatakannya dengan pasti. Saya memutuskan untuk memeriksa apa itu metode WriteObjectEnd callee . internal void WriteObjectEnd(NameInfo memberNameInfo, NameInfo typeNameInfo) { }
Baiklah ... Ayo lanjut. :)
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); }
Saat mengulas kode ini, saya tidak akan langsung mengatakan apa yang salah di sini atau apa yang tampak mencurigakan. Tetapi penganalisa mungkin mengatakan apa masalahnya.Peringatan PVS-Studio: V3066 Kemungkinan urutan argumen yang salah diteruskan ke konstruktor 'SerializationHeaderRecord': 'minorVersion' dan 'majorVersion'. BinaryFormatterWriter.cs 111Lihat konstruktor callee dari kelas SerializationHeaderRecord . internal SerializationHeaderRecord( BinaryHeaderEnum binaryHeaderEnum, int topId, int headerId, int majorVersion, int minorVersion) { _binaryHeaderEnum = binaryHeaderEnum; _topId = topId; _headerId = headerId; _majorVersion = majorVersion; _minorVersion = minorVersion; }
Seperti yang dapat kita lihat, parameter konstruktor mengikuti urutan majorVersion , minorVersion ; sedangkan ketika memanggil konstruktor, mereka dilewatkan dalam urutan ini: minorVersion , majorVersion . Sepertinya salah ketik. Dalam kasus itu dibuat dengan sengaja (bagaimana jika?) - Saya pikir itu akan membutuhkan komentar tambahan.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 33The checksecurity parameter constructor tidak Digunakan dalam cara apapun. Tidak ada komentar tentang itu. Saya kira itu dibiarkan untuk kompatibilitas, tetapi bagaimanapun, dalam konteks percakapan keamanan baru-baru ini, itu terlihat menarik.Masalah 39Inilah kode yang sepertinya tidak biasa bagi saya. Pola terlihat satu dan sama dalam ketiga kasus yang terdeteksi dan terletak di metode dengan nama yang sama dan nama variabel. Akibatnya:- baik saya tidak cukup tercerahkan untuk mendapatkan tujuan duplikasi seperti itu;
- atau kesalahan disebarkan oleh 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
Apa yang berbeda dalam metode lain adalah jenis elemen array temp (bukan FixupHolder , tetapi panjang atau objek ). Jadi saya masih memiliki kecurigaan terhadap copy-paste ...Masalah 40Kode 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 338Analyzer mengasumsikan bahwa ekspresi yang diberikan selalu memiliki nilai sebenarnya . Benar-benar begitu. Bahkan tidak masalah nilai apa sebenarnya dalam quotePrefix - kondisi itu sendiri ditulis secara tidak benar. Mari kita sampai ke bagian bawah ini.Kami memiliki || operator, sehingga nilai ekspresi akan benar , jika operan kiri atau kanan (atau keduanya) akan memiliki nilai sebenarnya . Semuanya jelas dengan yang kiri. Yang kanan akan dievaluasi hanya dalam kasus jika yang kiri memiliki yang salahnilai. Berarti yang ini, jika ungkapan tersebut Terdiri dalam cara Bahwa nilai dari operan kanan selalu untuk benar ketika nilai dari kiri seseorang adalah palsu , dengan hasil dari ekspresi Seluruh permanen akan dari BE ke true .Dari kode di atas kita tahu bahwa jika operan yang tepat dievaluasi, nilai string ekspresi.IsNullOrEmpty (quotePrefix) - true , jadi salah satu dari pernyataan ini benar:- quotePrefix == null ;
- quotePrefix.Length == 0 .
Jika salah satu dari pernyataan ini benar, ungkapan quotePrefix! = "" Akan juga benar, yang ingin kami buktikan. Berarti nilai seluruh ekspresi selalu benar , terlepas dari konten quotePrefix .Masalah 41Kembali ke 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 26Kita dapat melihat dari peringatan penganalisa dan kode, bahwa hanya satu parameter konstruktor yang tidak digunakan - userOptions , dan yang lain digunakan untuk menginisialisasi properti dengan nama yang sama. Sepertinya pengembang lupa menginisialisasi salah satu properti.Edisi 42Ada kode yang mencurigakan, yang kami temui 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. Pertama, casting eksplisit akan dilakukan, setelah itu - casting melalui operator as . Jika baris nilai ["DataType"] - null, itu akan berhasil melewati kedua coran dan akan dilakukan sebagai argumen untuk metode Tambahkan . Jika baris ["DataType"] mengembalikan nilai, yang tidak dapat dilemparkan ke tipe tipe, pengecualian dari tipe InvalidCastException akan dihasilkan tepat selama pemeran eksplisit. Pada akhirnya, mengapa kita perlu dua casting di sini? Pertanyaannya terbuka.Edisi 43Mari kita lihat fragmen mencurigakan dariSystem.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 29Penganalisis memperingatkan tentang kemungkinan pengecualian dari tipe NullReferenceException saat memanggil metode IndexOf untuk variabel versiString . Saat menerima nilai untuk variabel, pembuat kode menggunakan '?.' operator untuk menghindari pengecualian NullReferenceException ketika mengakses properti InfromationalVersion . Caranya adalah jika panggilan GetCustomAttribute <...> mengembalikan nol, pengecualian masih akan dihasilkan, tetapi di bawah ini - saat memanggil metode IndexOf , karena versionString akan memiliki nilai nol .Masalah 44Mari kita membahas proyek System.ComponentModel.Composition dan melihat melalui beberapa peringatan. Dua peringatan dikeluarkan untuk kode berikut: public static bool CanSpecialize(....) { .... object[] genericParameterConstraints = ....; GenericParameterAttributes[] genericParameterAttributes = ....;
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
Dalam kode ada pemeriksaan genericParameterAttributes! = Null dan genericParameterConstraints! = Null . Karenanya, null - nilai yang dapat diterima untuk variabel-variabel ini, kami akan mempertimbangkannya. Jika kedua variabel memiliki nilai nol , kami akan keluar dari metode, tanpa pertanyaan. Bagaimana jika salah satu dari dua variabel yang disebutkan di atas adalah nol , tetapi dengan melakukannya kita tidak keluar dari metode? Jika hal tersebut memungkinkan dan eksekusi dapat melewati loop, kami akan mendapatkan pengecualian dari tipe NullReferenceException .Masalah 45Selanjutnya kita akan pindah ke peringatan lain yang menarik dari proyek ini. Namun, mari kita lakukan sesuatu yang berbeda - pertama kita akan menggunakan kelas lagi, dan kemudian melihat kode. Selanjutnya, kita akan menambahkan referensi ke paket NuGet dengan nama yang sama dari versi pra-rilis yang tersedia terakhir dalam proyek (saya menginstal paket versi 4.6.0-preview6.19303.8). Mari kita menulis kode sederhana, misalnya, seperti: LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(null); Console.WriteLine(eq);
The yang Setara metode tidak dikomentari, I DID tidak menemukan deskripsi metode ini untuk NET Inti AT docs.microsoft.com, hanya untuk .NET Framework. Jika kita melihatnya (" Metode LazyMemberInfo.Equals (Obyek) ") - kita tidak akan melihat sesuatu yang istimewa apakah itu mengembalikan benar atau salah , tidak ada informasi tentang pengecualian yang dihasilkan. Kami akan menjalankan kode dan melihat:Kita bisa sedikit memutar dan menulis kode berikut dan juga mendapatkan output yang menarik: LazyMemberInfo lazyMemberInfo = new LazyMemberInfo(); var eq = lazyMemberInfo.Equals(typeof(String)); Console.WriteLine(eq);
Hasil dari eksekusi kode.Menariknya, kedua pengecualian ini dihasilkan dalam ekspresi yang sama. Mari kita lihat di dalam metode Equals . public override bool Equals(object obj) { LazyMemberInfo that = (LazyMemberInfo)obj;
Peringatan PVS-Studio: V3115 Melewati metode 'null' ke 'Equals' tidak boleh menghasilkan 'NullReferenceException'. LazyMemberInfo.cs 116Sebenarnya dalam hal ini kasus analyzer mengacaukan closeup sedikit, karena dengan IT, mengeluarkan peringatan untuk that._memberType ekspresi. Namun, pengecualian terjadi sebelumnya ketika mengeksekusi obj (LazyMemberInfo) . Kami sudah membuat catatan tentang itu.Saya pikir semuanya jelas dengan InvalidCastException. Mengapa NullReferenceException dihasilkan? Faktanya adalah bahwa LazyMemberInfo adalah sebuah struct, oleh karena itu, ia akan dibebaskan dari kotak. Nilai nol unboxing, pada gilirannya,mengarah ke terjadinya pengecualian NullReferenceExceptionjenis. Juga ada beberapa kesalahan ketik dalam komentar - penulis mungkin harus memperbaikinya. Pengecualian eksplisit melempar masih di tangan penulis.Masalah 46By the way, saya menemukan kasus serupa 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 53Masalahnya sama seperti dalam kasus yang dijelaskan di atas.Masalah 47Mari kita pertimbangkan beberapa fragmen dari System.Text.Json .Ingat saya menulis bahwa ToString tidak boleh mengembalikan nol ? Saatnya memantapkan pengetahuan ini. 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: {
Pada pandangan pertama, metode ini tidak mengembalikan nol , tetapi penganalisa berpendapat sebaliknya.Peringatan PVS-Studio: V3108 Tidak disarankan untuk mengembalikan 'null' dari metode 'ToSting ()'. JsonElement.cs 1460Analyzer menunjuk ke garis dengan memanggil metode GetString () . Mari kita melihatnya. public string GetString() { CheckValidInstance(); return _parent.GetString(_idx, JsonTokenType.String); }
Mari kita bahas lebih dalam versi berlebihan dari metode GetString : internal string GetString(int index, JsonTokenType expectedType) { .... if (tokenType == JsonTokenType.Null) { return null; } .... }
Tepat setelah kami melihat kondisinya, yang pelaksanaannya akan menghasilkan nilai nol - baik dari metode ini dan ToString yang awalnya kami pertimbangkan.Edisi 48Fragmen 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 179Saat memanggil metode CreateProperty , properti dirujuk beberapa kali melalui properti variabel : property.DeclaredPropertyType , property.ImplementedPropertyType , property? .PropertyInfo . Seperti yang Anda lihat, dalam satu kasus, penulis menggunakan kode '?.' operator.
Jika tidak salah di sini dan properti dapat memiliki nilai nol , operator ini tidak akan membantu, sebagai pengecualian dari tipe NullReferenceException akan dihasilkan dengan akses langsung.Masalah 49Fragmen mencurigakan berikut ini ditemukan dalam proyek System.Security.Cryptography.Xml . Mereka berpasangan, sama seperti beberapa kali dengan peringatan lainnya. Sekali lagi, kode ini terlihat seperti salin-tempel, 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); } } }
Fragmen 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 parameter docPos ditimpa sebelum nilainya digunakan. Oleh karena itu, nilai, yang digunakan sebagai argumen metode, diabaikan begitu saja.Masalah 50Mari kita pertimbangkan beberapa peringatan pada 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;
Peringatan PVS-Studio: V3095 Objek 'nilai' digunakan sebelum diverifikasi terhadap nol. Periksa baris: 8696, 8708. TdsParser.cs 8696Penganalisis bingung dengan nilai cek ! = Null di salah satu kondisi. Sepertinya hilang di sana selama refactoring, karena nilainya berulang kali. Jika nilai dapat memiliki nilai nol - semuanya buruk.Masalah 51Kesalahan berikutnya adalah dari tes, tetapi tampaknya menarik bagi saya, jadi saya memutuskan untuk mengutipnya. protected virtual TDSMessageCollection CreateQueryResponse(....) { .... if (....) { .... } else if ( lowerBatchText.Contains("name") && lowerBatchText.Contains("state") && lowerBatchText.Contains("databases") && lowerBatchText.Contains("db_name"))
Peringatan PVS-Studio: V3053 Ekspresi yang berlebihan. Periksa substring 'nama' dan 'db_name'. QueryEngine.cs 151Faktanya adalah bahwa dalam hal ini kombinasi dari subekspresi lowerBatchText.Contains ("name") dan lowerBatchText.Contains ("db_name") berlebihan. Memang, jika string yang dicentang mengandung substring "db_name" , itu akan berisi substring "nama" juga. Jika string tidak mengandung "nama" , string juga tidak akan mengandung "db_name" . Akibatnya, ternyata cek lebih rendahBatchText.Contains ("nama")berlebihan. Kecuali itu dapat mengurangi jumlah ekspresi yang dievaluasi, jika string yang dicentang tidak mengandung "nama" .Masalah 52Fragmen yang mencurigakan dari kode proyek System.Net.Requests . protected override PipelineInstruction PipelineCallback( PipelineEntry entry, ResponseDescription response, ....) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Command:{entry?.Command} Description:{response?.StatusDescription}");
Peringatan PVS-Studio: V3125 Objek 'entri' digunakan setelah diverifikasi terhadap nol. Periksa baris: 270, 227. FtpControlStream.cs 270Saat membuat string interpolasi, ekspresi seperti entri ?. Perintah dan respons ?. Deskripsi digunakan. '?.' operator digunakan alih-alih '.' Operator tidak mendapatkan pengecualian dari tipe NullReferenceException jika seandainya ada parameter yang sesuai memiliki nilai nol . Dalam hal ini, teknik ini berfungsi. Lebih lanjut, seperti yang dapat kita lihat dari kode, kemungkinan nilai nol untuk respons terpecah (keluar dari metode jika respons == null), sedangkan tidak ada yang serupa untuk masuk. Akibatnya, jika entri - null lebih lanjut di sepanjang kode ketika mengevaluasi entri. Perintah (dengan penggunaan '.', Bukan '?.'), Pengecualian akan dihasilkan.Pada titik ini, tinjauan kode yang cukup rinci sedang menunggu kami, jadi saya sarankan Anda istirahat sejenak, membuat teh atau kopi. Setelah itu saya akan di sini untuk melanjutkan.Apakah kamu kembali? Kalau begitu mari kita lanjutkan. :)
Masalah 53Sekarang mari kita cari sesuatu yang menarik dalam proyek System.Collections.Immutable . Kali ini kita akan melakukan beberapa eksperimen dengan System.Collections.Immutable.ImmutableArray struct <T> . Metode IStructuralEquatable.Equals dan IStructuralComparable.CompareTo menarik bagi kami.Mari kita mulai dengan metode IStructuralEquatable.Equals . Kode diberikan di bawah ini, saya sarankan Anda mencoba untuk mendapatkan apa yang salah sendiri: 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 Anda berhasil? Jika ya - selamat saya. :)
PVS-Studio warning: V3125 Objek 'ours' digunakan setelah diverifikasi dengan null. Periksa baris: 1212, 1204. ImmutableArray_1.cs 1212Penganalisis bingung dengan panggilan instance Equals method melalui variabel kita , yang terletak di ekspresi pengembalian terakhir , karena menunjukkan bahwa pengecualian dari tipe NullReferenceException mungkin terjadi di sini. Mengapa penganalisa menyarankan demikian? Untuk membuatnya lebih mudah dijelaskan, saya memberikan fragmen kode yang disederhanakan dari metode yang sama di bawah ini. 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); }
Dalam ekspresi terakhir, kita dapat melihat, bahwa nilai variabel kita berasal dari self.array . Cek self.array == null dilakukan beberapa kali di atas. Yang berarti, milik kita, sama seperti self.array, dapat memiliki nilai nol . Setidaknya secara teori. Apakah kondisi ini dapat dicapai dalam praktik? Mari kita coba mencari tahu. Untuk melakukan ini, sekali lagi saya mengutip tubuh metode dengan menetapkan poin-poin penting. bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { var self = this;
Poin kunci 1. self.array == this.array (karena self = ini ). Oleh karena itu, sebelum memanggil metode, kita perlu mendapatkan kondisi this.array == null .Poin kunci 2 . Kita dapat mengabaikan ini jika , yang akan menjadi cara paling sederhana untuk mendapatkan apa yang kita inginkan. Untuk mengabaikan ini jika , kita hanya perlu variabel lain dari jenis Array atau yang diturunkan, dan tidak mengandung nilai nol . Dengan cara ini, setelah menggunakan operator as , referensi non-nol akan ditulis dalam OtherArray dan kami akan mengabaikan pernyataan if pertama .Poin kunci 3. Poin ini membutuhkan pendekatan yang lebih kompleks. Kita pasti perlu keluar pada pernyataan if kedua (yang dengan ekspresi kondisional milik mereka! = Null ). Jika itu tidak terjadi dan kemudian cabang mulai dieksekusi, pasti kita tidak akan mendapatkan poin 5 yang diperlukan dalam kondisi self.array == null karena titik kunci 4. Untuk menghindari memasukkan pernyataan if dari titik kunci 3, salah satu syarat ini harus dipenuhi:- nilai lainnya harus nol ;
- tipe lain yang sebenarnya tidak boleh mengimplementasikan antarmuka IImmutableArray .
Poin kunci 5 . Jika kita sampai pada titik ini dengan nilai self.array == null , itu berarti bahwa kita telah mencapai tujuan kita, dan pengecualian dari tipe NullReferenceException akan dihasilkan.Kami mendapatkan kumpulan data berikut yang akan mengarahkan kami ke titik yang dibutuhkan.Pertama: this.array - nol .Kedua - salah satu dari yang berikut:- lainnya - null ;
- yang lain memiliki tipe Array atau yang berasal darinya;
- yang lain tidak memiliki tipe Array atau yang diturunkan darinya dan dengan melakukan itu, tidak mengimplementasikan antarmuka IImmutableArray .
array adalah bidang, dideklarasikan dengan cara berikut: internal T[] array;
Karena ImmutableArray <T> adalah struktur, ia memiliki konstruktor default (tanpa argumen) yang akan menghasilkan bidang array yang mengambil nilai secara default, yang merupakan nol. Dan itulah yang kami butuhkan.Jangan lupa bahwa kami sedang menyelidiki implementasi eksplisit dari metode antarmuka, oleh karena itu, casting harus dilakukan sebelum panggilan.Sekarang kita memiliki permainan di tangan untuk mencapai kejadian pengecualian dalam tiga cara. Kami menambahkan referensi ke versi pustaka debugging, menulis kode, mengeksekusi dan melihat apa yang terjadi.Fragmen kode 1. var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(null, comparer);
Fragmen kode 2. var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(new string[] { }, comparer);
Fragmen kode 3. var comparer = EqualityComparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralEquatable)immutableArray).Equals(typeof(Object), comparer);
Hasil eksekusi dari ketiga fragmen kode akan sama, hanya dicapai oleh input data input yang berbeda, dan jalur eksekusi.Edisi 54Jika Anda tidak lupa, kami memiliki metode lain yang perlu kami mendiskreditkan. :) Tapi kali ini kami tidak akan membahasnya secara mendetail. Apalagi kita 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);
PVS-Studio warning: V3125 Objek 'ours' digunakan setelah diverifikasi dengan null. Periksa baris: 1265, 1251. ImmutableArray_1.cs 1265Seperti yang Anda lihat, kasus ini sangat mirip dengan contoh sebelumnya.Mari kita menulis kode berikut: Object other = ....; var comparer = Comparer<String>.Default; ImmutableArray<String> immutableArray = new ImmutableArray<string>(); ((IStructuralComparable)immutableArray).CompareTo(other, comparer);
Kami akan mencoba menemukan beberapa data entri untuk mencapai titik tersebut, di mana pengecualian dari tipe NullReferenceException mungkin terjadi:Nilai: lainnya - string baru [] {} ;Hasil:Jadi, kami kembali berhasil menemukan data tersebut, dengan pengecualian yang terjadi dalam metode ini.Masalah 55Dalam proyek System.Net. HTTP Listener, saya menemukan beberapa tempat mencurigakan dan sangat mirip. Sekali lagi, saya tidak bisa menghilangkan perasaan tentang copy-paste, yang terjadi di sini. Karena polanya sama, kita akan melihat satu contoh kode. Saya akan mengutip peringatan penganalisa untuk kasus-kasus lainnya. 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)); } .... }
Peringatan PVS-Studio: V3095 Objek 'buffer' digunakan sebelum diverifikasi terhadap null. Periksa baris: 51, 53. HttpRequestStream.cs 51Generasi pengecualian tipe ArgumentNullException di bawah buffer kondisi == null jelas menunjukkan bahwa null adalah nilai yang tidak dapat diterima untuk variabel ini. Namun, jika nilai ekspresi NetEventSource.IsEnabled benar dan buffer - null , ketika mengevaluasi buffer. Ekspresi panjang, pengecualian dari tipe NullReferenceException akan dihasilkan. Seperti yang bisa kita lihat, kita bahkan tidak akan mencapaibuffer == null periksa dalam hal ini.Peringatan PVS-Studio dikeluarkan untuk metode lain dengan pola:- 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 56Potongan kode serupa ada 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 3282Dalam kondisi tertentu, penulis ingin melempar pengecualian dari tipe InvalidOperationException . Saat memanggil metode untuk membuat objek pengecualian, pembuat kode menggunakan parameter tx , periksa untuk null untuk menghindari pengecualian dari tipe NullReferenceException saat mengevaluasi ekspresi tx.DistributedTxId . Sangat ironis bahwa pemeriksaan tidak akan membantu, seperti ketika mengevaluasi kondisi pernyataan if , bidang contoh diakses melalui variabel tx -tx._outcomeSource._isoLevel .Masalah 57Kode dari proyek System.Runtime.Caching . internal void SetLimit(int cacheMemoryLimitMegabytes) { long cacheMemoryLimit = cacheMemoryLimitMegabytes; cacheMemoryLimit = cacheMemoryLimit << MEGABYTE_SHIFT; _memoryLimit = 0;
Peringatan PVS-Studio: Ekspresi V3022 'cacheMemoryLimit! = 0 && _memoryLimit! = 0' selalu salah. CacheMemoryMonitor.cs 250Jika Anda memperhatikan kode ini, Anda akan melihat bahwa salah satu ekspresi - cacheMemoryLimit! = 0 && _memoryLimit! = 0 akan selalu salah . Karena _memoryLimit memiliki nilai 0 (diatur sebelum pernyataan if ), operan kanan operator && salah . Oleh karena itu, hasil dari seluruh ekspresi salah .Masalah 58Saya mengutip fragmen kode yang mencurigakan dari proyek System.Diagnostics.TraceSource di bawah ini. 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 115Bahkan, ini adalah kasus yang menarik. Karena centang n == null, saya berasumsi, bahwa null adalah nilai yang diharapkan untuk variabel lokal ini. Jika demikian, pengecualian dari tipe NullReferenceException akan dihasilkan ketika mengakses properti instance - n.Prev . Jika dalam kasus ini n tidak pernah bisa menjadi nol , base.Pop () tidak akan pernah dipanggil.Masalah 59Fragmen kode yang menarik dari System.Drawing.Primitiveproyek. Sekali lagi, saya sarankan Anda mencoba menemukan masalahnya sendiri. Berikut 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) {
Oke, oke, hanya bercanda ... Atau apakah Anda masih menemukan sesuatu? Bagaimanapun, mari kita kurangi kodenya untuk secara jelas menyatakan masalahnya.Ini adalah versi kode pendek: 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 302Saya tidak bisa mengatakan dengan pasti, tapi saya pikir itu adalah kesalahan. Dalam kasus lain, ketika pengembang ingin mengembalikan nilai yang sama untuk beberapa enumerator, ia menggunakan beberapa case , mengikuti satu sama lain. Dan cukup mudah untuk membuat kesalahan dengan copy-paste di sini, saya pikir.Mari menggali lebih dalam. Untuk mendapatkan nilai "buttonface" dari metode ToHtml yang dianalisis , Anda bisa meneruskan salah satu dari nilai berikut ini ke sana (diharapkan):- SystemColors.Control ;
- SystemColors.ControlLight .
Jika kami memeriksa nilai ARGB untuk masing-masing warna ini, kami akan melihat yang berikut:- SystemColors.Control - (255, 240, 240, 240) ;
- SystemColors.ControlLight - (255, 227, 227, 227) .
Jika kita memanggil metode konversi terbalik FromHtml pada nilai yang diterima ( "buttonface" ), kita akan mendapatkan Kontrol warna (255, 240, 240, 240) . Bisakah kita mendapatkan warna ControlLight dari FromHtml ? Ya Metode ini berisi tabel warna, yang merupakan dasar untuk menyusun warna (dalam hal ini). 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 persis apa yang seharusnya digunakan dalam kasusiketahuiColor.ControlLight .Edisi 60Kami akan memeriksa 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 612Nilai ditulis dalam variabel lokal yang tersisa , tetapi tidak lagi digunakan dalam metode. Mungkin, beberapa kode, menggunakannya, dihapus, tetapi variabel itu sendiri dilupakan. Atau ada kesalahan penting dan variabel ini harus entah bagaimana digunakan.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 523Penganalisa dengan tepat mencatat, bahwa bagian dari ekspresi _rangelist.Count> 0 akan selalu benar , jika kode ini dieksekusi. Bahkan jika daftar ini (yang menunjuk pada penginjil ), kosong, setelah menambahkan elemen _rangelist.Add (....) itu tidak akan sama.Masalah 62Mari kita lihat peringatan aturan diagnostik V3128 dalam proyek System.Drawing.Common dan System.Transactions.Local . private class ArrayEnumerator : IEnumerator { private object[] _array; private object _item; private int _index; private int _startIndex; private int _endIndex; public ArrayEnumerator(object[] array, int startIndex, int count) { _array = array; _startIndex = startIndex; _endIndex = _index + count; _index = _startIndex; } .... }
Peringatan PVS-Studio: V3128 Kolom '_index' digunakan sebelum diinisialisasi dalam konstruktor. PrinterSettings.Windows.cs 1679Saat menginisialisasi bidang _endIndex , bidang _index lain digunakan, yang memiliki nilai standar standar (int) , (yaitu 0 ) pada saat penggunaannya. Bidang _index diinisialisasi di bawah. Jika itu bukan kesalahan - variabel _index seharusnya dihilangkan dalam ekspresi ini agar tidak membingungkan.Edisi 63 internal class TransactionTable { .... private int _timerInterval; .... internal TransactionTable() {
Peringatan PVS-Studio: V3128 Kolom '_timerInterval' digunakan sebelum diinisialisasi dalam konstruktor. TransactionTable.cs 151Kasusnya mirip dengan yang di atas. Pertama nilai bidang _timerInterval digunakan (saat ini masih default (int) ) untuk menginisialisasi _timer. Hanya setelah itu bidang _timerInterval itu sendiri akan diinisialisasi.Edisi 64Peringatan berikutnya dikeluarkan oleh aturan diagnostik, yang masih dalam pengembangan. Tidak ada dokumentasi atau pesan terakhir, tetapi kami telah menemukan beberapa fragmen menarik dengan bantuannya. Lagi-lagi fragmen ini terlihat seperti salin-tempel , jadi kami hanya akan mempertimbangkan satu fragmen 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 974dari Caranya Bahwa adalah setelah memeriksa reference.IsAlive , sampah dari kekuatan Dikumpulkan yang BE dan objek, yang para WeakReference poin, BE akan sampah Dikumpulkan. Dalam hal ini, Target akan mengembalikan nilai nol . Akibatnya, ketika mengakses bidang instance _ldapHandle , pengecualian dari tipe NullReferenceException akan terjadi. Microsoft sendiri memperingatkan tentang jebakan ini dengan cek 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.Summary on Analysis
Apakah ini semua kesalahan dan tempat menarik, ditemukan selama analisis? Tentu saja tidak! Ketika melihat melalui hasil analisis, saya benar-benar memeriksa peringatan. Ketika jumlah mereka bertambah dan menjadi jelas ada cukup banyak dari mereka untuk sebuah artikel, saya menggulung hasilnya, mencoba untuk memilih hanya yang menurut saya paling menarik. Ketika saya sampai pada yang terakhir (log terbesar), saya hanya bisa melihat peringatan sampai pemandangan menangkap sesuatu yang tidak biasa. Jadi, jika Anda menggali, saya yakin Anda dapat menemukan tempat-tempat yang jauh lebih menarik.Contoh, saya dari diabaikan oleh Hampir semua V3022 dan V3063 peringatan. Jadi bisa dikatakan, jika saya menemukan kode seperti itu: String str = null; if (str == null) ....
Saya akan menghilangkannya, karena ada banyak tempat menarik yang ingin saya gambarkan. Ada peringatan tentang penguncian yang tidak aman menggunakan pernyataan kunci dengan penguncian oleh ini dan seterusnya - V3090 ; panggilan acara tidak aman - V3083 ; objek, yang jenisnya menerapkan IDisposable , tetapi yang Buang / Tutup tidak disebut - V3072 dan diagnostik serupa dan banyak lagi.Saya juga tidak mencatat masalah, ditulis dalam tes. Setidaknya, saya mencoba, tetapi tidak sengaja bisa mengambilnya. Kecuali beberapa tempat yang menurut saya cukup menarik untuk menarik perhatian mereka. Tetapi kode pengujian juga dapat mengandung kesalahan, karena itu tes akan bekerja secara tidak benar.Secara umum, masih banyak hal yang harus diselidiki - tetapi saya tidak memiliki niat untuk menandai semua masalah yang ditemukan .Kualitas kode itu terasa tidak merata bagi saya. Beberapa proyek benar-benar bersih, yang lain berisi tempat-tempat yang mencurigakan. Mungkin kita bisa mengharapkan proyek bersih, terutama ketika datang ke kelas perpustakaan yang paling umum digunakan.Singkatnya, kita dapat mengatakan, bahwa kodenya berkualitas cukup tinggi, karena jumlahnya cukup besar. Tapi, seperti artikel ini sarankan, ada beberapa sudut gelap.Ngomong-ngomong, proyek sebesar ini juga merupakan tes yang bagus untuk penganalisa. Saya berhasil menemukan sejumlah peringatan palsu / aneh yang saya pilih untuk dipelajari dan dikoreksi. Jadi sebagai hasil analisis, saya berhasil menemukan poin, di mana kita harus bekerja pada PVS-Studio itu sendiri.Kesimpulan
Jika Anda sampai di tempat ini dengan membaca seluruh artikel - biarkan saya menjabat tangan Anda! Saya harap saya bisa menunjukkan kepada Anda kesalahan yang menarik dan menunjukkan manfaat analisis statis. Jika Anda telah mempelajari sesuatu yang baru untuk diri Anda sendiri, itu akan membuat Anda menulis kode yang lebih baik - saya akan sangat senang.Bagaimanapun, beberapa bantuan oleh analisis statis tidak akan merugikan, jadi sarankan Anda mencoba PVS-Studio pada proyek Anda dan melihat tempat menarik apa yang dapat ditemukan dengan penggunaannya. Jika Anda memiliki pertanyaan atau Anda hanya ingin berbagi fragmen yang menarik - jangan ragu untuk menulis di support@viva64.com . :)
Salam terbaik!PS Untuk .NET Core pustaka pengembang
Terima kasih banyak atas apa yang kamu lakukan! Kerja bagus! Semoga artikel ini akan membantu Anda membuat kode sedikit lebih baik. Ingat, bahwa saya belum menulis semua tempat yang mencurigakan dan Anda sebaiknya memeriksa sendiri proyek menggunakan penganalisis. Dengan cara ini, Anda dapat menyelidiki semua peringatan secara detail. Selain itu, akan lebih mudah untuk bekerja dengannya, daripada dengan log teks sederhana / daftar kesalahan ( saya menulis tentang ini lebih terinci di sini ).