
Ketika kami memutuskan untuk mencari kesalahan di Azure SDK untuk proyek .NET, kami sangat terkejut dengan ukurannya. "Tiga setengah juta baris kode," kami terus berkata, mempelajari statistik proyek. Mungkin ada begitu banyak temuan. Aduh dan mundur! Proyek itu ternyata licik. Jadi apa semangat proyek dan bagaimana diperiksa - baca di artikel ini.
Tentang proyek
Saya menulis artikel ini menindaklanjuti yang sebelumnya, yang juga tentang proyek yang berkaitan dengan Microsoft Azure:
Azure PowerShell: Kebanyakan Tidak Berbahaya . Jadi, kali ini saya bertaruh pada sejumlah kesalahan yang beragam dan menarik. Bagaimanapun, ukuran proyek adalah faktor yang sangat penting dalam hal analisis statis, khususnya ketika memeriksa proyek untuk pertama kalinya. Bahkan, dalam praktiknya, aplikasi pemeriksaan satu kali bukan pendekatan yang tepat. Namun demikian, jika pengembang mendukungnya, itu hanya terjadi pada tahap pengantar analisa. Pada saat yang sama, tidak ada seorang pun yang berhasil memilah-milah sejumlah besar peringatan dan hanya menghapusnya sebagai hutang teknis menggunakan mekanisme penekan peringatan massal dan menyimpannya di pangkalan-pangkalan khusus. Omong-omong, memiliki sejumlah besar peringatan baik-baik saja ketika menjalankan analisa untuk pertama kalinya. Adapun kami, kami pergi untuk pemeriksaan satu kali untuk tujuan penelitian. Untuk alasan ini, proyek besar selalu lebih disukai untuk analisis berikut dibandingkan dengan yang kecil.
Namun, Azure SDK untuk proyek .NET segera terbukti menjadi test bed yang tidak dapat dijalankan. Bahkan ukurannya yang mengesankan tidak membantu, tetapi agak rumit mengerjakannya. Alasannya diberikan dalam statistik proyek berikut:
- File sumber .cs (tidak termasuk tes): 16.500
- Solusi Visual Studio (.sln): 163
- Baris kode yang tidak kosong: 3 462 000
- Dari yang dihasilkan secara otomatis ini: sekitar 3.300.000
- Repositori proyek tersedia di GitHub .
Sekitar 95% kode dihasilkan secara otomatis, dan banyak dari kode itu diulang berkali-kali. Memeriksa proyek-proyek seperti itu dengan analisa statis biasanya memakan waktu dan tidak berguna, karena ada banyak yang bisa diterapkan, tetapi tidak logis (setidaknya pada pandangan pertama) dan kode berlebihan. Ini mengarah pada sejumlah besar kesalahan positif.
Semua jumlah kode yang tersebar di 163 solusi Visual Studio menjadi "cherry on top". Butuh beberapa upaya untuk memeriksa kode yang tersisa (tidak dihasilkan secara otomatis). Apa yang benar-benar membantu adalah kenyataan bahwa semua kode yang dibuat secara otomatis disimpan dalam subdirektori solusi oleh jalur relatif "<Direktori solusi> \ src \ Generated". Juga setiap file .cs dari jenis tersebut berisi komentar khusus dalam tag
<auto-generated> :
Untuk kemurnian percobaan, saya memeriksa sekitar sepuluh solusi yang dibuat secara acak secara acak. Saya akan ceritakan hasilnya nanti.
Jadi, meskipun jumlah kode βjujurβ yang tersisa sedikit, saya masih berhasil menemukan sejumlah kesalahan dari yang tersisa. Kali ini saya tidak akan mengutip peringatan dalam urutan kode diagnostik PVS-Studio. Sebagai gantinya, saya akan mengelompokkan pesan pada solusi yang mereka temukan.
Baiklah, mari kita lihat apa yang berhasil saya temukan di Azure SDK untuk kode .NET.
Microsoft.Azure.Management.Advisor
Ini adalah salah satu dari banyak solusi yang berisi kode yang dibuat secara otomatis. Seperti yang saya katakan sebelumnya, saya secara acak memeriksa sekitar selusin solusi semacam itu. Dalam setiap kasus, peringatan sama, dan, seperti yang diharapkan, tidak berguna. Berikut ini beberapa contohnya.
Ekspresi
V3022 'Kredensial! = Null' selalu benar. AdvisorManagementClient.cs 204
Jelas, kode ini berlebihan dan
Kredensial! = Pemeriksaan
kosong tidak ada gunanya. Namun demikian, kodenya berfungsi. Dan dihasilkan secara otomatis. Untuk alasan ini, tidak ada keluhan di sini.
Ekspresi
V3022 '_queryParameters.Count> 0' selalu salah. ConfigurationsOperations.cs 871
Sekali lagi, ini tampak seperti konstruksi yang tidak logis. Untuk beberapa alasan, pembuat kode memeriksa ukuran daftar
kosong yang baru dibuat. Padahal, itu semua benar. Pada titik ini, cek tidak masuk akal, tetapi jika pengembang menambahkan daftar generasi, misalnya, berdasarkan koleksi lain, cek pasti akan bernilai sementara. Lagi - tidak ada klaim kode, tentu saja, berkaitan dengan asalnya.
Ratusan peringatan serupa telah dikeluarkan untuk setiap solusi yang dibuat secara otomatis. Mengingat kesia-siaan mereka, saya pikir tidak ada gunanya membahas lebih lanjut kasus-kasus seperti itu. Selanjutnya, hanya kesalahan nyata dalam kode "normal" yang akan dipertimbangkan.
Azure. Skor
V3001 Ada sub-ekspresi identik 'buffer.Length' ke kiri dan ke kanan operator '<'. AzureBaseBuffersExtensions.cs 30
public static async Task WriteAsync(...., ReadOnlyMemory<byte> buffer, ....) { byte[]? array = null; .... if (array == null || buffer.Length < buffer.Length)
Kesalahan dalam kondisi itu mungkin hasil dari copy-paste. Menurut fakta bahwa
buffer disalin dalam
array , cek akan terlihat seperti:
if (array == null || array.Length < buffer.Length)
Lagi pula, seperti yang selalu saya katakan, pembuat kode harus berurusan dengan memperbaiki kesalahan tersebut.
V3083 Doa yang tidak aman dari acara '_onChange', NullReferenceException dimungkinkan. Pertimbangkan menugaskan acara ke variabel lokal sebelum menjalankannya. ClientOptionsMonitor.cs 44
private event Action<TOptions, string> _onChange; .... private void InvokeChanged(....) { .... if (_onChange != null) { _onChange.Invoke(options, name); } }
Tidak kritis, tetapi ada kesalahan di sini. Konsumen mungkin berhenti berlangganan dari acara antara memeriksa acara untuk
nol dan permintaannya. Kemudian variabel
_onChange akan menjadi
nol dan pengecualian akan dibuang. Kode ini harus ditulis ulang dengan cara yang lebih aman. Misalnya, sebagai berikut:
private void InvokeChanged(....) { .... _onChange?.Invoke(options, name); }
Azure.Messaging.EventHubs
V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'eventPropertyValue'. AmqpMessageConverter.cs 650
private static bool TryCreateEventPropertyForAmqpProperty( object amqpPropertyValue, out object eventPropertyValue) { eventPropertyValue = null; .... switch (GetTypeIdentifier(amqpPropertyValue)) { case AmqpProperty.Type.Byte: .... case AmqpProperty.Type.String: eventPropertyValue = amqpPropertyValue; return true; .... } .... switch (amqpPropertyValue) { case AmqpSymbol symbol: eventPropertyValue = ....; break; case byte[] array: eventPropertyValue = ....; break; case ArraySegment<byte> segment when segment.Count == segment.Array.Length: eventPropertyValue = ....; break; case ArraySegment<byte> segment: .... eventPropertyValue = ....; break; case DescribedType described when (described.Descriptor is AmqpSymbol): eventPropertyValue = ....; break; default: var exception = new SerializationException( string.Format(...., eventPropertyValue.GetType().FullName));
Mari kita lihat apa yang terjadi dengan nilai variabel
eventPropertyValue dalam fragmen kode yang diberikan. Variabel diberikan
nol pada awal metode. Selanjutnya, dalam salah satu kondisi
sakelar pertama, variabel diinisialisasi, setelah itu metode keluar. Blok
switch kedua berisi banyak kondisi, di mana masing-masing variabel juga menerima nilai baru. Sedangkan di blok
default , variabel
eventPropertyValue digunakan tanpa centang, yang merupakan kesalahan, karena variabelnya
nol pada saat ini.
V3066 Kemungkinan urutan argumen yang salah diteruskan ke konstruktor 'EventHubConsumer': 'partisiId' dan 'consumerGroup'. TrackOneEventHubClient.cs 394
public override EventHubConsumer CreateConsumer(....) { return new EventHubConsumer ( new TrackOneEventHubConsumer(....), TrackOneClient.EventHubName, partitionId,
Penganalisa curiga urutan argumen ketiga dan keempat saat memanggil konstruktor kelas
EventHubConsumer . Jadi mari kita periksa deklarasi konstruktor ini:
internal EventHubConsumer(TransportEventHubConsumer transportConsumer, string eventHubName, string consumerGroup, // <= 3 string partitionId, // <= 4 EventPosition eventPosition, EventHubConsumerOptions consumerOptions, EventHubRetryPolicy retryPolicy) { .... }
Memang, argumen tercampur aduk. Saya berani menyarankan bagaimana kesalahan itu dibuat. Mungkin, salah memformat kode adalah kesalahan di sini. Lihat lagi deklarasi konstruktor
EventHubConsumer . Karena fakta bahwa parameter
transportConsumer pertama berada di baris yang sama dengan nama kelas, mungkin tampak bahwa parameter partisiId berada di tempat ketiga, bukan yang keempat (komentar saya dengan nomor parameter tidak tersedia dalam kode asli) . Itu hanya dugaan, tetapi saya akan mengubah pemformatan kode konstruktor sebagai berikut:
internal EventHubConsumer ( TransportEventHubConsumer transportConsumer, string eventHubName, string consumerGroup, string partitionId, EventPosition eventPosition, EventHubConsumerOptions consumerOptions, EventHubRetryPolicy retryPolicy) { .... }
Azure. Penyimpanan
V3112 Kelainan dalam perbandingan serupa. Mungkin salah ketik ada di dalam ekspresi 'ContentLanguage == other.ContentEncoding'. BlobSasBuilder.cs 410
public struct BlobSasBuilder : IEquatable<BlobSasBuilder> { .... public bool Equals(BlobSasBuilder other) => BlobName == other.BlobName && CacheControl == other.CacheControl && BlobContainerName == other.BlobContainerName && ContentDisposition == other.ContentDisposition && ContentEncoding == other.ContentEncoding &&
Sebuah kesalahan yang dilakukan karena kurangnya perhatian. Menemukan kesalahan dengan ulasan kode cukup sulit. Ini versi kode yang benar:
.... ContentEncoding == other.ContentEncoding && ContentLanguage == other.ContentLanguage && ....
V3112 Kelainan dalam perbandingan serupa. Mungkin salah ketik ada di dalam ekspresi 'ContentLanguage == other.ContentEncoding'. FileSasBuilder.cs 265
public struct FileSasBuilder : IEquatable<FileSasBuilder> { .... public bool Equals(FileSasBuilder other) => CacheControl == other.CacheControl && ContentDisposition == other.ContentDisposition && ContentEncoding == other.ContentEncoding
Ada kesalahan yang persis sama dalam bagian kode yang sangat mirip. Kode mungkin telah disalin dan sebagian diubah. Namun kesalahan tetap ada.
Microsoft.Azure.Batch
V3053 Ekspresi yang berlebihan. Periksa substring 'IList' dan 'List'. PropertyData.cs 157
V3053 Ekspresi yang berlebihan. Periksa substring 'Daftar' dan 'IReadOnlyList'. PropertyData.cs 158
public class PropertyData { .... public bool IsTypeCollection => this.Type.Contains("IList") || this.Type.Contains("IEnumerable") || this.Type.Contains("List") ||
Penganalisa mengeluarkan dua peringatan tentang cek tidak berguna atau salah. Dalam kasus pertama, mencari substring "Daftar" setelah mencari "IList" tampak berlebihan. Memang benar, kondisi ini:
this.Type.Contains("IList") || this.Type.Contains("List")
dapat diubah dengan baik untuk yang berikut:
this.Type.Contains("List")
Dalam kasus kedua, pencarian untuk substring "IReadOnlyList" tidak ada gunanya, karena sebelumnya substring yang lebih pendek "List" dicari.
Ada juga kemungkinan bahwa substring pencarian sendiri memiliki kesalahan dan harus ada sesuatu yang lain. Bagaimanapun, hanya pembuat kode yang menyarankan versi kode yang benar dengan mempertimbangkan kedua komentar.
V3095 Objek 'httpRequest.Content.Headers' digunakan sebelum diverifikasi terhadap nol. Periksa baris: 76, 79. BatchSharedKeyCredential.cs 76
public override Task ProcessHttpRequestAsync( HttpRequestMessage httpRequest, ....) { .... signature.Append(httpRequest.Content != null && httpRequest.Content.Headers.Contains("Content-Language") ? .... : ....; long? contentLength = httpRequest.Content?.Headers?.ContentLength; .... }
Variabel
httpRequest.Content.Headers pertama kali digunakan tanpa pemeriksaan, tetapi kemudian diatasi menggunakan operator akses bersyarat.
V3125 Objek 'omPropertyData' digunakan setelah diverifikasi terhadap null. Periksa baris: 156, 148. CodeGenerationUtilities.cs 156
private static string GetProtocolCollectionToObjectModelCollectionString( ...., PropertyData omPropertyData, ....) { if (IsMappedEnumPair(omPropertyData?.GenericTypeParameter, ....)) { .... } if (IsTypeComplex(omPropertyData.GenericTypeParameter)) .... }
Dan ini adalah situasi sebaliknya. Satu blok kode berisi varian akses aman ke
omPropertyData yang berpotensi menjadi referensi nol. Lebih lanjut dalam kode, referensi ini ditangani tanpa pemeriksaan.
V3146 Kemungkinan nol dereferensi 'nilai'. 'FirstOrDefault' dapat mengembalikan nilai nol default. BatchSharedKeyCredential.cs 127
public override Task ProcessHttpRequestAsync(HttpRequestMessage httpRequest, ....) { .... foreach (string canonicalHeader in customHeaders) { string value = httpRequest.Headers. GetValues(canonicalHeader).FirstOrDefault(); value = value.Replace('\n', ' ').Replace('\r', ' ').TrimStart(); .... } .... }
Karena metode
FirstOrDefault , jika pencarian gagal, nilai default akan dikembalikan, yang merupakan
nol untuk tipe
string . Nilai akan ditetapkan ke variabel
nilai , yang kemudian digunakan dalam kode dengan metode
Ganti tanpa pemeriksaan apa pun. Kode harus dibuat lebih aman. Misalnya, sebagai berikut:
foreach (string canonicalHeader in customHeaders) { string value = httpRequest.Headers. GetValues(canonicalHeader).FirstOrDefault(); value = value?.Replace('\n', ' ').Replace('\r', ' ').TrimStart(); .... }
Microsoft.Azure.ServiceBus
V3121 Enumerasi 'BlocksUsing' dideklarasikan dengan atribut 'Flags', tetapi tidak mengatur inisialisasi apa pun untuk mengesampingkan nilai default. Fx.cs 69
static class Fx { .... public static class Tag { .... [Flags] public enum BlocksUsing { MonitorEnter, MonitorWait, ManualResetEvent, AutoResetEvent, AsyncResult, IAsyncResult, PInvoke, InputQueue, ThreadNeutralSemaphore, PrivatePrimitive, OtherInternalPrimitive, OtherFrameworkPrimitive, OtherInterop, Other, NonBlocking, } .... } .... }
Enumerasi dideklarasikan dengan atribut
Flags . Pada saat yang sama, nilai-nilai konstan dibiarkan secara default (
MonitorEnter = 0 ,
MonitorWait = 1 ,
ManualResetEvent = 2 dan seterusnya). Ini dapat menghasilkan kasus berikut: ketika mencoba menggunakan kombinasi bendera, misalnya, konstanta kedua dan ketiga
MonitorWait (= 1) |
ManualResetEvent (= 2) , bukan nilai unik yang akan diterima, tetapi konstan dengan nilai 3 secara default
(AutoResetEvent ). Ini mungkin mengejutkan bagi kode penelepon. Jika enumerasi
BlocksUsing benar-benar digunakan untuk mengatur kombinasi flags (bidang bit), konstanta harus diberi nilai, sama dengan angka yang merupakan kekuatan dua.
[Flags] public enum BlocksUsing { MonitorEnter = 1, MonitorWait = 2, ManualResetEvent = 4, AutoResetEvent = 8, AsyncResult = 16, IAsyncResult = 32, PInvoke = 64, InputQueue = 128, ThreadNeutralSemaphore = 256, PrivatePrimitive = 512, OtherInternalPrimitive = 1024, OtherFrameworkPrimitive = 2048, OtherInterop = 4096, Other = 8192, NonBlocking = 16384, }
V3125 Objek 'sesi' digunakan setelah diverifikasi terhadap nol. Periksa baris: 69, 68. AmqpLinkCreator.cs 69
public async Task<Tuple<AmqpObject, DateTime>> CreateAndOpenAmqpLinkAsync() { .... AmqpSession session = null; try {
Perhatikan penanganan variabel
sesi di blok
tangkap . Metode
Abort dipanggil dengan aman oleh operator akses bersyarat. Tetapi setelah metode
GetInnerException disebut tidak aman. Dengan melakukan hal itu,
NullReferenceException mungkin dilemparkan bukan pengecualian dari tipe yang diharapkan. Kode ini harus diperbaiki. Metode
AmqpExceptionHelper.GetClientException mendukung melewati nilai
nol untuk parameter
innerException :
public static Exception GetClientException( Exception exception, string referenceId = null, Exception innerException = null, bool connectionError = false) { .... }
Oleh karena itu, seseorang hanya dapat menggunakan operator akses bersyarat saat memanggil
sesi. DapatkanInnerException () :
public async Task<Tuple<AmqpObject, DateTime>> CreateAndOpenAmqpLinkAsync() { .... AmqpSession session = null; try {
Kesimpulan
Seperti yang Anda lihat, ukuran proyek besar tidak selalu menjamin banyak kesalahan. Namun, kami tetap waspada karena kami selalu dapat menemukan sesuatu. Bahkan dalam proyek yang kompleks secara struktural seperti Azure SDK untuk .NET. Menemukan beberapa cacat krusial membutuhkan upaya tambahan. Tetapi semakin banyak kesulitan semakin menyenangkan hasilnya. Di sisi lain, untuk menghindari upaya yang tidak semestinya, kami sarankan menggunakan analisis statis langsung di komputer pengembang saat menulis kode baru. Ini adalah pendekatan yang paling efektif.
Unduh dan coba PVS-Studio dalam aksi. Semoga berhasil dalam memerangi serangga!