Kami senang mencari bug di proyek Microsoft. Mengapa Ini sederhana: proyek mereka biasanya mudah diperiksa (pekerjaan dapat dilakukan segera di lingkungan Visual Studio, yang mana PVS-Studio memiliki plug-in yang nyaman) dan mereka mengandung beberapa kesalahan. Oleh karena itu, algoritma pekerjaan yang biasa adalah sebagai berikut: cari dan unduh proyek terbuka dari MS; lihat itu; pilih kesalahan yang menarik; pastikan jumlahnya sedikit; menulis artikel tanpa lupa memuji pengembangnya. Hebat! Menang-menang-menang: butuh sedikit waktu, manual ini senang melihat materi baru di blog, dan karma dalam urutan yang sempurna. Tapi kali ini ada yang tidak beres. Mari kita lihat apa yang menarik ditemukan dalam kode sumber Formulir Windows dan apakah Microsoft harus dipuji saat ini.
PendahuluanPada awal Desember 2018, Microsoft
mengumumkan rilis .NET Core 3 Preview 1. Sedikit lebih awal (kira-kira dari pertengahan Oktober), GitHub mulai bekerja aktif pada publikasi kode sumber untuk
Windows Forms , platform .NET Core UI untuk membuat aplikasi desktop Windows. Anda dapat melihat statistik komit di
sini . Sekarang siapa pun dapat mengunduh kode sumber WinForms untuk ditinjau.
Saya juga mengunduh sumber untuk mencari kesalahan di sana menggunakan PVS-Studio. Cek tidak menimbulkan kesulitan. Diperlukan: Visual Studio 2019, .NET Core 3.0 SDK Preview, PVS-Studio. Dan sekarang log peringatan penganalisa diterima.
Setelah menerima log PVS-Studio, saya biasanya mengurutkannya dalam urutan nomor diagnostik (jendela dengan log pesan PVS-Studio di Visual Studio memiliki berbagai opsi untuk menyortir dan memfilter daftar). Ini memungkinkan Anda untuk bekerja dengan kelompok kesalahan dari jenis yang sama, yang sangat menyederhanakan analisis kode sumber. Saya menandai kesalahan menarik dalam daftar dengan tanda bintang, dan hanya setelah itu, setelah menganalisis seluruh log, saya menulis fragmen kode dan membuat deskripsi. Dan karena biasanya ada beberapa kesalahan, saya mencampurnya, mencoba untuk menempatkan yang paling menarik di awal dan akhir artikel. Tapi kali ini, kesalahannya ternyata agak banyak (eh, itu tidak mungkin untuk menyimpan intrik untuk waktu yang lama), dan saya akan membawanya sesuai urutan nomor diagnostik.
Apa yang ditemukan? 833 peringatan dari tingkat kepercayaan Tinggi dan Menengah (249 dan 584, masing-masing) dikeluarkan untuk 540.000 baris kode (tidak termasuk yang kosong) dalam 1670 file cs. Dan ya, menurut tradisi, saya tidak memeriksa tes dan tidak mempertimbangkan peringatan tingkat kepercayaan rendah (215 dikeluarkan). Menurut pengamatan saya sebelumnya, ada terlalu banyak peringatan untuk proyek dari MS. Tetapi tidak semua peringatan adalah kesalahan.
Untuk proyek ini, jumlah positif palsu sekitar 30%. Dalam sekitar 20% kasus, saya tidak bisa membuat kesimpulan yang pasti tentang apakah ini kesalahan atau tidak, karena saya tidak begitu mengenal kode itu. Ya, setidaknya 20% dari kesalahan yang saya lewatkan dapat dikaitkan dengan faktor manusia: tergesa-gesa, kelelahan, dll. Ngomong-ngomong, efek sebaliknya juga mungkin: Saya melihat melalui beberapa jenis pemicu yang sama, yang jumlahnya bisa mencapai 70-80, melalui yang kadang-kadang dapat meningkatkan jumlah kesalahan yang saya anggap nyata.
Dalam kasus apa pun, 30% peringatan menunjukkan kesalahan nyata, yang merupakan persentase yang sangat besar, mengingat bahwa alat analisa belum dikonfigurasikan sebelumnya.
Jadi, jumlah kesalahan yang bisa saya deteksi adalah sekitar 240, yang berada dalam batas statistik. Saya ulangi, menurut pendapat saya, untuk proyek dari MS ini bukan hasil yang paling luar biasa (meskipun ini hanya akan 0,44 kesalahan per 1000 baris kode), dan mungkin ada lebih banyak kesalahan nyata dalam kode WinForms. Saya mengusulkan untuk membicarakan alasan di akhir artikel, tetapi sekarang mari kita lihat kesalahan yang paling menarik.
KesalahanPVS-Studio:
V3003 Penggunaan
pola 'if (A) {...} else if (A) {...}' terdeteksi. Ada kemungkinan kehadiran kesalahan logis. Periksa baris: 213, 224. ButtonStandardAdapter.cs 213
void PaintWorker(PaintEventArgs e, bool up, CheckState state) { up = up && state == CheckState.Unchecked; .... if (up & IsHighContrastHighlighted()) { .... } else if (up & IsHighContrastHighlighted()) { .... } else { .... } .... }
Dalam
jika dan
jika blok, kondisi yang sama diperiksa. Sepertinya copy-paste. Apakah ini sebuah kesalahan? Jika Anda melihat deklarasi metode
IsHighContrastHighlighted , ada keraguan:
protected bool IsHighContrastHighlighted() { return SystemInformation.HighContrast && Application.RenderWithVisualStyles && (Control.Focused || Control.MouseIsOver || (Control.IsDefault && Control.Enabled)); }
Metode ini mungkin dapat mengembalikan nilai yang berbeda dalam panggilan yang berurutan. Dan apa yang dilakukan dalam metode pemanggilan tampak aneh, tentu saja, tetapi ia memiliki hak untuk hidup. Namun, saya akan menyarankan para penulis untuk melihat potongan kode ini. Untuk jaga-jaga. Namun, ini adalah contoh yang baik tentang betapa sulitnya untuk menarik kesimpulan ketika menganalisis kode asing.
PVS-Studio:
V3004 Pernyataan 'then' setara dengan pernyataan 'else'. RichTextBox.cs 1018
public int SelectionCharOffset { get { int selCharOffset = 0; .... NativeMethods.CHARFORMATA cf = GetCharFormat(true);
Dan di sini kesalahan copy-paste pasti dibuat. Terlepas dari kondisinya, variabel
selCharOffset akan selalu mendapatkan nilai yang sama.
Ada dua kesalahan serupa dalam kode WinForms:
- V3004 Pernyataan 'lalu' setara dengan pernyataan 'lain'. SplitContainer.cs 1700
- V3004 Pernyataan 'lalu' setara dengan pernyataan 'lain'. ToolstripProfessionalRenderer.cs 371
PVS-Studio:
V3008 Variabel diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 681, 680. ProfessionalColorTable.cs 681
internal void InitSystemColors(ref Dictionary<KnownColors, Color> rgbTable) { .... rgbTable[ProfessionalColorTable.KnownColors.msocbvcrCBBdrOuterDocked] = buttonFace; rgbTable[ProfessionalColorTable.KnownColors.msocbvcrCBBdrOuterDocked] = buttonShadow; .... }
Metode mengisi kamus
rgbTable . Alat analisis menunjuk ke sepotong kode di mana dua nilai berbeda ditulis secara berurutan pada kunci yang sama. Dan semuanya akan baik-baik saja, tetapi ada 16 tempat seperti itu dalam metode ini. Ini tidak lagi terlihat seperti satu kesalahan. Tetapi mengapa melakukan ini, bagi saya itu tetap menjadi misteri. Saya tidak menemukan tanda-tanda kode autogenerated. Di editor, ini terlihat seperti ini:
Saya akan memberikan sepuluh operasi pertama dengan daftar:
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 785, 784. ProfessionalColorTable.cs 785
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 787, 786. ProfessionalColorTable.cs 787
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 789, 788. ProfessionalColorTable.cs 789
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 791, 790. ProfessionalColorTable.cs 791
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 797, 796. ProfessionalColorTable.cs 797
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 799, 798. ProfessionalColorTable.cs 799
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 807, 806. ProfessionalColorTable.cs 807
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 815, 814. ProfessionalColorTable.cs 815
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 817, 816. ProfessionalColorTable.cs 817
- V3008 Variabel ini diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 823, 822. ProfessionalColorTable.cs 823
PVS-Studio:
V3011 Dua kondisi yang berlawanan ditemui. Kondisi kedua selalu salah. Periksa baris: 5242, 5240. DataGrid.cs 5242
private void CheckHierarchyState() { if (checkHierarchy && listManager != null && myGridTable != null) { if (myGridTable == null)
Pernyataan pengembalian tidak akan pernah dieksekusi. Kemungkinan besar, kondisi
myGridTable! = Null di eksternal
jika blok ditambahkan kemudian selama refactoring. Dan sekarang memeriksa
myGridTable == null tidak ada gunanya. Untuk meningkatkan kualitas kode, hapus centang ini.
PVS-Studio:
V3019 Mungkin variabel yang salah dibandingkan dengan nol setelah konversi jenis menggunakan kata kunci 'sebagai'. Periksa variabel 'kiri', 'cscLeft'. TypeCodeDomSerializer.cs 611
PVS-Studio:
V3019 Mungkin variabel yang salah dibandingkan dengan nol setelah konversi jenis menggunakan kata kunci 'sebagai'. Periksa variabel 'benar', 'cscRight'. TypeCodeDomSerializer.cs 615
public int Compare(object left, object right) { OrderedCodeStatementCollection cscLeft = left as OrderedCodeStatementCollection; OrderedCodeStatementCollection cscRight = right as OrderedCodeStatementCollection; if (left == null) { return 1; } else if (right == null) { return -1; } else if (right == left) { return 0; } return cscLeft.Order - cscRight.Order;
Penganalisa segera mengeluarkan dua peringatan untuk metode
Bandingkan . Apa masalahnya? Ini adalah bahwa nilai-nilai
cscLeft dan
cscRight tidak diperiksa untuk kesetaraan
nol . Mereka bisa mendapatkan nilai ini setelah melakukan pengecoran yang gagal ke jenis
OrderedCodeStatementCollection . Maka pengecualian akan dilemparkan dalam
pernyataan pengembalian terakhir. Situasi seperti ini dimungkinkan ketika semua memeriksa lulus
kiri dan
kanan dan tidak mengarah ke jalan keluar awal dari metode.
Untuk memperbaiki kodenya,
cscLeft /
cscRight harus digunakan di
mana-mana alih-alih dari
kiri /
kanan .
PVS-Studio:
V3020 'istirahat' tanpa syarat dalam satu lingkaran. SelectionService.cs 421
void ISelectionService.SetSelectedComponents( ICollection components, SelectionTypes selectionType) { ....
Fragmen ini merujuk, lebih tepatnya, ke kode "dengan bau". Tidak ada kesalahan di sini. Tetapi muncul pertanyaan tentang bagaimana siklus
pendahuluan diatur. Mengapa diperlukan di sini - jelas: karena kebutuhan untuk mengekstrak elemen koleksi, dinyatakan sebagai
ICollection . Tetapi mengapa dalam loop, awalnya dirancang untuk iterasi tunggal (prasyarat adalah kehadiran elemen tunggal dalam koleksi
komponen ), jaring pengaman tambahan dalam bentuk
istirahat diperlukan? Mungkin, jawabannya dapat dipertimbangkan: "Sudah berkembang secara historis." Kode terlihat jelek.
PVS-Studio: Ekspresi V3022 'ocxState! = Null' selalu benar. AxHost.cs 2186
public State OcxState { .... set { .... if (value == null) { return; } .... ocxState = value; if (ocxState != null)
Karena kesalahan logis, "kode mati" dibentuk dalam fragmen ini. Ekspresi di blok
lain tidak akan pernah dieksekusi.
PVS-Studio:
V3027 Variabel 'e' digunakan dalam ekspresi logis sebelum diverifikasi terhadap nol dalam ekspresi logis yang sama. ImageEditor.cs 99
public override object EditValue(....) { .... ImageEditor e = ....; Type myClass = GetType(); if (!myClass.Equals(e.GetType()) && e != null && myClass.IsInstanceOfType(e)) { .... } .... }
Variabel
e dalam kondisi ini pertama kali digunakan dan kemudian diperiksa untuk ketimpangan
nol . Hai,
NullReferenceException .
Kesalahan serupa lainnya:
PVS-Studio:
V3027 Variabel 'dropDownItem' digunakan dalam ekspresi logis sebelum diverifikasi terhadap nol dalam ekspresi logis yang sama. ToolStripMenuItemDesigner.cs 1351
internal void EnterInSituEdit(ToolStripItem toolItem) { .... ToolStripDropDownItem dropDownItem = toolItem as ToolStripDropDownItem; if (!(dropDownItem.Owner is ToolStripDropDownMenu) && dropDownItem != null && dropDownItem.Bounds.Width < commitedEditorNode.Bounds.Width) { .... } .... }
Situasi yang mirip dengan yang sebelumnya, hanya dengan variabel
dropDownItem . Saya pikir kesalahan seperti itu muncul sebagai akibat dari kurangnya perhatian selama refactoring. Mungkin bagian dari kondisinya
! (DropDownItem.Owner adalah ToolStripDropDownMenu) ditambahkan ke kode nanti.
PVS-Studio:
V3030 Pemeriksaan berulang. Kondisi 'kolomCount> 0' sudah diverifikasi di baris 3900. ListView.cs 3903
internal ColumnHeader InsertColumn( int index, ColumnHeader ch, bool refreshSubItems) { ....
Kesalahan yang mungkin tampak tidak berbahaya. Memang, well, pemeriksaan ekstra dilakukan, yang tidak mempengaruhi logika kerja. Dan kadang-kadang mereka bahkan melakukannya ketika Anda perlu memeriksa kembali status komponen visual, misalnya, dengan mendapatkan jumlah entri dalam daftar. Hanya dalam kasus ini, mereka memeriksa ulang kolom Variabel
lokal . Ini sangat mencurigakan. Entah mereka ingin memeriksa variabel lain, atau di salah satu cek mereka menggunakan kondisi yang salah.
PVS-Studio:
V3061 Parameter 'lprcClipRect' selalu ditulis ulang dalam tubuh metode sebelum digunakan. WebBrowserSiteBase.cs 281
int UnsafeNativeMethods.IOleInPlaceSite.GetWindowContext( out UnsafeNativeMethods.IOleInPlaceFrame ppFrame, out UnsafeNativeMethods.IOleInPlaceUIWindow ppDoc, NativeMethods.COMRECT lprcPosRect, NativeMethods.COMRECT lprcClipRect, NativeMethods.tagOIFI lpFrameInfo) { ppDoc = null; ppFrame = Host.GetParentContainer(); lprcPosRect.left = Host.Bounds.X; lprcPosRect.top = Host.Bounds.Y; .... lprcClipRect = WebBrowserHelper.GetClipRect();
Kesalahan yang tidak terlihat. Ya, parameter
lprcClipRect benar
- benar diinisialisasi dengan nilai baru, tanpa menggunakannya dengan cara apa pun. Tetapi apa akibatnya ini? Saya pikir di suatu tempat di kode panggilan, tautan yang melewati parameter ini akan tetap tidak berubah, meskipun itu dimaksudkan secara berbeda. Memang, lihat bekerja dengan variabel lain dalam metode ini. Bahkan namanya (awalan "Get") mengisyaratkan bahwa beberapa inisialisasi akan dibuat di dalam metode melalui parameter yang diteruskan. Begitulah. Dua parameter pertama (
ppFrame dan
ppDoc ) dilewatkan dengan pengubah
keluar dan mendapatkan nilai baru. Tautan
lprcPosRect dan
lpFrameInfo digunakan untuk mengakses bidang kelas dan menginisialisasi mereka. Dan hanya
lprcClipRect yang keluar dari daftar umum. Kemungkinan besar, pengubah
out atau
ref diperlukan untuk parameter ini.
PVS-Studio:
V3066 Kemungkinan urutan argumen yang salah diteruskan ke metode 'AdjustCellBorderStyle': 'isFirstDisplayedRow' dan 'isFirstDisplayedColumn'. DataGridViewComboBoxCell.cs 1934
protected override void OnMouseMove(DataGridViewCellMouseEventArgs e) { .... dgvabsEffective = AdjustCellBorderStyle( DataGridView.AdvancedCellBorderStyle, dgvabsPlaceholder, singleVerticalBorderAdded, singleHorizontalBorderAdded, isFirstDisplayedRow,
Penganalisa curiga bahwa dua argumen terakhir tercampur aduk. Mari kita lihat deklarasi metode
AdjustCellBorderStyle :
public virtual DataGridViewAdvancedBorderStyle AdjustCellBorderStyle( DataGridViewAdvancedBorderStyledataGridViewAdvancedBorderStyleInput, DataGridViewAdvancedBorderStyle dataGridViewAdvancedBorderStylePlaceholder, bool singleVerticalBorderAdded, bool singleHorizontalBorderAdded, bool isFirstDisplayedColumn, bool isFirstDisplayedRow) { .... }
Tampak seperti kesalahan. Ya, sering beberapa argumen disengaja secara terbalik, misalnya, untuk menukar beberapa variabel. Tetapi saya tidak berpikir bahwa inilah masalahnya. Tidak ada dalam pemanggilan atau metode dipanggil mengatakan pola penggunaan seperti itu. Pertama, variabel tipe
bool bingung. Kedua, nama-nama metode juga umum: tidak ada "Swap" atau "Reverse". Lagipula, tidak begitu sulit untuk melakukan kesalahan seperti itu. Orang sering merasakan urutan pasangan baris / kolom berbeda. Bagi saya, misalnya, hanya "baris / kolom" sudah biasa. Tetapi bagi penulis metode
AdjustCellBorderStyle yang disebut, jelas, urutan yang lebih akrab adalah "kolom / baris".
PVS-Studio:
V3070 Variabel tidak diinisialisasi 'LANG_USER_DEFAULT' digunakan ketika menginisialisasi variabel 'LOCALE_USER_DEFAULT'. NativeMethods.cs 890
internal static class NativeMethods { .... public static readonly int LOCALE_USER_DEFAULT = MAKELCID(LANG_USER_DEFAULT); public static readonly int LANG_USER_DEFAULT = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); .... }
Kesalahan langka. Urutan inisialisasi bidang kelas dicampur. Untuk menghitung nilai bidang
LOCALE_USER_DEFAULT , gunakan bidang
LANG_USER_DEFAULT , yang belum diinisialisasi dan memiliki nilai 0. By the way, variabel
LANG_USER_DEFAULT tidak digunakan di tempat lain dalam kode. Saya tidak terlalu malas dan menulis program konsol kecil yang mensimulasikan situasi. Alih-alih nilai dari beberapa konstanta yang digunakan dalam kode WinForms, saya menggantikan nilai aktualnya:
internal static class NativeMethods { public static readonly int LOCALE_USER_DEFAULT = MAKELCID(LANG_USER_DEFAULT); public static readonly int LANG_USER_DEFAULT = MAKELANGID(0x00, 0x01); public static int MAKELANGID(int primary, int sub) { return ((((ushort)(sub)) << 10) | (ushort)(primary)); } public static int MAKELCID(int lgid) { return MAKELCID(lgid, 0x0); } public static int MAKELCID(int lgid, int sort) { return ((0xFFFF & lgid) | (((0x000f) & sort) << 16)); } } class Program { static void Main() { System.Console.WriteLine(NativeMethods.LOCALE_USER_DEFAULT); } }
Sebagai hasil dari peluncuran, berikut ini akan ditampilkan pada konsol: 0. Sekarang kita menukar deklarasi bidang
LOCALE_USER_DEFAULT dan
LANG_USER_DEFAULT . Hasil program dalam bentuk ini: 1024. Saya pikir tidak ada lagi yang perlu dikomentari di sini.
PVS-Studio:
V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'ces'. CodeDomSerializerBase.cs 562
protected void DeserializeStatement( IDesignerSerializationManager manager, CodeStatement statement) { .... CodeExpressionStatement ces = statement as CodeExpressionStatement; if (ces != null) { .... } else { .... DeserializeExpression(manager, null, ces.Expression);
Kode yang harus "jatuh" cukup stabil, karena Anda bisa masuk ke cabang
lain tepat ketika referensi
ces adalah
nol .
Contoh serupa lainnya:
PVS-Studio:
V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'kotak kombo'. ComboBox.cs 6610
public void ValidateOwnerDrawRegions(ComboBox comboBox, ....) { .... if (comboBox != null) { return; } Rectangle topOwnerDrawArea = new Rectangle(0, 0, comboBox.Width, innerBorder.Top); .... }
Kode paradoks. Rupanya, mereka mencampuradukkan tes dengan menulis
jika (comboBox! = Null) bukannya
if (comboBox == null) . Maka - kami akan menerima
NullReferenceException berikutnya.
Kami memeriksa dua kesalahan yang cukup jelas,
V3080 , di mana Anda dapat secara visual melacak situasi kemungkinan penggunaan referensi nol dalam metode ini. Tetapi diagnostik
V3080 jauh lebih cerdas dan dapat mencari kesalahan serupa untuk rangkaian panggilan metode. Belum lama ini, kami secara signifikan memperkuat mekanisme aliran data dan analisis antar-prosedur. Anda dapat membaca tentang ini di artikel "
Jenis Referensi Nullable dalam C # 8.0 dan Analisis Statis ". Dan di sini ada kesalahan serupa yang ditemukan di WinForms:
PVS-Studio:
V3080 Kemungkinan ada null dereference di dalam metode di 'reader.NameTable'. Pertimbangkan memeriksa argumen 1: contentReader. ResXResourceReader.cs 267
private void EnsureResData() { .... XmlTextReader contentReader = null; try { if (fileContents != null) { contentReader = new XmlTextReader(....); } else if (reader != null) { contentReader = new XmlTextReader(....); } else if (fileName != null || stream != null) { .... contentReader = new XmlTextReader(....); } SetupNameTable(contentReader);
Lihat apa yang terjadi dengan
contentReader variabel di tubuh metode. Setelah inisialisasi dengan referensi nol, sebagai hasil dari salah satu pemeriksaan, tautan akan diinisialisasi. Namun, serangkaian cek tidak berakhir dengan blok
lain . Ini berarti bahwa dalam beberapa kasus yang jarang terjadi (atau karena refactoring di masa depan), tautan masih dapat tetap nol. Selanjutnya, itu akan diteruskan ke metode
SetupNameTable , di mana ia digunakan tanpa verifikasi:
private void SetupNameTable(XmlReader reader) { reader.NameTable.Add(ResXResourceWriter.TypeStr); reader.NameTable.Add(ResXResourceWriter.NameStr); .... }
Ini berpotensi kode tidak aman.
Dan satu kesalahan lagi, di mana penganalisa harus melalui serangkaian panggilan untuk mengidentifikasi masalah:
PVS-Studio:
V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'tata letak'. DockAndAnchorLayout.cs 156
private static Rectangle GetAnchorDestination( IArrangedElement element, Rectangle displayRect, bool measureOnly) { .... AnchorInfo layout = GetAnchorInfo(element); int left = layout.Left + displayRect.X; .... }
Penganalisa mengklaim bahwa adalah mungkin untuk mendapatkan referensi nol dari metode
GetAnchorInfo , yang akan menghasilkan pengecualian saat menghitung nilai
kiri . Mari kita telusuri seluruh rantai panggilan dan periksa apakah ini benar:
private static AnchorInfo GetAnchorInfo(IArrangedElement element) { return (AnchorInfo)element.Properties.GetObject(s_layoutInfoProperty); } public object GetObject(int key) => GetObject(key, out _); public object GetObject(int key, out bool found) { short keyIndex = SplitKey(key, out short element); if (!LocateObjectEntry(keyIndex, out int index)) { found = false; return null; }
Memang, dalam beberapa kasus, metode
GetObject yang menutup rantai panggilan akan kembali
nol , yang tanpa pemeriksaan tambahan akan diteruskan ke metode panggilan. Mungkin, metode
GetAnchorDestination harus menyediakan situasi seperti itu.
Dalam kode WinForms ada beberapa kesalahan seperti itu,
lebih dari 70 . Mereka semua serupa dan saya tidak akan memberikan deskripsi mereka di artikel.
PVS-Studio:
V3091 Analisis empiris. Mungkin salah ketik ada di dalam string literal: "ShowCheckMargin". Kata 'ShowCheckMargin' mencurigakan. PropertyNames.cs 136
internal class PropertyNames { .... public static readonly string ShowImageMargin = "ShowCheckMargin"; ... public static readonly string ShowCheckMargin = "ShowCheckMargin"; .... }
Contoh kesalahan yang baik yang tidak mudah dideteksi. Saat menginisialisasi bidang kelas, mereka menggunakan nilai yang sama, meskipun pembuat kode jelas tidak berpikir begitu (salin-tempel yang harus disalahkan). Penganalisa membuat kesimpulan ini dengan membandingkan nama-nama variabel dan nilai-nilai string yang ditugaskan. Saya hanya memberi baris kesalahan, tetapi lihat tampilannya di editor kode:
Ini adalah deteksi kesalahan seperti itu yang menunjukkan kekuatan dan perawatan alat analisis statis tanpa batas.
PVS-Studio:
V3095 Objek 'currentForm' digunakan sebelum diverifikasi terhadap nol. Periksa baris: 3386, 3404. Application.cs 3386
private void RunMessageLoopInner(int reason, ApplicationContext context) { .... hwndOwner = new HandleRef( null, UnsafeNativeMethods.GetWindowLong( new HandleRef(currentForm, currentForm.Handle),
Klasik Variabel
currentForm digunakan tanpa pemeriksaan. Tetapi lebih lanjut dalam kode ada pemeriksaan untuk kesetaraan
nol . Dalam hal ini, saya dapat menyarankan Anda untuk lebih berhati-hati ketika bekerja dengan jenis referensi, serta menggunakan analisis statis :).
Kesalahan serupa lainnya:
PVS-Studio:
V3095 Objek 'backgroundBrush' digunakan sebelum diverifikasi terhadap nol. Periksa baris: 2331, 2334. DataGrid.cs 2331
public Color BackgroundColor { .... set { .... if (!value.Equals(backgroundBrush.Color))
Dalam kode WinForms, saya telah menemukan lebih
dari 60 kesalahan seperti itu. Menurut saya, semuanya cukup kritis dan membutuhkan perhatian pengembang. Tetapi dalam artikel itu tidak begitu menarik untuk membicarakannya, jadi saya akan membatasi diri pada keduanya yang dibahas di atas.
PVS-Studio:
V3125 Objek '_propInfo' digunakan dan diverifikasi terhadap null di cabang eksekusi yang berbeda. Periksa baris: 996, 982. Binding.cs 996
private void SetPropValue(object value) { .... if (....) { if .... else if (_propInfo != null) .... } else { _propInfo.SetValue(_control, value); } .... }
Untuk melengkapi gambar - juga semacam klasik, bug
V3125 . Situasi sebaliknya. Pada awalnya, referensi yang berpotensi nol digunakan dengan aman, memeriksa
nol , tetapi kemudian mereka tidak melakukan ini lagi dalam kode.
Dan kesalahan serupa lainnya:
PVS-Studio:
V3125 Objek 'pemilik' digunakan setelah diverifikasi terhadap nol. Periksa baris: 64, 60. FlatButtonAppearance.cs 64
public int BorderSize { .... set { .... if (owner != null && owner.ParentInternal != null) { LayoutTransaction.DoLayoutIf(....); } owner.Invalidate();
Kecantikan Tapi ini dari sudut pandang peneliti luar. Memang, selain dua
V3125 ini , alat analisa menemukan lebih
dari 50 pola yang sama dalam kode WinForms. Pengembang harus bekerja.
Dan akhirnya - kesalahan yang agak menarik, menurut saya.
PVS-Studio:
V3137 Variabel 'hCurrentFont' ditugaskan tetapi tidak digunakan pada akhir fungsi. DeviceContext2.cs 241
sealed partial class DeviceContext : .... { WindowsFont selectedFont; .... internal void DisposeFont(bool disposing) { if (disposing) { DeviceContexts.RemoveDeviceContext(this); } if (selectedFont != null && selectedFont.Hfont != IntPtr.Zero) { IntPtr hCurrentFont = IntUnsafeNativeMethods.GetCurrentObject( new HandleRef(this, hDC), IntNativeMethods.OBJ_FONT); if (hCurrentFont == selectedFont.Hfont) {
Mari kita lihat apa yang disiagakan oleh penganalisa, dan mengapa fakta bahwa suatu variabel diberi nilai, tetapi tidak digunakan dalam metode di masa depan, dapat mengindikasikan masalah.
Kelas parsial dideklarasikan di file
DeviceContext2.cs . Metode
DisposeFont digunakan untuk membebaskan sumber daya setelah bekerja dengan grafik: konteks perangkat dan font. Untuk pemahaman yang lebih baik, saya telah menyediakan seluruh metode
DisposeFont . Perhatikan variabel lokal
hCurrentFont . Masalahnya adalah bahwa mendeklarasikan variabel ini dalam suatu metode menyembunyikan bidang kelas dengan nama yang sama. Saya menemukan dua metode kelas
DeviceContext yang menggunakan
bidang yang disebut
hCurrentFont :
public IntPtr SelectFont(WindowsFont font) { .... hCurrentFont = font.Hfont; .... } public void ResetFont() { .... hCurrentFont = hInitialFont; }
Lihatlah metode
ResetFont . Baris terakhir ada persis apa yang
dilakukan metode
DisposeFont di blok bersarang
jika (penganalisis menunjuk ke tempat ini). Dan bidang
hCurrentFont ini dengan nama yang sama
dideklarasikan di bagian lain dari kelas parsial dalam file
DeviceContext.cs :
sealed partial class DeviceContext : .... { .... IntPtr hInitialFont; .... IntPtr hCurrentFont;
Jadi, kesalahan yang jelas telah dibuat. Pertanyaan lain adalah kekritisannya. Sekarang, sebagai hasil dari metode
DisposeFont yang
berfungsi di bagian yang ditandai dengan komentar βpilih kembali font awalβ, bidang
hCurrentFont tidak akan diberi nilai awal. Saya pikir hanya penulis kode yang dapat memberikan putusan yang tepat.
KesimpulanJadi, kali ini saya harus "memarahi" MS sedikit. Di WinForms ada banyak kesalahan yang membutuhkan perhatian pengembang. Mungkin ini karena beberapa terburu-buru dengan yang MS sedang mengerjakan .NET Core 3 dan komponen, termasuk WinForms.
Menurut pendapat saya, kode WinForms masih "lembab", tapi saya harap situasinya akan segera berubah menjadi lebih baik.Alasan kedua untuk sejumlah besar kesalahan mungkin karena alat analisa kami menjadi lebih baik untuk mencarinya :).Ngomong-ngomong, artikel oleh rekan saya Sergey Vasiliev akan segera diterbitkan di mana dia mencari dan menemukan beberapa masalah dalam kode .NET Core libraries. Saya berharap karyanya juga akan berkontribusi untuk meningkatkan kinerja platform .NET, karena kami selalu mencoba untuk mengomunikasikan hasil analisis proyek mereka kepada pengembang.Nah, bagi mereka yang ingin meningkatkan produk mereka sendiri atau melakukan penelitian untuk menemukan kesalahan dalam proyek orang lain, saya sarankan mengunduh dan mencoba PVS-Studio.Semua kode bersih!
Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Sergey Khrenov. WinForms: Kesalahan, Holmes