
Bukan rahasia lagi bahwa Microsoft telah bekerja pada versi ke-8 bahasa C # selama beberapa waktu. Versi bahasa baru (C # 8.0) sudah tersedia dalam rilis Visual Studio 2019 baru-baru ini, tetapi masih dalam versi beta. Versi baru ini akan menerapkan beberapa fitur dengan cara yang agak tidak jelas, atau agak tak terduga. Jenis Referensi Nullable adalah salah satunya. Fitur ini diumumkan sebagai sarana untuk melawan Pengecualian Referensi Null (NRE).
Sangat bagus melihat bahasa berkembang dan mendapatkan fitur baru untuk membantu pengembang. Secara kebetulan, beberapa waktu lalu, kami secara signifikan meningkatkan kemampuan penganalisis C # PVS-Studio untuk mendeteksi NRE. Dan sekarang kita bertanya-tanya apakah analisa statis pada umumnya dan PVS-Studio pada khususnya masih harus repot-repot untuk mendiagnosis potensi referensi kosong karena, setidaknya dalam kode baru yang akan menggunakan Referensi Nullable, referensi semacam itu akan menjadi "mustahil"? Mari kita coba membersihkannya.
Pro dan kontra dari fitur baru
Satu pengingat sebelum kita melanjutkan: versi beta terbaru dari C # 8.0, tersedia saat menulis posting ini, telah menonaktifkan jenis Referensi Nullable secara default, yaitu perilaku tipe referensi tidak berubah.
Jadi, apa jenis referensi nullable yang sebenarnya ada di C # 8.0 jika kita mengaktifkan opsi ini? Mereka pada dasarnya adalah tipe referensi lama yang sama baik kecuali bahwa sekarang Anda harus menambahkan '?' setelah nama tipe (misalnya,
string? ), mirip dengan
Nullable <T> , mis. jenis nilai nullable (misalnya,
int? ). Tanpa '?', Tipe
string kami sekarang akan ditafsirkan sebagai referensi yang tidak dapat
dibatalkan , yaitu jenis referensi yang tidak dapat ditetapkan
nol .
Null Reference Exception adalah salah satu pengecualian yang paling menjengkelkan untuk masuk ke program Anda karena tidak banyak berbicara tentang sumbernya, terutama jika metode melempar berisi sejumlah operasi dereferensi secara berurutan. Kemampuan untuk melarang penetapan nol ke variabel tipe referensi terlihat keren, tetapi bagaimana dengan kasus-kasus di mana melewatkan
null ke suatu metode memiliki beberapa logika eksekusi tergantung padanya? Alih-alih
nol , kita bisa, tentu saja, menggunakan nilai literal, konstan, atau hanya "tidak mungkin" yang secara logis tidak dapat ditugaskan ke variabel di tempat lain. Tapi ini menimbulkan risiko mengganti crash program dengan "silent", tetapi eksekusi salah, yang seringkali lebih buruk daripada menghadapi kesalahan segera.
Bagaimana kalau melempar pengecualian? Pengecualian yang berarti yang dilemparkan ke lokasi di mana terjadi kesalahan selalu lebih baik daripada
NRE di suatu tempat di atas atau di bawah tumpukan. Tapi itu hanya baik dalam proyek Anda sendiri, di mana Anda dapat memperbaiki konsumen dengan memasukkan blok
uji coba dan itu semata-mata tanggung jawab Anda. Saat mengembangkan pustaka menggunakan Referensi (non) Nullable, kami perlu menjamin bahwa metode tertentu selalu mengembalikan nilai. Bagaimanapun, itu tidak selalu mungkin (atau paling tidak mudah) bahkan dalam kode Anda sendiri untuk mengganti kembalinya
nol dengan melempar pengecualian (karena dapat mempengaruhi terlalu banyak kode).
Referensi Nullable dapat diaktifkan baik di tingkat proyek global dengan menambahkan properti
NullableContextOptions dengan nilai
diaktifkan, atau pada tingkat file dengan menggunakan arahan preprosesor:
#nullable enable string cantBeNull = string.Empty; string? canBeNull = null; cantBeNull = canBeNull!;
Fitur Referensi Nullable akan membuat tipe lebih informatif. Tanda tangan metode memberi Anda petunjuk tentang perilakunya: apakah cek nol atau tidak, apakah bisa mengembalikan
nol atau tidak. Sekarang, ketika Anda mencoba menggunakan variabel referensi nullable tanpa memeriksanya, kompiler akan mengeluarkan peringatan.
Ini cukup nyaman saat menggunakan pustaka pihak ketiga, tetapi juga menambah risiko menyesatkan pengguna pustaka, karena masih mungkin untuk melewati
null menggunakan operator null-forgiving baru (!). Artinya, menambahkan hanya satu tanda seru dapat mematahkan semua asumsi lebih lanjut tentang antarmuka menggunakan variabel seperti:
#nullable enable String GetStr() { return _count > 0 ? _str : null!; } String str = GetStr(); var len = str.Length;
Ya, Anda dapat berargumen bahwa ini adalah pemrograman yang buruk dan tidak ada yang akan menulis kode seperti itu secara nyata, tetapi selama ini berpotensi dilakukan, Anda tidak dapat merasa aman hanya mengandalkan kontrak yang diberlakukan oleh antarmuka metode yang diberikan ( mengatakan bahwa itu tidak dapat mengembalikan
nol ).
Omong-omong, Anda bisa menulis kode yang sama menggunakan beberapa
! operator, seperti C # sekarang memungkinkan Anda untuk melakukannya (dan kode tersebut dapat dikompilasi dengan sempurna):
cantBeNull = canBeNull!!!!!!!;
Dengan menulis seperti ini, kami dapat menekankan ide, โlihat, ini mungkin
nol !!!โ (kami di tim kami, kami menyebutnya pemrograman "emosional"). Bahkan, ketika membangun pohon sintaks, kompiler (dari Roslyn) menafsirkan
! Operator dengan cara yang sama seperti menafsirkan tanda kurung biasa, yang berarti Anda dapat menulis sebanyak mungkin
! Terserah Anda - seperti halnya dengan tanda kurung. Tetapi jika Anda cukup menulisnya, Anda dapat "merobohkan" kompiler. Mungkin ini akan diperbaiki di rilis final C # 8.0.
Demikian pula, Anda dapat menghindari peringatan kompiler saat mengakses variabel referensi yang tidak dapat dibatalkan tanpa centang:
canBeNull!.ToString();
Mari kita tambahkan lebih banyak emosi:
canBeNull!!!?.ToString();
Anda tidak akan pernah melihat sintaks seperti itu dalam kode nyata. Dengan menulis operator
pengampunan nol, kami memberi tahu kompiler, "Kode ini baik-baik saja, periksa tidak diperlukan." Dengan menambahkan operator Elvis, kami memberi tahu, โAtau mungkin tidak; mari kita periksa untuk berjaga-jaga. "
Sekarang, Anda dapat dengan wajar bertanya mengapa Anda masih dapat menetapkan
nol untuk variabel tipe referensi yang tidak dapat
dibatalkan dengan mudah jika konsep tipe ini menyiratkan bahwa variabel tersebut tidak dapat memiliki nilai
nol ? Jawabannya adalah bahwa "di bawah tenda", pada tingkat kode IL, tipe referensi tidak dapat dibatalkan masih ... jenis referensi "biasa" yang lama dan bagus, dan seluruh sintaks nullabilitas sebenarnya hanyalah anotasi untuk built-in kompiler analyzer (yang, kami percaya, tidak cukup nyaman untuk digunakan, tapi saya akan menguraikannya nanti). Secara pribadi, kami tidak menganggapnya sebagai solusi "rapi" untuk memasukkan sintaks baru hanya sebagai anotasi untuk alat pihak ketiga (bahkan dibangun ke dalam kompiler) karena fakta bahwa ini hanya anotasi mungkin tidak jelas sama sekali untuk programmer, karena sintaks ini sangat mirip dengan sintaks untuk struct nullable namun bekerja dengan cara yang sama sekali berbeda.
Kembali ke cara lain untuk memecahkan jenis Referensi Nullable. Pada saat penulisan artikel ini, ketika Anda memiliki solusi yang terdiri dari beberapa proyek, melewati variabel tipe referensi, katakanlah,
String dari metode yang dideklarasikan dalam satu proyek ke metode di proyek lain yang memiliki
NullableContext kompiler menganggap itu berurusan dengan String yang tidak dapat dibatalkan dan kompiler akan tetap diam. Dan itu terlepas dari banyak atribut
[Nullable (1)] yang ditambahkan ke setiap bidang dan metode dalam kode IL saat mengaktifkan Referensi Nullable
. Atribut ini, omong-omong, harus diperhitungkan jika Anda menggunakan refleksi untuk menangani atribut dan menganggap bahwa kode hanya berisi yang Anda kustom.
Situasi seperti itu dapat menyebabkan masalah tambahan saat mengadaptasi basis kode besar dengan gaya Referensi Nullable. Proses ini kemungkinan akan berjalan untuk sementara waktu, proyek demi proyek. Jika Anda berhati-hati, tentu saja, Anda dapat secara bertahap mengintegrasikan fitur baru, tetapi jika Anda sudah memiliki proyek yang berfungsi, setiap perubahan itu berbahaya dan tidak diinginkan (jika berfungsi, jangan menyentuhnya!). Itu sebabnya kami memastikan bahwa Anda tidak perlu mengubah kode sumber Anda atau menandainya untuk mendeteksi potensi
NRE saat menggunakan penganalisa PVS-Studio. Untuk memeriksa lokasi yang bisa melempar
NullReferenceException, cukup jalankan alat analisa dan cari peringatan V3080. Tidak perlu mengubah properti proyek atau kode sumber. Tidak perlu menambahkan arahan, atribut, atau operator. Tidak perlu mengubah kode lawas.
Ketika menambahkan dukungan Referensi Nullable ke PVS-Studio, kami harus memutuskan apakah penganalisa harus mengasumsikan bahwa variabel tipe referensi yang tidak dapat dibatalkan selalu memiliki nilai yang bukan nol. Setelah menyelidiki cara jaminan ini dapat dilanggar, kami memutuskan bahwa PVS-Studio tidak boleh membuat asumsi seperti itu. Bagaimanapun, bahkan jika suatu proyek menggunakan tipe referensi yang tidak dapat dibatalkan sepanjang jalan, penganalisa dapat menambah fitur ini dengan mendeteksi situasi spesifik di mana variabel tersebut dapat memiliki nilai
nol .
Bagaimana PVS-Studio mencari Pengecualian Referensi Null
Mekanisme aliran data dalam C # analyzer PVS-Studio melacak kemungkinan nilai variabel selama proses analisis. Ini juga termasuk analisis antar-prosedur, yaitu melacak kemungkinan nilai yang dikembalikan oleh suatu metode dan metode yang disarangkan, dan seterusnya. Selain itu, PVS-Studio mengingat variabel yang dapat diberi nilai
nol . Setiap kali ia melihat variabel seperti itu dereferensi tanpa cek, apakah itu dalam kode saat ini dalam analisis, atau di dalam metode yang dipanggil dalam kode ini, ia akan mengeluarkan peringatan V3080 tentang potensi Null Reference Exception.
Gagasan di balik diagnostik ini adalah membuat penganalisa marah hanya ketika melihat tugas
nol . Ini adalah perbedaan utama dari perilaku diagnostik kami dari perilaku penganalisa bawaan kompiler yang menangani jenis Referensi Nullable. Alat analisis bawaan akan menunjuk pada setiap dan setiap dereferensi variabel referensi yang tidak dapat dicentang - mengingat bahwa itu belum disesatkan oleh penggunaan
! operator atau bahkan hanya pemeriksaan rumit (perlu dicatat, bahwa benar-benar ada penganalisa statis, PVS-Studio tidak terkecuali di sini, dapat "disesatkan" dengan satu atau lain cara, terutama jika Anda bermaksud melakukannya).
PVS-Studio, di sisi lain, memperingatkan Anda hanya jika ia melihat
nol (apakah dalam konteks lokal atau konteks metode luar). Bahkan jika variabel adalah tipe referensi yang tidak dapat dibatalkan, penganalisis akan terus menunjuk padanya jika ia melihat penugasan
nol untuk variabel itu. Pendekatan ini, kami percaya, lebih tepat (atau setidaknya lebih nyaman bagi pengguna) karena tidak menuntut "mengolesi" seluruh kode dengan pemeriksaan nol untuk melacak referensi potensial - setelah semua, opsi ini tersedia bahkan sebelum Nullable Reference diperkenalkan, misalnya, melalui penggunaan kontrak. Terlebih lagi, penganalisa sekarang dapat memberikan kontrol yang lebih baik atas variabel referensi yang tidak dapat dibatalkan sendiri. Jika variabel seperti itu digunakan "cukup" dan tidak pernah ditugaskan
nol , PVS-Studio tidak akan mengatakan apa-apa. Jika variabel ditetapkan
nol dan kemudian direferensikan tanpa pemeriksaan sebelumnya, PVS-Studio akan mengeluarkan peringatan V3080:
#nullable enable String GetStr() { return _count > 0 ? _str : null!; } String str = GetStr(); var len = str.Length; <== V3080: Possible null dereference. Consider inspecting 'str'
Sekarang mari kita lihat beberapa contoh yang menunjukkan bagaimana diagnostik ini dipicu oleh kode Roslyn itu sendiri. Kami sudah
memeriksa proyek ini baru-baru ini, tetapi kali ini kami hanya akan melihat potensi Pengecualian Referensi Null yang tidak disebutkan dalam artikel sebelumnya. Kita akan melihat bagaimana PVS-Studio mendeteksi potensi NRE dan bagaimana mereka dapat diperbaiki menggunakan sintaks Referensi Nullable yang baru.
V3080 [CWE-476] Kemungkinan null dereference di dalam metode. Pertimbangkan memeriksa argumen 2: chainedTupleType. Microsoft.CodeAnalysis.CSharp TupleTypeSymbol.cs 244 NamedTypeSymbol chainedTupleType; if (_underlyingType.Arity < TupleTypeSymbol.RestPosition) { .... chainedTupleType = null; } else { .... } return Create(ConstructTupleUnderlyingType(firstTupleType, chainedTupleType, newElementTypes), elementNames: _elementNames);
Seperti yang Anda lihat, variabel
chainedTupleType dapat diberi nilai
nol di salah satu cabang eksekusi. Ini kemudian diteruskan ke metode
ConstructTupleUnderlyingType dan digunakan di sana setelah pemeriksaan
Debug.Assert . Ini adalah pola yang sangat umum di Roslyn, tetapi perlu diingat bahwa
Debug.Assert dihapus dalam versi rilis. Itu sebabnya penganalisa masih menganggap dereferensi di dalam metode
ConstructTupleUnderlyingType berbahaya. Inilah inti dari metode itu, di mana dereference berlangsung:
internal static NamedTypeSymbol ConstructTupleUnderlyingType( NamedTypeSymbol firstTupleType, NamedTypeSymbol chainedTupleTypeOpt, ImmutableArray<TypeWithAnnotations> elementTypes) { Debug.Assert (chainedTupleTypeOpt is null == elementTypes.Length < RestPosition); .... while (loop > 0) { .... currentSymbol = chainedTupleTypeOpt.Construct(chainedTypes); loop--; } return currentSymbol; }
Ini sebenarnya masalah perselisihan apakah penganalisis harus mempertimbangkan Asserts seperti itu (beberapa pengguna kami ingin melakukannya) - setelah semua, penganalisa memang mengambil kontrak dari System.Diagnostics.Contracts ke akun. Berikut adalah satu contoh kecil kehidupan nyata dari pengalaman kami menggunakan Roslyn di penganalisa kami sendiri. Sambil
menambahkan dukungan dari Visual Studio versi terbaru baru- baru ini, kami juga memperbarui Roslyn ke versi ke-3. Setelah itu, PVS-Studio mulai crash pada kode tertentu yang belum pernah crash sebelumnya. Kecelakaan, disertai dengan Pengecualian Referensi Null, akan terjadi bukan dalam kode kami tetapi dalam kode Roslyn. Debugging mengungkapkan bahwa fragmen kode di mana Roslyn sekarang crash memiliki jenis
Debug yang sangat baik.
Assert berbasis nol memeriksa beberapa baris lebih tinggi - dan pemeriksaan itu jelas tidak membantu.
Ini adalah contoh grafis tentang bagaimana Anda bisa mendapatkan masalah dengan Referen Nullable karena kompiler memperlakukan
Debug. Masukkan sebagai cek yang dapat diandalkan dalam konfigurasi apa pun. Yaitu, jika Anda menambahkan
#nullable aktifkan dan tandai argumen
chainedTupleTypeOpt sebagai referensi yang dapat
dibatalkan , kompiler tidak akan mengeluarkan peringatan apa pun pada dereferensi di dalam metode
ConstructTupleUnderlyingType .
Pindah ke contoh peringatan lainnya oleh PVS-Studio.
V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'efektifRuleset'. RuleSet.cs 146 var effectiveRuleset = ruleSet.GetEffectiveRuleSet(includedRulesetPaths); effectiveRuleset = effectiveRuleset.WithEffectiveAction(ruleSetInclude.Action); if (IsStricterThan(effectiveRuleset.GeneralDiagnosticOption, ....)) effectiveGeneralOption = effectiveRuleset.GeneralDiagnosticOption;
Peringatan ini mengatakan bahwa panggilan metode
WithEffectiveAction dapat mengembalikan
nol , sedangkan nilai kembali yang ditetapkan ke variabel
efektifRuleset tidak diperiksa sebelum digunakan (
efektifRuleset.GeneralDiagnosticOption ). Inilah tubuh metode
WithEffectiveAction :
public RuleSet WithEffectiveAction(ReportDiagnostic action) { if (!_includes.IsEmpty) throw new ArgumentException(....); switch (action) { case ReportDiagnostic.Default: return this; case ReportDiagnostic.Suppress: return null; .... return new RuleSet(....); default: return null; } }
Dengan Referensi Nullable diaktifkan untuk metode
GetEffectiveRuleSet , kita akan mendapatkan dua lokasi di mana perilaku kode harus diubah. Karena metode yang ditunjukkan di atas dapat melempar pengecualian, masuk akal untuk menganggap bahwa panggilan itu dibungkus dalam blok
try-catch dan akan benar untuk menulis ulang metode untuk melempar pengecualian daripada mengembalikan
nol . Namun, jika Anda melacak beberapa panggilan kembali, Anda akan melihat bahwa kode penangkapan terlalu jauh untuk memprediksi konsekuensinya. Mari kita lihat konsumen dari variabel
efektifRuleset, metode
IsStricterThan :
private static bool IsStricterThan(ReportDiagnostic action1, ReportDiagnostic action2) { switch (action2) { case ReportDiagnostic.Suppress: ....; case ReportDiagnostic.Warn: return action1 == ReportDiagnostic.Error; case ReportDiagnostic.Error: return false; default: return false; } }
Seperti yang Anda lihat, ini adalah pernyataan beralih sederhana yang memilih antara dua enumerasi, dengan
ReportDiagnostic.Default sebagai nilai default. Jadi akan lebih baik untuk menulis ulang panggilan sebagai berikut:
Tanda tangan
WithEffectiveAction akan berubah:
#nullable enable public RuleSet? WithEffectiveAction(ReportDiagnostic action)
Ini akan menjadi seperti apa panggilan itu:
RuleSet? effectiveRuleset = ruleSet.GetEffectiveRuleSet(includedRulesetPaths); effectiveRuleset = effectiveRuleset?.WithEffectiveAction(ruleSetInclude.Action); if (IsStricterThan(effectiveRuleset?.GeneralDiagnosticOption ?? ReportDiagnostic.Default, effectiveGeneralOption)) effectiveGeneralOption = effectiveRuleset.GeneralDiagnosticOption;
Karena
IsStricterThan hanya melakukan perbandingan, kondisinya dapat ditulis ulang - misalnya, seperti ini:
if (effectiveRuleset == null || IsStricterThan(effectiveRuleset.GeneralDiagnosticOption, effectiveGeneralOption))
Contoh selanjutnya.
V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'propertySymbol'. BinderFactory.BinderFactoryVisitor.cs 372 var propertySymbol = GetPropertySymbol(parent, resultBinder); var accessor = propertySymbol.GetMethod; if ((object)accessor != null) resultBinder = new InMethodBinder(accessor, resultBinder);
Untuk memperbaiki peringatan ini, kita perlu melihat apa yang terjadi pada variabel
propertySymbol selanjutnya.
private SourcePropertySymbol GetPropertySymbol( BasePropertyDeclarationSyntax basePropertyDeclarationSyntax, Binder outerBinder) { .... NamedTypeSymbol container = GetContainerType(outerBinder, basePropertyDeclarationSyntax); if ((object)container == null) return null; .... return (SourcePropertySymbol)GetMemberSymbol(propertyName, basePropertyDeclarationSyntax.Span, container, SymbolKind.Property); }
Metode
GetMemberSymbol juga dapat mengembalikan
nol dalam kondisi tertentu.
private Symbol GetMemberSymbol( string memberName, TextSpan memberSpan, NamedTypeSymbol container, SymbolKind kind) { foreach (Symbol sym in container.GetMembers(memberName)) { if (sym.Kind != kind) continue; if (sym.Kind == SymbolKind.Method) { .... var implementation = ((MethodSymbol)sym).PartialImplementationPart; if ((object)implementation != null) if (InSpan(implementation.Locations[0], this.syntaxTree, memberSpan)) return implementation; } else if (InSpan(sym.Locations, this.syntaxTree, memberSpan)) return sym; } return null; }
Jika jenis referensi nullable diaktifkan, panggilan akan berubah menjadi ini:
#nullable enable SourcePropertySymbol? propertySymbol = GetPropertySymbol(parent, resultBinder); MethodSymbol? accessor = propertySymbol?.GetMethod; if ((object)accessor != null) resultBinder = new InMethodBinder(accessor, resultBinder);
Sangat mudah untuk memperbaikinya ketika Anda tahu di mana mencarinya. Analisis statis dapat menangkap potensi kesalahan ini tanpa usaha dengan mengumpulkan semua kemungkinan nilai lapangan dari semua rantai panggilan prosedur.
V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'simpleName'. CSharpCommandLineParser.cs 1556 string simpleName; simpleName = PathUtilities.RemoveExtension( PathUtilities.GetFileName(sourceFiles.FirstOrDefault().Path)); outputFileName = simpleName + outputKind.GetDefaultExtension(); if (simpleName.Length == 0 && !outputKind.IsNetModule()) ....
Masalahnya adalah sejalan dengan pemeriksaan
simpleName.Length . Variabel
simpleName hasil dari mengeksekusi serangkaian metode yang panjang dan dapat ditugaskan
nol . Ngomong-ngomong, jika Anda penasaran, Anda bisa melihat metode
RemoveExtension untuk melihat perbedaannya dari
Path.GetFileNameWithoutExtension. SimpleName! = Pemeriksaan
kosong akan cukup, tetapi dengan jenis referensi yang tidak dapat dibatalkan, kode akan berubah menjadi seperti ini:
#nullable enable public static string? RemoveExtension(string path) { .... } string simpleName;
Seperti inilah bentuk panggilannya:
simpleName = PathUtilities.RemoveExtension( PathUtilities.GetFileName(sourceFiles.FirstOrDefault().Path)) ?? String.Empty;
Kesimpulan
Jenis referensi Nullable bisa sangat membantu ketika mendesain arsitektur dari awal, tetapi pengerjaan ulang kode yang ada mungkin membutuhkan banyak waktu dan perawatan, karena dapat menyebabkan sejumlah bug yang sulit dipahami. Artikel ini tidak bertujuan untuk mencegah Anda menggunakan jenis Referensi Nullable. Kami menemukan fitur baru ini secara umum berguna walaupun cara penerapannya mungkin kontroversial.
Namun, selalu ingat tentang keterbatasan pendekatan ini dan perlu diingat bahwa mengaktifkan mode Referensi Nullable tidak melindungi Anda dari NRE dan bahwa, ketika disalahgunakan, itu sendiri dapat menjadi sumber kesalahan ini. Kami menyarankan Anda melengkapi fitur Referensi Nullable dengan alat analisis statis modern, seperti PVS-Studio, yang mendukung analisis antar-prosedur untuk melindungi program Anda dari NRE. Masing-masing pendekatan ini - analisis antar-prosedur yang mendalam dan tanda tangan metode anotasi (yang sebenarnya apa yang dilakukan mode Referensi Nullable) - memiliki pro dan kontra. Penganalisa akan memberi Anda daftar lokasi yang berpotensi berbahaya dan membiarkan Anda melihat konsekuensi dari memodifikasi kode yang ada. Jika ada penugasan nol di suatu tempat, penganalisa akan menunjuk pada setiap konsumen dari variabel di mana dereferensi tanpa cek.
Anda dapat memeriksa proyek ini atau proyek Anda sendiri untuk cacat lainnya - cukup
unduh PVS-Studio dan cobalah.