Dalam versi ketujuh dari analisa statis PVS-Studio, kami menambahkan dukungan dari bahasa Java. Saatnya untuk cerita singkat tentang bagaimana kami mulai membuat dukungan bahasa Jawa, seberapa jauh kami telah datang, dan apa yang ada dalam rencana kami selanjutnya. Tentu saja, artikel ini akan mendaftar uji coba analisa pertama pada proyek open source.
PVS-Studio
Berikut ini adalah deskripsi singkat tentang PVS-Studio untuk pengembang Java yang belum pernah mendengarnya.
Alat ini dirancang untuk mendeteksi kesalahan dan kerentanan potensial dalam kode sumber program, ditulis dalam C, C ++, C #, dan Java. Ia bekerja di lingkungan Windows, Linux, dan macOS.
PVS-Studio melakukan analisis kode statis dan menghasilkan laporan yang membantu pengembang menemukan dan menghilangkan cacat. Bagi mereka yang tertarik pada bagaimana tepatnya PVS-Studio mencari kesalahan, saya sarankan untuk melihat artikel "
Teknologi yang digunakan dalam analisa kode PVS-Studio untuk menemukan bug dan potensi kerentanan ".
Awal
Saya bisa saja datang dengan cerita yang cerdik tentang bagaimana kami telah berspekulasi tentang apa bahasa berikutnya untuk mendukung di PVS-Studio. Tentang pilihan Jawa yang masuk akal, yang didasarkan pada popularitas tinggi bahasa ini, dan sebagainya.
Namun, seperti yang terjadi dalam hidup, pilihan itu dibuat bukan dengan analisis mendalam, tetapi dengan eksperimen :). Ya, kami memikirkan arah pengembangan lebih lanjut dari penganalisa PVS-Studio. Kami mempertimbangkan bahasa-bahasa tersebut, seperti: Java, PHP, Python, JavaScript, IBM RPG. Kami bahkan cenderung ke bahasa Jawa tetapi pilihan terakhir tidak dibuat. Bagi mereka yang pandangannya tertuju pada IBM RPG yang tidak dikenal, saya ingin mengarahkan Anda ke
catatan ini, dari mana semuanya akan menjadi jelas.
Pada akhir 2017, kolega saya Egor Bredikhin meninjau pustaka kode parsing (dengan kata lain - parser) untuk arah pengembangan baru, menarik bagi kami. Akhirnya, ia menemukan beberapa proyek untuk mem-parsing kode Java. Dia berhasil dengan cepat membuat prototipe analyzer dengan beberapa diagnostik berdasarkan
Spoon . Selain itu, telah menjadi jelas bahwa kita akan dapat menggunakan dalam Java analyzer beberapa mekanisme dari C ++ analyzer menggunakan
SWIG . Kami melihat apa yang kami dapatkan dan menyadari bahwa penganalisa kami berikutnya adalah untuk Java.
Kami ingin mengucapkan terima kasih kepada Egor atas usaha dan kerja kerasnya yang telah ia lakukan selama menganalisis Java. Proses pengembangan itu sendiri dijelaskan olehnya dalam artikel "
Pengembangan analisa statis baru: PVS-Studio Java ."
Bagaimana dengan pesaing?
Ada banyak penganalisa kode statis gratis dan komersial untuk Java di seluruh dunia. Tidak ada gunanya mencantumkan semuanya dalam artikel. Saya hanya akan meninggalkan tautan ke "
Daftar alat untuk analisis kode statis " (lihat bagian Java dan Multi-Bahasa).
Namun, saya tahu bahwa pertama dan terpenting kita akan ditanya tentang IntelliJ IDEA, FindBugs, dan SonarQube (SonarJava).
IntelliJ IDEAPenganalisa kode statis yang sangat kuat dibangun di IntelliJ IDEA. Terlebih lagi, penganalisa terus berkembang, penulisnya mengikuti dengan cermat kegiatan kami. Jadi, IntelliJ IDEA adalah cookie yang sulit bagi kami. Kami tidak akan dapat melampaui IntelliJ IDEA dalam kemampuan diagnostik, setidaknya untuk saat ini. Karena itu, kami akan berkonsentrasi pada keunggulan kami yang lain.
Analisis statis di IntelliJ IDEA terutama adalah salah satu fitur lingkungan, yang membebankan batasan tertentu padanya. Bagi kami, kami memiliki kebebasan dalam apa yang dapat kami lakukan dengan analis kami. Misalnya, kami dapat dengan cepat mengadaptasinya untuk kebutuhan pelanggan tertentu. Dukungan cepat dan mendalam adalah keunggulan kompetitif kami. Klien kami berkomunikasi secara langsung dengan pengembang, mengerjakan satu atau bagian lain dari PVS-Studio.
Dalam PVS-Studio, ada banyak peluang untuk mengintegrasikannya ke dalam siklus pengembangan proyek-proyek besar yang lama. Misalnya, ini adalah
integrasi kami
dengan SonarQube . Ini juga mencakup
penindasan massal terhadap penganalisa, yang memungkinkan Anda untuk segera mulai menggunakan alat dalam proyek besar untuk melacak bug hanya dalam kode baru atau yang dimodifikasi. PVS-Studio
dapat dibangun dalam proses integrasi berkelanjutan. Saya pikir ini dan fitur lainnya akan membantu penganalisa kami untuk menemukan tempat di bawah Matahari di dunia Jawa.
FindbugsProyek FindBugs ditinggalkan. Namun demikian, kita harus menyebutkannya dengan alasan bahwa, mungkin, ini adalah penganalisa statis bebas yang paling terkenal dari kode Java.
SpotBugs bisa disebut penerus FindBugs. Namun, itu kurang populer, dan belum jelas apa yang akan terjadi dengannya.
Secara umum, kami berpikir bahwa meskipun FindBugs telah dan masih tetap sangat populer, dan selain itu penganalisa gratis, kita tidak boleh memikirkannya. Proyek ini hanya akan diam-diam menjadi sejarah.
Ngomong-ngomong, sekarang PVS-Studio juga dapat
digunakan secara gratis ketika bekerja dengan proyek terbuka.
SonarQube (SonarJava)Kami percaya bahwa kami tidak bersaing dengan SonarQube, tetapi melengkapinya. PVS-Studio terintegrasi dalam SonarQube, yang memungkinkan pengembang untuk menemukan lebih banyak bug dan kerentanan keamanan potensial dalam proyek mereka. Kami secara teratur memberi tahu cara mengintegrasikan alat PVS-Studio dan analisis lain di SonarQube pada kelas master yang kami pegang dalam hal konferensi yang berbeda.
Cara Menjalankan PVS-Studio untuk Java
Kami menyediakan cara-cara paling populer dari integrasi penganalisa dalam sistem build untuk pengguna:
- Plugin untuk Maven;
- Plugin untuk Gradle;
- Plugin untuk IntelliJ IDEA
Selama fase pengujian, kami bertemu banyak pengguna yang memiliki sistem pembangunan yang ditulis sendiri, terutama di bidang pengembangan ponsel. Mereka menikmati kesempatan untuk menjalankan alat analisis secara langsung, mendaftar sumber dan classpath.
Anda dapat menemukan informasi terperinci tentang semua cara untuk menjalankan analisa pada halaman dokumentasi "
Cara Menjalankan PVS-Studio Java ".
Kami tidak dapat menghindar dari platform kontrol kualitas kode
SonarQube , yang sangat populer di kalangan pengembang Java, jadi kami menambahkan dukungan bahasa Java di
plugin kami
untuk SonarQube .
Rencana selanjutnya
Kami memiliki banyak ide yang mungkin memerlukan penyelidikan lebih lanjut, tetapi beberapa rencana spesifik, yang melekat pada salah satu analis kami, adalah sebagai berikut:
- Pembuatan diagnostik baru dan peningkatan yang sudah ada;
- Peningkatan Analisis Dataflow;
- Meningkatkan keandalan dan kegunaan.
Mungkin, kita akan menemukan waktu untuk mengadaptasi plugin IntelliJ IDEA untuk CLion. Hai untuk pengembang C ++ yang membaca tentang Java analyzer :-)
Contoh Kesalahan Ditemukan di Proyek Sumber Terbuka
Menggigil kayu saya jika saya tidak menunjukkan dalam artikel beberapa bug ditemukan dengan analisa baru! Yah, kita bisa mengambil proyek Java open source yang besar dan menulis artikel klasik yang meninjau kesalahan, seperti yang
biasa kita lakukan .
Namun, saya segera mengantisipasi pertanyaan tentang apa yang dapat kami temukan dalam proyek-proyek seperti IntelliJ IDEA, FindBugs, dan sebagainya. Jadi saya tidak punya jalan keluar, selain mulai dengan proyek-proyek ini. Jadi, saya memutuskan untuk segera memeriksa dan menulis beberapa contoh kesalahan yang menarik dari proyek-proyek berikut:
- Edisi Komunitas IntelliJ IDEA . Saya pikir tidak perlu menjelaskan mengapa proyek ini dipilih :).
- SpotBugs Seperti yang saya tulis sebelumnya, proyek FindBugs tidak mengalami kemajuan. Jadi mari kita lihat ke dalam proyek SpotBugs, yang merupakan penerus FindBugs. SpotBugs adalah penganalisa statis kode Java klasik.
- Sesuatu dari proyek perusahaan SonarSource, yang mengembangkan perangkat lunak untuk pemantauan terus menerus terhadap kualitas kode. Sekarang mari kita lihat ke dalam SonarQube dan SonarJava .
Menulis tentang bug dari proyek-proyek ini adalah sebuah tantangan. Faktanya adalah bahwa proyek-proyek ini berkualitas sangat tinggi. Sebenarnya, itu tidak mengejutkan. Pengamatan kami menunjukkan bahwa penganalisa kode statis selalu diuji dengan baik dan diverifikasi menggunakan alat lain.
Terlepas dari semua ini, saya harus memulai persis dengan proyek-proyek ini. Saya tidak akan memiliki kesempatan kedua untuk menulis tentang mereka. Saya yakin bahwa setelah rilis PVS-Studio untuk Java, pengembang proyek yang terdaftar akan menggunakan PVS-Studio dan mulai menggunakannya untuk reguler atau, setidaknya, sesekali memeriksa kode mereka. Sebagai contoh, saya tahu bahwa Tagir Valeev (
lany ), salah satu pengembang JetBrains, bekerja pada penganalisis kode statis IntelliJ IDEA, saat ini, ketika saya sedang menulis artikel tersebut sudah bermain dengan versi Beta dari PVS-Studio . Dia menulis kepada kami sekitar 15 email dengan laporan bug dan rekomendasi. Terima kasih, Tagir!
Untungnya, saya tidak perlu menemukan banyak bug dalam satu proyek tertentu. Saat ini tugas saya adalah menunjukkan bahwa penganalisa PVS-Studio untuk Java muncul tidak sia-sia, dan akan dapat mengisi sederet alat lain yang dirancang untuk meningkatkan kualitas kode. Saya baru saja memeriksa laporan alat analisa dan membuat daftar beberapa kesalahan yang sepertinya menarik. Jika memungkinkan, saya mencoba mengutip berbagai jenis kesalahan. Mari kita lihat bagaimana hasilnya.
IntelliJ IDEA, Divisi Integer
private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2;
Peringatan PVS-Studio: V6011 [CWE-682] Literal '0,2' dari tipe 'ganda' dibandingkan dengan nilai tipe 'int'. TitleCapitalizationInspection.java 169
Intinya adalah bahwa fungsi harus mengembalikan true jika kurang dari 20% dari kata-kata dimulai dengan huruf kapital. Sebenarnya, pemeriksaan tidak berfungsi, karena pembagian bilangan bulat terjadi. Sebagai hasil pembagian, kita hanya dapat memperoleh dua nilai: 0 atau 1.
Fungsi akan mengembalikan false, hanya jika semua kata dimulai dengan huruf kapital. Dalam semua kasus lain, operasi pembagian akan menghasilkan 0 dan fungsi akan mengembalikan true.
IntelliJ IDEA, Loop Mencurigakan
public int findPreviousIndex(int current) { int count = myPainter.getErrorStripeCount(); int foundIndex = -1; int foundLayer = 0; if (0 <= current && current < count) { current--; for (int index = count - 1; index >= 0; index++) {
Peringatan PVS-Studio: V6007 [CWE-571] Ekspresi 'indeks> = 0' selalu benar. Updater.java 184
Pertama, lihat kondisinya
(0 <= saat && saat ini <hitung) . Ini dijalankan hanya dalam kasus jika nilai variabel
jumlah lebih besar dari 0.
Sekarang lihat loop:
for (int index = count - 1; index >= 0; index++)
Indeks variabel diinisialisasi dengan
jumlah ekspresi
- 1 . Karena variabel
jumlah lebih besar dari 0, nilai awal dari variabel
indeks akan selalu lebih besar atau sama dengan 0. Ternyata loop akan dieksekusi sampai terjadi overflow dari variabel
indeks .
Kemungkinan besar, itu hanya kesalahan ketik, dan penurunan, bukan kenaikan variabel yang harus dijalankan:
for (int index = count - 1; index >= 0; index--)
IntelliJ IDEA, Copy-Paste
@NonNls public static final String BEFORE_STR_OLD = "before:"; @NonNls public static final String AFTER_STR_OLD = "after:"; private static boolean isBeforeOrAfterKeyword(String str, boolean trimKeyword) { return (trimKeyword ? LoadingOrder.BEFORE_STR.trim() : LoadingOrder.BEFORE_STR).equalsIgnoreCase(str) || (trimKeyword ? LoadingOrder.AFTER_STR.trim() : LoadingOrder.AFTER_STR).equalsIgnoreCase(str) || LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str) ||
Peringatan PVS-Studio: V6001 [CWE-570] Ada sub-ekspresi identik 'LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase (str)' ke kiri dan ke kanan '||' operator. Periksa baris: 127, 128. ExtensionOrderConverter.java 127
Efek lama yang bagus
dari baris terakhir . Seorang pengembang melompat pistol dan telah memperbanyak baris kode, lupa memperbaikinya. Akibatnya, string
str dibandingkan dengan
BEFORE_STR_OLD dua kali. Kemungkinan besar, salah satu perbandingan harus dengan
AFTER_STR_OLD .
IntelliJ IDEA, Typo
public synchronized boolean isIdentifier(@NotNull String name, final Project project) { if (!StringUtil.startsWithChar(name,'\'') && !StringUtil.startsWithChar(name,'\"')) { name = "\"" + name; } if (!StringUtil.endsWithChar(name,'"') && !StringUtil.endsWithChar(name,'\"')) { name += "\""; } .... }
Peringatan PVS-Studio: V6001 [CWE-571] Ada sub-ekspresi identik '! StringUtil.endsWithChar (nama,' "')' di sebelah kiri dan di sebelah kanan operator '&&'. JsonNamesValidator.java 27
Fragmen kode ini memeriksa bahwa nama terlampir dalam tanda kutip tunggal atau ganda. Jika tidak, tanda kutip ganda ditambahkan secara otomatis.
Karena kesalahan ketik, akhir nama diperiksa hanya untuk adanya tanda kutip ganda. Akibatnya, nama dalam tanda kutip tunggal akan diproses secara tidak benar.
Nama
'Abcd'
karena menambahkan tanda kutip ganda tambahan akan berubah menjadi:
'Abcd'"
IntelliJ IDEA, Perlindungan Salah dari Array Overrun
static Context parse(....) { .... for (int i = offset; i < endOffset; i++) { char c = text.charAt(i); if (c == '<' && i < endOffset && text.charAt(i + 1) == '/' && startTag != null && CharArrayUtil.regionMatches(text, i + 2, endOffset, startTag)) { endTagStartOffset = i; break; } } .... }
Peringatan PVS-Studio: V6007 [CWE-571] Ekspresi 'i <endOffset' selalu benar. MasukkanAfterJavadocTagHandler.java 183
Subekspresi
i <endOffset dalam kondisi
jika operator tidak masuk akal. Variabel
i selalu kurang dari
endOffset dalam hal apa pun, yang mengikuti kondisi eksekusi loop.
Kemungkinan besar, pengembang ingin melindungi dari string overrun saat memanggil fungsi:
- text.charAt (i +1)
- CharArrayUtil.regionMatches (teks, i + 2, endOffset, startTag)
Dalam hal ini, subekspresi untuk memeriksa indeks harus:
(i) <endOffset-2 .
IntelliJ IDEA, Pengecekan Berulang
public static String generateWarningMessage(....) { .... if (buffer.length() > 0) { if (buffer.length() > 0) { buffer.append(" ").append( IdeBundle.message("prompt.delete.and")).append(" "); } } .... }
Peringatan PVS-Studio: V6007 [CWE-571] Ekspresi 'buffer.length ()> 0' selalu benar. DeleteUtil.java 62
Ini bisa berupa kode berlebihan yang tidak berbahaya atau kesalahan penting.
Jika pemeriksaan duplikat muncul secara tidak sengaja, misalnya selama refactoring, tidak ada yang salah dengan itu. Anda bisa menghapus cek kedua.
Skenario lain juga dimungkinkan. Pemeriksaan kedua harus sangat berbeda dan kode berperilaku tidak seperti yang dimaksudkan. Maka itu adalah kesalahan nyata.
Catatan Ngomong-ngomong, ada banyak berbagai macam cek berlebihan. Yah, sering kali jelas bahwa itu bukan kesalahan. Namun, kami tidak dapat menganggap peringatan penganalisa sebagai positif palsu. Untuk penjelasan, saya ingin mengutip contoh seperti itu, juga diambil dari IntelliJ IDEA:
private static boolean isMultiline(PsiElement element) { String text = element.getText(); return text.contains("\n") || text.contains("\r") || text.contains("\r\n"); }
Penganalisa mengatakan bahwa function
text.contains ("\ r \ n") selalu mengembalikan false. Memang, jika karakter "\ n" dan "\ r" tidak ditemukan, tidak ada gunanya mencari "\ r \ n". Ini bukan bug, dan kodenya buruk hanya karena bekerja sedikit lebih lambat, melakukan pencarian yang tidak berarti untuk substring.
Bagaimana menangani kode semacam itu, dalam setiap kasus, merupakan pertanyaan bagi pengembang. Saat menulis artikel, saya biasanya tidak memperhatikan kode seperti itu.
IntelliJ IDEA, Ada Yang Salah
public boolean satisfiedBy(@NotNull PsiElement element) { .... @NonNls final String text = expression.getText().replaceAll("_", ""); if (text == null || text.length() < 2) { return false; } if ("0".equals(text) || "0L".equals(text) || "0l".equals(text)) { return false; } return text.charAt(0) == '0'; }
Peringatan PVS-Studio: V6007 [CWE-570] Ekspresi '"0". Equals (text)' selalu salah. ConvertIntegerToDecimalPredicate.java 46
Kode berisi kesalahan logis pasti. Saya merasa sulit untuk mengatakan apa sebenarnya yang ingin diperiksa oleh programmer dan cara memperbaiki cacat. Jadi di sini, saya hanya akan menunjuk pada cek yang tidak berarti.
Pada awalnya harus diperiksa bahwa string berisi setidaknya dua simbol. Jika tidak, maka fungsi mengembalikan
false .
Selanjutnya muncul tanda centang
"0". Equals (text) . Ini tidak ada artinya, karena tidak ada string yang hanya dapat berisi satu karakter.
Jadi, ada yang salah di sini, dan kodenya harus diperbaiki.
SpotBugs (Penerus FindBugs), Kesalahan Batasan Jumlah Iterasi
public static String getXMLType(@WillNotClose InputStream in) throws IOException { .... String s; int count = 0; while (count < 4) { s = r.readLine(); if (s == null) { break; } Matcher m = tag.matcher(s); if (m.find()) { return m.group(1); } } throw new IOException("Didn't find xml tag"); .... }
Peringatan PVS-Studio: V6007 [CWE-571] Ekspresi 'count <4' selalu benar. Util.java 394
Secara teori, pencarian tag xml harus dilakukan hanya dalam empat baris pertama file. Tetapi karena fakta bahwa seseorang lupa untuk menambah variabel
jumlah , seluruh file akan dibaca.
Pertama, ini bisa menjadi operasi yang sangat lambat, dan kedua, di suatu tempat di tengah file, sesuatu mungkin ditemukan yang akan dianggap sebagai tag xml, bukan itu.
SpotBugs (Penerus FindBugs), Kliring Nilai
private void reportBug() { int priority = LOW_PRIORITY; String pattern = "NS_NON_SHORT_CIRCUIT"; if (sawDangerOld) { if (sawNullTestVeryOld) { priority = HIGH_PRIORITY;
Peringatan PVS-Studio: V6021 [CWE-563] Nilai diberikan ke variabel 'prioritas' tetapi tidak digunakan. FindNonShortCircuit.java 197
Nilai variabel
prioritas diatur tergantung pada nilai variabel
sawNullTestVeryOld . Namun, itu tidak masalah sama sekali. Setelah itu, variabel
prioritas akan diberi nilai lain dalam kasus apa pun. Kesalahan yang jelas dalam logika fungsi.
SonarQube, Salin-Tempel
public class RuleDto { .... private final RuleDefinitionDto definition; private final RuleMetadataDto metadata; .... private void setUpdatedAtFromDefinition(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } private void setUpdatedAtFromMetadata(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } .... }
PVS-Studio: V6032 Aneh bahwa tubuh metode 'setUpdatedAtFromDefinition' sepenuhnya setara dengan tubuh metode lain 'setUpdatedAtFromMetadata'. Periksa baris: 396, 405. RuleDto.java 396
Bidang
definisi digunakan dalam metode
setUpdatedAtFromMetadata . Kemungkinan besar, bidang
metadata harus digunakan. Ini sangat mirip dengan efek dari Copy-Paste yang gagal.
SonarJava, Duplikat Saat Menginisialisasi Peta
private final Map<JavaPunctuator, Tree.Kind> assignmentOperators = Maps.newEnumMap(JavaPunctuator.class); public KindMaps() { .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... }
Peringatan PVS-Studio: V6033 [CWE-462] Item dengan kunci yang sama 'JavaPunctuator.PLUSEQU' telah ditambahkan. Periksa baris: 104, 100. KindMaps.java 104
Pasangan nilai kunci yang sama diatur di peta dua kali. Kemungkinan besar, itu terjadi secara tidak sengaja dan sebenarnya tidak ada kesalahan nyata. Namun, kode ini harus diperiksa dalam hal apa pun, karena, mungkin, seseorang lupa menambahkan pasangan lain.
Kesimpulan
Mengapa menulis kesimpulan ketika itu sangat jelas ?! Saya sarankan Anda mengunduh PVS-Studio sekarang juga dan coba periksa proyek kerja Anda dalam bahasa Jawa!
Unduh PVS-Studio .
Terima kasih atas perhatiannya. Saya berharap bahwa segera kami akan menyenangkan pembaca kami dengan serangkaian artikel tentang memeriksa berbagai proyek Java open source.