Apache Dubbo adalah salah satu proyek Java paling populer di GitHub. Itu tidak mengejutkan. Itu dibuat 8 tahun yang lalu dan secara luas diterapkan sebagai lingkungan RPC berkinerja tinggi. Tentu saja, sebagian besar bug dalam kodenya telah lama diperbaiki dan kualitas kodenya dijaga pada tingkat tinggi. Namun, tidak ada alasan untuk memilih untuk tidak memeriksa proyek yang menarik menggunakan penganalisis kode statis PVS-Studio. Mari kita lihat bagaimana hasilnya.
Tentang PVS-Studio
Penganalisis kode statis
PVS-Studio telah ada selama lebih dari 10 tahun di pasar TI dan merupakan solusi perangkat lunak yang multifungsi dan mudah diperkenalkan. Saat ini, alat analisa mendukung C, C ++, C #, Java dan bekerja pada Windows, Linux dan macOS.
PVS-Studio adalah solusi B2B dan digunakan oleh sejumlah besar tim di berbagai perusahaan. Jika Anda ingin memperkirakan kemampuan penganalisa, Anda dapat mengunduh distribusi dan meminta kunci percobaan di
sini .
Juga ada
opsi bagaimana Anda dapat menggunakan PVS-Studio secara gratis di proyek sumber terbuka atau jika Anda seorang pelajar.
Apache Dubbo: Apa Artinya dan Fitur Utama
Saat ini, hampir semua sistem perangkat lunak besar
didistribusikan . Jika dalam sistem terdistribusi ada koneksi interaktif antara komponen jarak jauh dengan latensi rendah dan data yang relatif sedikit untuk dilewati, itu adalah alasan kuat untuk menggunakan lingkungan
RPC (panggilan prosedur jarak jauh).
Apache Dubbo adalah lingkungan
RPC (panggilan prosedur jarak jauh) berkinerja tinggi dengan kode sumber terbuka berbasis Java. Sama seperti banyak sistem RPC lainnya, dubbo didasarkan pada gagasan untuk menciptakan layanan yang mendefinisikan beberapa metode yang dapat dipanggil dari jauh dengan parameter dan jenis nilai pengembalian. Di sisi server, antarmuka diimplementasikan dan server dubbo diluncurkan untuk menangani permintaan pelanggan. Di sisi klien, ada tulisan rintisan yang menyediakan metode yang sama seperti server. Dubbo menyarankan tiga fungsi utama, yang meliputi panggilan jarak jauh antarmuka, toleransi kegagalan dan penyeimbangan muatan, serta pendaftaran otomatis dan penemuan layanan.
Tentang Analisis
Urutan tindakan analisis ini cukup sederhana dan tidak memerlukan banyak waktu dalam kasus saya:
- Unduh Apache Dubbo dari GitHub ;
- Menggunakan instruksi penganalisa Java start-up dan menjalankan analisis;
- Dapatkan laporan analisa, tinjau dan soroti beberapa kasus menarik.
Hasil analisis: 73 peringatan tingkat kepastian Tinggi dan Menengah (masing-masing 46 dan 27) dikeluarkan untuk 4000+ file, yang merupakan indikator kualitas kode yang bagus.
Tidak semua peringatan adalah kesalahan. Ini adalah hasil yang biasa, seperti sebelum langsung menggunakan analisa, kita harus mengkonfigurasinya. Setelah itu, Anda dapat mengharapkan persen positif palsu yang cukup rendah (
contoh ).
Saya tidak mempertimbangkan 9 peringatan (7 Tinggi dan 2 Sedang), terkait dengan file dengan tes.
Akibatnya, saya memiliki sejumlah kecil peringatan, yang juga termasuk positif palsu, karena saya belum mengonfigurasi analisanya. Menggali ke 73 peringatan dengan penjelasan lebih lanjut dalam artikel itu panjang, konyol dan membosankan, jadi saya memilih yang paling sulit.
Masuk Byte di Jawa
Ekspresi
V6007 'endKey [i] <0xff' selalu benar. OptionUtil.java (32)
public static final ByteSequence prefixEndOf(ByteSequence prefix) { byte[] endKey = prefix.getBytes().clone(); for (int i = endKey.length - 1; i >= 0; i--) { if (endKey[i] < 0xff) {
Nilai tipe
byte (nilai dari -128 hingga 127) dibandingkan dengan nilai 0xff (255). Dalam kondisi ini, tidak diperhitungkan bahwa tipe
byte di Java ditandatangani, oleh karena itu kondisi akan selalu benar, yang berarti tidak ada artinya.
Kembalinya Nilai yang Sama
Ekspresi
V6007 'isPreferIPV6Address ()' selalu salah. NetUtils.java (236)
private static Optional<InetAddress> toValidAddress(InetAddress address) { if (address instanceof Inet6Address) { Inet6Address v6Address = (Inet6Address) address; if (isPreferIPV6Address()) {
Metode ini
adalahPreferIPV6Address .
static boolean isPreferIPV6Address() { boolean preferIpv6 = Boolean.getBoolean("java.net.preferIPv6Addresses"); if (!preferIpv6) { return false;
Metode
isPreferIPV6Address mengembalikan
false dalam kedua kasus. Kemungkinan besar, seorang pengembang ingin satu kasus untuk mengembalikan
true, jika metode ini tidak masuk akal.
Pemeriksaan tidak berguna
Ekspresi
V6007 '! Mask [i]. Equals (ipAddress [i])' selalu benar. NetUtils.java (476)
public static boolean matchIpRange(....) throws UnknownHostException { .... for (int i = 0; i < mask.length; i++) { if ("*".equals(mask[i]) || mask[i].equals(ipAddress[i])) { continue; } else if (mask[i].contains("-")) { .... } else if (....) { continue; } else if (!mask[i].equals(ipAddress[i])) {
Dalam kondisi if-else-if, centang
"*". Setara (mask [i]) || mask [i]. equals (ipAddress [i]) dilakukan. Jika kondisinya tidak terpenuhi, check-in berikutnya jika-else-if terjadi yang menunjukkan kepada kita bahwa
mask [i] dan
ipAddress [i] tidak sama. Tetapi dalam salah satu pemeriksaan berikut ini di if-else-if itu diperiksa bahwa
mask [i] dan
ipAddress [i] tidak sama. Karena
mask [i] dan
ipAddress [i ] tidak diberi nilai apa pun, pemeriksaan kedua tidak ada gunanya.
Ekspresi
V6007 'message.length> 0' selalu benar. DeprecatedTelnetCodec.java (302)
V6007 Ekspresi 'message! = Null' selalu benar. DeprecatedTelnetCodec.java (302)
protected Object decode(.... , byte[] message) throws IOException { .... if (message == null || message.length == 0) { return NEED_MORE_INPUT; } ....
Pesan cek
! = Null && message.length> 0 pada baris 302 berlebihan. Sebelum garis check in 302, cek berikut dilakukan:
if (message == null || message.length == 0) { return NEED_MORE_INPUT; }
Jika kondisi pemeriksaan tidak memenuhi, kita akan tahu bahwa
pesan tidak nol dan panjangnya tidak sama dengan 0. Ini mengikuti bahwa panjangnya lebih dari 0 (karena panjang string tidak boleh menjadi angka negatif).
Pesan variabel lokal tidak diberi nilai apa pun sebelum baris 302, yang berarti di baris 302 nilai variabel
pesan sama seperti dalam kode di atas. Dapat disimpulkan bahwa
pesan ekspresi
! = Null && message.length> 0 akan selalu benar, tetapi kode di blok lain tidak akan pernah dieksekusi.
Menetapkan Nilai Bidang Referensi yang Tidak Diinisialisasi
Ekspresi
V6007 '! ShouldExport ()' selalu salah. ServiceConfig.java (371)
public synchronized void export() { checkAndUpdateSubConfigs(); if (!shouldExport()) {
Metode
shouldExport dari kelas
ServiceConfig memanggil metode
getExport , didefinisikan dalam kelas yang sama.
private boolean shouldExport() { Boolean export = getExport();
Metode
getExport memanggil metode
getExport dari kelas
AbstractServiceConfig abstrak, yang mengembalikan nilai bidang
ekspor tipe
Boolean . Ada juga metode
setExport untuk mengatur nilai bidang.
protected Boolean export; .... public Boolean getExport() { return export; } .... public void setExport(Boolean export) { this.export = export; }
Bidang ekspor diatur dalam kode hanya dengan metode
setExport . Metode
setExport dipanggil hanya dalam metode build dari kelas
AbstractServiceBuilder abstrak (memperluas
AbstractServiceConfig ) hanya dalam kasus jika bidang tersebut tidak nol.
@Override public void build(T instance) { .... if (export != null) { instance.setExport(export); } .... }
Karena kenyataan bahwa semua bidang referensi secara default diinisialisasi dengan
nol dan bidang
ekspor tidak diberi nilai, metode
setExport tidak akan pernah dipanggil.
Akibatnya, metode
getExport dari kelas
ServiceConfig , memperluas kelas
AbstractServiceConfig akan selalu mengembalikan
nol . Nilai kembali digunakan dalam metode
shouldExport dari kelas
ServiceConfig , oleh karena itu metode
shouldExport akan selalu mengembalikan
true . Karena mengembalikan true, nilai ekspresi
! ShouldExport () akan selalu salah. Ternyata, bidang
ekspor kelas
ServiceConfig tidak akan pernah dikembalikan sampai kode berikut dijalankan:
if (shouldDelay()) { DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), ....); } else { doExport(); }
Nilai Parameter Non-negatif
V6009 Fungsi 'substring' dapat menerima nilai '-1' sementara nilai non-negatif diharapkan. Periksa argumen: 2. AbstractEtcdClient.java (169)
protected void createParentIfAbsent(String fixedPath) { int i = fixedPath.lastIndexOf('/'); if (i > 0) { String parentPath = fixedPath.substring(0, i); if (categories.stream().anyMatch(c -> fixedPath.endsWith(c))) { if (!checkExists(parentPath)) { this.doCreatePersistent(parentPath); } } else if (categories.stream().anyMatch(c -> parentPath.endsWith(c))) { String grandfather = parentPath .substring(0, parentPath.lastIndexOf('/'));
Hasil fungsi
lastIndexOf dilewatkan sebagai parameter kedua ke fungsi
substring , yang parameter kedua tidak boleh berupa angka negatif, meskipun
lastIndexOf dapat mengembalikan
-1 jika tidak menemukan nilai yang dicari dalam string. Jika parameter kedua dari metode
substring kurang dari yang pertama (-1 <0), pengecualian
StringIndexOutOfBoundsException akan dibuang. Untuk memperbaiki kesalahan, kita perlu memeriksa hasilnya, dikembalikan oleh fungsi
lastIndexOf . Jika benar (setidaknya, bukan negatif), berikan ke fungsi
substring .
Penghitung loop yang tidak digunakan
V6016 Akses mencurigakan ke elemen objek 'types' dengan indeks konstan di dalam satu loop. RpcUtils.java (153)
public static Class<?>[] getParameterTypes(Invocation invocation) { if ($INVOKE.equals(invocation.getMethodName()) && invocation.getArguments() != null && invocation.getArguments().length > 1 && invocation.getArguments()[1] instanceof String[]) { String[] types = (String[]) invocation.getArguments()[1]; if (types == null) { return new Class<?>[0]; } Class<?>[] parameterTypes = new Class<?>[types.length]; for (int i = 0; i < types.length; i++) { parameterTypes[i] = ReflectUtils.forName(types[0]);
Dalam
for loop, indeks konstan
0 digunakan untuk mengakses elemen
tipe array. Itu mungkin dimaksudkan untuk menggunakan variabel
i sebagai indeks untuk mengakses elemen array. Tetapi penulis telah meninggalkan itu.
Sia-sia lakukan-saat
V6019 Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. GrizzlyCodecAdapter.java (136)
@Override public NextAction handleRead(FilterChainContext context) throws IOException { .... do { savedReadIndex = frame.readerIndex(); try { msg = codec.decode(channel, frame); } catch (Exception e) { previousData = ChannelBuffers.EMPTY_BUFFER; throw new IOException(e.getMessage(), e); } if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) { frame.readerIndex(savedReadIndex); return context.getStopAction(); } else { if (savedReadIndex == frame.readerIndex()) { previousData = ChannelBuffers.EMPTY_BUFFER; throw new IOException("Decode without read data."); } if (msg != null) { context.setMessage(msg); return context.getInvokeAction(); } else { return context.getInvokeAction(); } } } while (frame.readable());
Ekspresi dalam kondisi loop
do -
while (frame.readable ()) adalah kode yang tidak dapat dijangkau, saat metode keluar selama iterasi pertama loop. Beberapa pemeriksaan variabel
msg dilakukan di badan loop menggunakan if-else. Dengan demikian, baik dalam
jika dan nilai-nilai dikembalikan dari metode atau pengecualian dilemparkan. Itulah alasan mengapa tubuh loop dijalankan hanya sekali, jadi penggunaan loop ini tidak ada gunanya.
Salin-tempel di sakelar
V6067 Dua atau lebih cabang kasus melakukan tindakan yang sama. JVMUtil.java (67), JVMUtil.java (71)
private static String getThreadDumpString(ThreadInfo threadInfo) { .... if (i == 0 && threadInfo.getLockInfo() != null) { Thread.State ts = threadInfo.getThreadState(); switch (ts) { case BLOCKED: sb.append("\t- blocked on " + threadInfo.getLockInfo()); sb.append('\n'); break; case WAITING:
Kode sebagai
ganti untuk kasus
WAITING dan
TIMED_WAITING berisi kode salin-tempel. Jika tindakan yang sama harus dilakukan, kode dapat disederhanakan dengan menghapus konten blok
kasus WAITING . Akibatnya, kode yang sama akan dieksekusi untuk
WAITING dan
TIMED_WAITING.Kesimpulan
Siapa pun yang tertarik menggunakan
RPC di Jawa, mungkin pernah mendengar tentang Apache Dubbo. Ini adalah proyek open source yang populer dengan cerita panjang dan kode, yang ditulis oleh banyak pengembang. Kode proyek ini berkualitas tinggi, masih penganalisis kode statis PVS-Studio berhasil menemukan sejumlah kesalahan. Ini mengarah pada fakta bahwa analisis statis sangat penting ketika mengembangkan proyek-proyek berukuran sedang dan besar, tidak peduli seberapa sempurna kode Anda.
Catatan Pemeriksaan satu kali seperti itu menunjukkan kemampuan penganalisa kode statis, tetapi merupakan cara penggunaan yang sepenuhnya salah. Lebih detail dari ide ini diuraikan di
sini dan di
sini . Gunakan analisis secara teratur!
Terima kasih kepada pengembang Apache Dubbo untuk alat yang luar biasa ini. Saya harap artikel ini akan membantu Anda meningkatkan kode. Artikel ini tidak menjelaskan semua bagian kode yang mencurigakan, jadi sebaiknya pengembang memeriksa sendiri proyek dan mengevaluasi hasilnya.