PVS-Studio untuk Java

PVS-Studio untuk Java

Dalam versi ketujuh dari analisa statis PVS-Studio, kami menambahkan dukungan untuk bahasa Java. Sudah waktunya untuk berbicara sedikit tentang bagaimana kami mulai melakukan dukungan untuk bahasa Jawa, apa yang kami lakukan dan apa rencana masa depan. Dan, tentu saja, artikel itu akan menunjukkan tes pertama dari penganalisa pada proyek terbuka.

PVS-Studio


Untuk pengembang Java yang belum pernah mendengar tentang alat PVS-Studio sebelumnya, saya akan memberikan deskripsi singkat tentang itu.

PVS-Studio adalah alat untuk mendeteksi kesalahan dan kerentanan potensial dalam kode sumber program yang ditulis dalam C, C ++, C # dan Java. Ini berjalan pada Windows, Linux, dan macOS.

PVS-Studio melakukan analisis kode statis dan menghasilkan laporan yang membantu programmer menemukan dan memperbaiki cacat. Bagi mereka yang tertarik bagaimana tepatnya PVS-Studio mencari kesalahan, saya sarankan Anda membaca artikel " Teknologi yang Digunakan dalam Analisis Kode PVS-Studio untuk Menemukan Kesalahan dan Kerentanan Potensial ".

Mulai


Saya bisa menghasilkan cerita yang cerdas, karena kami telah berpikir selama dua tahun tentang bahasa apa yang akan didukung di PVS-Studio. Fakta bahwa Java adalah pilihan yang masuk akal berdasarkan popularitas tinggi bahasa ini dan sebagainya.

Namun, seperti yang terjadi dalam hidup, semuanya diputuskan bukan oleh analisis mendalam, tetapi dengan eksperimen :). Ya, kami berpikir ke arah mana penganalisa PVS-Studio harus dikembangkan lebih lanjut. Bahasa pemrograman seperti: Java, PHP, Python, JavaScript, IBM RPG dipertimbangkan. Dan kami cenderung ke bahasa Jawa, tetapi pilihan terakhir belum dibuat. Mereka yang matanya terpaku pada IBM RPG yang tidak dikenal, saya merujuk pada catatan ini di sini , dari mana semuanya akan menjadi jelas.

Pada akhir 2017, rekan Egor Bredikhin melihat perpustakaan siap pakai untuk kode parsing (dengan kata lain, parser) tersedia untuk arah baru yang menarik bagi kami. Dan saya menemukan beberapa proyek untuk mem-parsing kode Java. Berdasarkan Spoon , ia dengan cepat berhasil membuat alat analisa prototipe dengan beberapa diagnosa. Selain itu, menjadi jelas bahwa kita dapat menggunakan beberapa mekanisme penganalisa C ++ dengan bantuan SWIG dalam penganalisis Java. Kami melihat apa yang terjadi dan menyadari bahwa pengurai kami berikutnya adalah untuk Jawa.

Terima kasih kepada Egor untuk usahanya dan pekerjaan aktif yang dilakukan olehnya pada Java analyzer. Bagaimana perkembangan berlangsung ia menjelaskan dalam artikel " Pengembangan Analyzer Statis Baru: PVS-Studio Java ".

Pesaing?


Ada banyak penganalisa kode statis gratis dan komersial untuk Java di dunia. Tidak masuk akal untuk mendaftar semuanya dalam artikel, dan saya hanya meninggalkan tautan ke " Daftar alat untuk analisis kode statis " (lihat bagian Java dan Multi-bahasa).

Namun, saya tahu bahwa pertama-tama kita akan ditanya tentang IntelliJ IDEA, FindBugs dan SonarQube (SonarJava).

IntelliJ IDEA

IntelliJ IDEA memiliki penganalisa kode statis yang sangat kuat. Selain itu, penganalisa sedang berkembang, dan penulisnya memantau dengan cermat kegiatan kami. Dengan IntelliJ IDEA kami akan menjadi yang paling sulit. Kami tidak akan dapat melampaui IntelliJ IDEA dalam kemampuan diagnostik, setidaknya untuk saat ini. Karena itu, kami akan mencoba berkonsentrasi pada keunggulan kami yang lain.

Analisis statis di IntelliJ IDEA adalah, pertama-tama, salah satu chip lingkungan pengembangan, yang menerapkan batasan tertentu padanya. Kami bebas dalam apa yang dapat kami lakukan dengan analis kami. Misalnya, kami dapat dengan cepat mengadaptasi analisa untuk kebutuhan spesifik pelanggan. Dukungan cepat dan mendalam adalah keunggulan kompetitif kami. Klien kami berkomunikasi langsung dengan programmer yang sedang mengembangkan bagian tertentu dari PVS-Studio.

PVS-Studio memiliki banyak kemungkinan untuk mengintegrasikannya ke dalam siklus pengembangan proyek-proyek besar yang lama. Ini adalah integrasi dengan SonarQube . Ini adalah penindasan besar - besaran dari pesan analisa, yang memungkinkan Anda untuk segera mulai menggunakan analisa dalam proyek besar untuk melacak kesalahan hanya dalam kode baru atau yang diubah. PVS-Studio terintegrasi ke dalam proses integrasi berkelanjutan. Saya pikir ini dan fitur lainnya akan membantu penganalisa kami menemukan tempat di bawah matahari di dunia Jawa.

Findbugs

Proyek FindBugs ditinggalkan . Tetapi harus diingat dengan alasan bahwa ini mungkin merupakan penganalisa statik bebas yang paling terkenal dari kode Java.

Penerus FindBugs adalah proyek SpotBugs . Namun, ia kurang populer, dan apa yang akan terjadi padanya juga belum sepenuhnya jelas.

Secara umum, kami percaya bahwa meskipun FindBugs adalah dan tetap sangat populer, dan juga penganalisa gratis, kita tidak boleh memikirkannya. Proyek ini hanya diam-diam saja di masa lalu.

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 dengan SonarQube, yang memungkinkan pengembang untuk menemukan lebih banyak kesalahan dan kerentanan potensial dalam proyek mereka. Bagaimana cara mengintegrasikan alat PVS-Studio dan alat analisis lainnya ke SonarQube, kami secara teratur berbicara di kelas master yang kami selenggarakan di berbagai konferensi ( contoh ).

Bagaimana cara memulai PVS-Studio untuk Java


Kami telah menyediakan bagi pengguna cara paling populer untuk mengintegrasikan alat analisis ke dalam sistem perakitan:

  • Plugin untuk Maven;
  • Plugin untuk Gradle;
  • Plugin untuk IntelliJ IDEA

Pada tahap pengujian, kami bertemu banyak pengguna yang memiliki sistem perakitan yang ditulis sendiri, terutama dalam pengembangan ponsel. Mereka menyukai kemampuan untuk menjalankan analisa secara langsung, mendaftar sumber dan classpath.

Anda dapat menemukan informasi terperinci tentang semua metode memulai analisa pada halaman dokumentasi " Cara memulai PVS-Studio Java ".

Kami tidak dapat mengabaikan platform kontrol kualitas kode SonarQube , yang sangat populer di kalangan pengembang Java, jadi kami menambahkan dukungan bahasa Java ke plugin SonarQube kami.

Rencana selanjutnya


Kami memiliki banyak ide yang perlu dipelajari lebih lanjut, tetapi beberapa rencana khusus untuk analisis kami terlihat seperti ini:

  • Pembuatan diagnostik baru dan penyempurnaan yang sudah ada;
  • Pengembangan analisis aliran data;
  • Meningkatkan keandalan dan kegunaan.

Kami mungkin menemukan waktu untuk menyesuaikan plugin IntelliJ IDEA untuk CLion. Hai C ++ kepada pengembang yang membaca tentang Java analyzer :-)

Contoh kesalahan ditemukan di proyek sumber terbuka


Saya tidak akan menjadi saya jika saya tidak menunjukkan kesalahan yang ditemukan menggunakan penganalisa baru dalam artikel. Kita dapat mengambil beberapa proyek Java open source besar dan menulis artikel klasik dengan analisis kesalahan, seperti yang biasa kita lakukan .

Namun, saya segera meramalkan pertanyaan apakah kita dapat menemukan sesuatu dalam proyek seperti IntelliJ IDEA, FindBugs dan sebagainya. Karena itu, saya tidak punya jalan keluar, dan saya akan memulai dengan tepat 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 berkembang. Jadi lihatlah proyek SpotBugs, yang merupakan penerus FindBugs. SpotBugs adalah penganalisa kode Java statis klasik.
  • Beberapa proyek SonarSource, yang mengembangkan perangkat lunak untuk kontrol kualitas kode berkelanjutan. Lihatlah proyek SonarQube dan SonarJava .

Menulis tentang bug dalam proyek-proyek ini adalah tugas yang sulit. Faktanya adalah bahwa proyek-proyek ini berkualitas sangat tinggi. Sebenarnya, ini tidak mengejutkan. Seperti pengamatan kami menunjukkan, kode analisa statis selalu diuji dan diverifikasi dengan baik menggunakan alat lain.

Terlepas dari semua ini, saya harus mulai dengan proyek-proyek ini. Saya tidak akan memiliki kesempatan kedua untuk menulis sesuatu tentang mereka. Saya yakin bahwa setelah rilis rilis PVS-Studio untuk Java, pengembang proyek ini akan menggunakan PVS-Studio dan akan mulai menggunakannya untuk pemeriksaan kode mereka secara teratur atau setidaknya secara berkala. Sebagai contoh, saya tahu bahwa Tagir Valeyev ( lany ), salah satu pengembang JetBrains yang terlibat dalam penganalisis kode statis IntelliJ IDEA, sudah bermain dengan versi Beta PVS-Studio pada saat saya menulis artikel. Dia telah menulis kepada kami sekitar 15 surat dengan laporan bug dan rekomendasi. Tagir terima kasih!

Untungnya, saya tidak perlu menemukan kesalahan sebanyak mungkin dalam satu proyek tertentu. Sekarang tugas saya adalah untuk menunjukkan bahwa alat analisa PVS-Studio untuk Java muncul tidak sia-sia dan akan dapat mengisi garis alat lain yang dirancang untuk meningkatkan kualitas kode. Saya baru saja membaca laporan analisa dan menulis beberapa kesalahan yang sepertinya menarik bagi saya. Kapan pun memungkinkan, saya mencoba menulis kesalahan dari berbagai jenis. Mari kita lihat apa yang terjadi.

Divisi Integer IDEA IntelliJ


private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2; // allow reasonable amount of // capitalized words } 

Peringatan PVS-Studio: V6011 [CWE-682] Literal '0,2' dari tipe 'ganda' dibandingkan dengan nilai tipe 'int'. TitleCapitalizationInspection.java 169

Sebagaimana dimaksud, fungsi harus mengembalikan true jika kurang dari 20% dari kata-kata dimulai dengan huruf kapital. Bahkan, pemeriksaan tidak berfungsi, karena pembagian bilangan bulat terjadi. Sebagai hasil dari pembagian, hanya dua nilai yang dapat diperoleh: 0 atau 1.

Fungsi akan mengembalikan nilai yang salah hanya jika semua kata dimulai dengan huruf kapital. Dalam semua kasus lain, pembagian akan menghasilkan 0, dan fungsi akan mengembalikan nilai sebenarnya.

Siklus Mencurigakan IntelliJ IDEA


 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++) { // <= int layer = getLayer(index); if (layer > foundLayer) { foundIndex = index; foundLayer = layer; } } .... } 

Peringatan PVS-Studio: V6007 [CWE-571] Ekspresi 'indeks> = 0' selalu benar. Updater.java 184

Pertama, lihat kondisinya (0 <= saat && saat ini <hitung) . Ini hanya dieksekusi 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 variabel indeks selalu lebih besar dari atau sama dengan 0. Ternyata loop akan dieksekusi sampai variabel indeks meluap.

Kemungkinan besar, ini hanya salah ketik dan kenaikannya tidak boleh dieksekusi, tetapi penurunan variabel:

 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) || // <= LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str); // <= } 

PVS-Studio Warning: 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 . Si programmer bergegas dan, setelah mengalikan satu baris kode, lupa memperbaikinya. Akibatnya, dua kali string str dibandingkan dengan BEFORE_STR_OLD . Kemungkinan besar, salah satu perbandingan harus dengan AFTER_STR_OLD .

Kesalahan ketik IDEA IntelliJ


 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 += "\""; } .... } 

PVS-Studio Warning: V6001 [CWE-571] Ada sub-ekspresi identik '! StringUtil.endsWithChar (nama,' "')' di sebelah kiri dan di sebelah kanan operator '&&'. JsonNamesValidator.java 27

Potongan kode ini memverifikasi bahwa nama itu dalam tanda kutip tunggal atau ganda. Jika ini bukan masalahnya, tanda kutip ganda ditambahkan secara otomatis.

Karena kesalahan ketik, akhir nama hanya diperiksa untuk tanda kutip ganda. Akibatnya, nama yang diambil dalam tanda kutip tunggal tidak akan diproses dengan benar.

Nama depan

 'Abcd' 

karena penambahan tanda kutip ganda tambahan itu akan berubah menjadi:

 'Abcd'" 

IntelliJ IDEA, perlindungan overflow array yang salah


 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 pernyataan if tidak masuk akal. Variabel i selalu kurang dari endOffset , sebagai berikut dari kondisi untuk mengeksekusi loop.

Kemungkinan besar, pemrogram ingin melindungi dirinya dari keluar saat memanggil fungsi:

  • text.charAt (i +1)
  • CharArrayUtil.regionMatches (teks, i + 2, endOffset, startTag)

Dalam hal ini, subekspresi untuk memeriksa indeks harus seperti ini: i <endOffset - 2 .

IntelliJ IDEA Ulangi Periksa


 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 serius.

Jika pemeriksaan duplikat muncul secara kebetulan, misalnya, selama refactoring, maka tidak ada yang salah dengan itu. Pemeriksaan kedua hanya dapat dihapus.

Tapi skenario lain mungkin terjadi. Pemeriksaan kedua harus benar-benar berbeda dan kode tidak berlaku sebagaimana dimaksud. Maka ini adalah kesalahan nyata.

Catatan Omong-omong, ada banyak cek berlebih yang berbeda. Selain itu, sering terlihat bahwa ini bukan kesalahan. Namun, pesan penganalisa juga tidak bisa disebut false positive. Untuk memperjelas, berikut ini sebuah contoh, 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 fungsi text.contains ("\ r \ n") selalu mengembalikan false. Memang, jika simbol "\ n" dan "\ r" tidak ditemukan, maka tidak ada gunanya mencari "\ r \ n". Ini bukan kesalahan, dan kode itu buruk hanya karena kerjanya sedikit lebih lambat, melakukan pencarian yang tidak berarti untuk substring.

Bagaimana menangani kode seperti itu, dalam setiap kasus, terserah programmer untuk memutuskan. Saat menulis artikel, sebagai aturan, saya tidak memperhatikan kode tersebut.

IntelliJ IDEA, ada sesuatu 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 ini jelas mengandung kesalahan logis. Tetapi saya merasa sulit untuk mengatakan apa yang ingin diperiksa oleh programmer, dan bagaimana cara memperbaiki cacat tersebut. Karena itu, di sini saya hanya akan menunjukkan pemeriksaan yang tidak berarti.

Pada awalnya, diperiksa bahwa string harus mengandung setidaknya dua karakter. Jika tidak, maka fungsi mengembalikan false .

Berikut ini adalah tanda “0”. Sama dengan (teks) . Ini tidak ada artinya, karena sebuah string tidak dapat berisi hanya satu karakter.

Secara umum, ada sesuatu yang salah di sini dan kode harus diperbaiki.

SpotBugs (penerus FindBugs), kesalahan batas 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

Sesuai rencana, pencarian untuk tag xml harus dilakukan hanya dalam empat baris pertama file. Tetapi karena fakta bahwa mereka lupa untuk menambah jumlah variabel, seluruh file akan dibaca.

Pertama, ini bisa berubah menjadi operasi yang sangat lambat, dan kedua, di suatu tempat di tengah file dapat ditemukan sesuatu yang akan ditafsirkan sebagai tag xml, tetapi tidak akan seperti itu.

SpotBugs (penerus FindBugs), nilai yang ditimpa


 private void reportBug() { int priority = LOW_PRIORITY; String pattern = "NS_NON_SHORT_CIRCUIT"; if (sawDangerOld) { if (sawNullTestVeryOld) { priority = HIGH_PRIORITY; // <= } if (sawMethodCallOld || sawNumericTestVeryOld && sawArrayDangerOld) { priority = HIGH_PRIORITY; // <= pattern = "NS_DANGEROUS_NON_SHORT_CIRCUIT"; } else { priority = NORMAL_PRIORITY; // <= } } bugAccumulator.accumulateBug( new BugInstance(this, pattern, priority).addClassAndMethod(this), this); } 

Peringatan PVS-Studio: V6021 [CWE-563] Nilai ini ditetapkan untuk variabel 'prioritas' tetapi tidak digunakan. FindNonShortCircuit.java 197

Nilai variabel prioritas diatur tergantung pada nilai variabel sawNullTestVeryOld . Namun, ini tidak memainkan peran apa pun. Selanjutnya, variabel prioritas akan diberi nilai berbeda dalam hal 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

Metode setUpdatedAtFromMetadata menggunakan bidang definisi . Kemungkinan besar, bidang metadata harus digunakan. Ini sangat mirip dengan konsekuensi dari Copy-Paste gagal.

SonarJava, duplikat pada inisialisasi 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 ditempatkan dua kali dalam kartu. Kemungkinan besar, ini ternyata lalai, dan sebenarnya tidak ada kesalahan nyata. Namun, dalam hal apa pun, kode ini perlu diperiksa, karena Anda mungkin lupa menambahkan beberapa pasangan lain.

Kesimpulan


Tapi apa kesimpulannya?! Saya mengundang semua orang, tanpa penundaan, untuk mengunduh PVS-Studio dan mencoba menguji proyek Anda yang sedang berjalan di Jawa! Unduh PVS-Studio .

Terima kasih atas perhatiannya. Saya berharap bahwa segera kami akan menyenangkan pembaca dengan serangkaian artikel yang ditujukan untuk memeriksa berbagai proyek Java terbuka.



Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Andrey Karpov. PVS-Studio untuk Java .

Source: https://habr.com/ru/post/id436434/


All Articles