2019 akan segera berakhir, dan tim PVS-Studio sedang melihat kembali pencapaian tahun ini. Pada awal 2019, kami meningkatkan kemampuan diagnostik analis kami dengan menambahkan dukungan Java, yang memungkinkan kami untuk memeriksa dan meninjau proyek-proyek Java juga. Kami telah menemukan banyak bug selama tahun ini, dan inilah 10 bug teratas kami yang ditemukan di proyek Java.
Tidak. 10: byte yang ditandatangani
Sumber:
Analisis Kerangka Apache Dubbo RPC oleh PVS-Studio Static Code AnalyzerEkspresi
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) {
Banyak programmer percaya bahwa tipe
byte tidak ditandai. Ini memang terjadi dalam banyak bahasa pemrograman. Misalnya, ini berlaku untuk C #. Tetapi tidak demikian halnya di Jawa.
Dalam kondisi
endKey [i] <0xff , variabel tipe
byte dibandingkan dengan angka 255 (0xff) yang diwakili dalam bentuk heksadesimal. Pengembang mungkin lupa bahwa kisaran tipe
byte Java adalah [-128, 127]. Kondisi ini akan selalu benar, dan
untuk loop akan selalu memproses hanya elemen terakhir dari array
endKey .
Tidak. 9: Dua dalam satu
Sumber:
PVS-Studio untuk Jawa menyentuh jalan. Perhentian berikutnya adalah ElasticsearchEkspresi
V6007 '(int) x <0' selalu salah. BCrypt.java (429)
V6025 Kemungkinan indeks '(int) x' di luar batas. BCrypt.java (431)
private static byte char64(char x) { if ((int)x < 0 || (int)x > index_64.length) return -1; return index_64[(int)x]; }
Diskon! Satu metode - dua bug! Yang pertama berkaitan dengan tipe
char : karena tidak ditandatangani di Java, kondisi
(int) x <0 akan selalu salah. Bug kedua adalah pengindeksan biasa di luar batas array
index_64 ketika
(int) x == index_64.length . Ini terjadi karena kondisi
(int) x> index_64.length . Bug dapat diperbaiki dengan mengubah operator '>' menjadi '> =':
(int) x> = index_64.length .
Tidak. 8: Solusi dan implikasinya
Sumber:
Menganalisis Kode Platform CUBA dengan PVS-StudioEkspresi
V6007 'previousMenuItemFlatIndex> = 0' selalu benar. CubaSideMenuWidget.java (328)
protected MenuItemWidget findNextMenuItem(MenuItemWidget currentItem) { List<MenuTreeNode> menuTree = buildVisibleTree(this); List<MenuItemWidget> menuItemWidgets = menuTreeToList(menuTree); int menuItemFlatIndex = menuItemWidgets.indexOf(currentItem); int previousMenuItemFlatIndex = menuItemFlatIndex + 1; if (previousMenuItemFlatIndex >= 0) {
Penulis metode
findNextMenuItem ingin menyingkirkan nilai -1 yang dikembalikan oleh metode
indexOf ketika daftar
menuItemWidgets tidak mengandung
currentItem . Untuk melakukan itu, programmer menambahkan 1 ke hasil
indexOf (variabel
menuItemFlatIndex ) dan menulis nilai yang dihasilkan ke variabel
beforeMenuItemFlatIndex , yang kemudian digunakan dalam metode ini. Penambahan -1 terbukti menjadi solusi yang buruk karena menyebabkan beberapa kesalahan sekaligus:
- Pernyataan null kembali tidak akan pernah dieksekusi karena ekspresi beforeMenuItemFlatIndex> = 0 akan selalu benar; Oleh karena itu, metode findNextMenuItem akan selalu kembali dari dalam pernyataan if ;
- IndexOutOfBoundsException akan dimunculkan ketika daftar menuItemWidgets telah menjadi kosong karena program akan mencoba mengakses elemen pertama dari daftar kosong;
- IndexOutOfBoundsException akan dimunculkan ketika argumen currentItem telah menjadi elemen terakhir dari daftar menuItemWidget .
Tidak. 7: Membuat file dari ketiadaan
Sumber:
Huawei Cloud: Berawan di PVS-Studio Hari IniV6008 Potensi null dereference dari 'dataTmpFile'. CacheManager.java (91)
@Override public void putToCache(PutRecordsRequest putRecordsRequest) { .... if (dataTmpFile == null || !dataTmpFile.exists()) { try { dataTmpFile.createNewFile();
Saat menulis metode
putToCache , programmer membuat kesalahan ketik dalam kondisi
dataTmpFile == null || ! dataTmpFile.exists () sebelum membuat file baru
dataTmpFile.createNewFile () : mereka menulis operator '==' alih-alih '! ='. Kesalahan ketik ini akan menyebabkan Anda melempar
NullPointerException saat memanggil metode
createNewFile . Beginilah kondisinya dengan kesalahan pengetikan:
if (dataTmpFile != null || !dataTmpFile.exists())
Anda mungkin berpikir, βBaiklah, kita bisa santai sekarang.β Belum!
Sekarang satu bug telah diperbaiki, satu lagi muncul.
NullPointerException dapat dilemparkan saat memanggil metode
dataTmpFile.exists () . Untuk memperbaiki ini, kita perlu mengganti '||' operator dengan '&&'. Ini adalah versi terakhir dari kondisi, setelah semua perbaikan:
if (dataTmpFile != null && !dataTmpFile.exists())
Tidak. 6: Kesalahan logika yang sangat aneh
Sumber:
PVS-Studio untuk JavaV6007 [CWE-570] Ekspresi '"0". Equals (text)' selalu salah. ConvertIntegerToDecimalPredicate.java 46
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)) {
Hal yang menarik tentang metode ini adalah mengandung kesalahan logika yang jelas. Jika metode
puas dengan tidak kembali setelah pernyataan
if pertama, itu berarti bahwa string
teks setidaknya dua karakter. Ini juga berarti bahwa pemeriksaan pertama
"0". Sama dengan (teks) di pernyataan berikutnya
jika tidak ada artinya. Apa yang sebenarnya dimaksud oleh programmer itu masih merupakan misteri.
Tidak. 5: Benar-benar aneh!
Sumber:
PVS-Studio Mengunjungi Apache HiveV6034 Shift dengan nilai 'bitShiftsInWord - 1' bisa tidak konsisten dengan ukuran tipe: 'bitShiftsInWord - 1' = [-1 ... 30]. UnsignedInt128.java (1791)
private void shiftRightDestructive(int wordShifts, int bitShiftsInWord, boolean roundUp) { if (wordShifts == 0 && bitShiftsInWord == 0) { return; } assert (wordShifts >= 0); assert (bitShiftsInWord >= 0); assert (bitShiftsInWord < 32); if (wordShifts >= 4) { zeroClear(); return; } final int shiftRestore = 32 - bitShiftsInWord;
Dengan argumen input
wordShifts = 3 dan
bitShiftsInWord = 0 , variabel
roundCarryMask , yang menyimpan hasil pergeseran bitwise
(1 << (bitShiftsInWord - 1)) , akan menjadi angka negatif. Pengembang mungkin tidak mengharapkan itu.
Tidak. 4: Bisakah kita melihat pengecualian?
Sumber:
PVS-Studio Mengunjungi Apache HiveV6051 Penggunaan pernyataan 'kembali' di blok 'akhirnya' dapat menyebabkan hilangnya pengecualian yang tidak ditangani. ObjectStore.java (9080)
private List<MPartitionColumnStatistics> getMPartitionColumnStatistics(....) throws NoSuchObjectException, MetaException { boolean committed = false; try { .... committed = commitTransaction(); return result; } catch (Exception ex) { LOG.error("Error retrieving statistics via jdo", ex); if (ex instanceof MetaException) { throw (MetaException) ex; } throw new MetaException(ex.getMessage()); } finally { if (!committed) { rollbackTransaction(); return Lists.newArrayList(); } } }
Deklarasi metode
getMPartitionColumnStatistics menyesatkan karena dikatakan bisa melempar pengecualian. Pada kenyataannya, pengecualian apa pun yang dihasilkan di blok
percobaan , variabel yang
dikomit akan selalu
salah , sehingga pernyataan
kembali di blok
akhirnya akan mengembalikan nilai, sementara semua pengecualian yang dilemparkan akan hilang, tidak dapat diproses di luar metode. Jadi, tidak ada pengecualian yang dilemparkan dalam metode ini yang dapat meninggalkannya.
Tidak. 3: Hocus-pocus, atau mencoba untuk mendapatkan topeng baru
Sumber:
PVS-Studio Mengunjungi Apache HiveV6034 Shift dengan nilai 'j' bisa tidak konsisten dengan ukuran tipe: 'j' = [0 ... 63]. IoTrace.java (272)
public void logSargResult(int stripeIx, boolean[] rgsToRead) { .... for (int i = 0, valOffset = 0; i < elements; ++i, valOffset += 64) { long val = 0; for (int j = 0; j < 64; ++j) { int ix = valOffset + j; if (rgsToRead.length == ix) break; if (!rgsToRead[ix]) continue; val = val | (1 << j);
Bug ini juga ada hubungannya dengan perubahan bitwise, tetapi tidak hanya itu. Variabel
j digunakan sebagai penghitung rentang [0 ... 63] di dalam
untuk loop. Penghitung ini berpartisipasi dalam shift bitwise
1 << j . Semuanya tampak OK, tetapi di sinilah '1' integer literal dari tipe
int (nilai 32-bit) ikut bermain. Karena itu, pergeseran bitwise akan mulai mengembalikan nilai yang sebelumnya dikembalikan ketika nilai variabel
j telah melebihi 31. Jika perilaku ini tidak seperti yang diinginkan oleh programmer, nilai 1 harus direpresentasikan sebagai
panjang :
1L << j atau
(panjang) 1 << j .
Tidak. 2: Pesanan inisialisasi
Sumber:
Huawei Cloud: Berawan di PVS-Studio Hari IniSiklus inisialisasi Kelas
V6050 hadir. Inisialisasi 'INSTANCE' muncul sebelum inisialisasi 'LOG'. UntrustedSSL.java (32), UntrustedSSL.java (59), UntrustedSSL.java (33)
public class UntrustedSSL { private static final UntrustedSSL INSTANCE = new UntrustedSSL(); private static final Logger LOG = LoggerFactory.getLogger(UntrustedSSL.class); .... private UntrustedSSL() { try { .... } catch (Throwable t) { LOG.error(t.getMessage(), t);
Urutan bidang mana yang dideklarasikan dalam kelas membuat perbedaan karena mereka diinisialisasi dalam urutan yang sama mereka dideklarasikan. Melupakan fakta ini menyebabkan bug yang sulit dipahami seperti yang ada di atas.
Penganalisis menunjukkan bahwa bidang statis
LOG didereferensi dalam konstruktor saat ini diinisialisasi ke nilai
nol , yang mengarah ke melempar serangkaian pengecualian
NullPointerException ->
ExceptionInInitializerError .
Tetapi mengapa
LOG bidang statis memiliki nilai
nol pada saat memanggil konstruktor?
ExceptionInInitializerError adalah petunjuknya. Konstruktor sedang menginisialisasi
INSTANCE bidang statis, yang dideklarasikan sebelum bidang
LOG . Itu sebabnya bidang
LOG belum diinisialisasi ketika konstruktor dipanggil. Agar kode ini berfungsi dengan baik, bidang
LOG harus diinisialisasi sebelum panggilan.
Pertama: pemrograman berorientasi copy-paste
Sumber:
Apache Hadoop Code Quality: Production VS TestV6072 Dua fragmen kode serupa ditemukan. Mungkin, ini adalah kesalahan ketik dan variabel 'localFiles' harus digunakan daripada 'localArchives'. LocalDistributedCacheManager.java (183), LocalDistributedCacheManager.java (178), LocalDistributedCacheManager.java (176), LocalDistributedCacheManager.java (181)
public synchronized void setup(JobConf conf, JobID jobId) throws IOException { ....
Tempat pertama dalam daftar Top 10 kami diberikan untuk menyalin-menempel, atau, lebih tepatnya, bug yang berasal dari penggunaan teknik ini secara sembrono. Pernyataan
if kedua terlihat sangat mirip dengan salinan yang pertama, dengan beberapa variabel dimodifikasi:
- localArchives diubah menjadi LocalFiles ;
- MRJobConfig.CACHE_LOCALARCHIVES diubah menjadi MRJobConfig.CACHE_LOCALFILES .
Tetapi programmer berhasil membuat kesalahan bahkan dalam operasi sederhana ini: pernyataan
if di baris yang ditunjukkan oleh penganalisa masih menggunakan variabel
localArchives bukannya variabel
localFiles yang dimaksudkan.
Kesimpulan
Memperbaiki bug yang ditemukan pada tahap pengembangan selanjutnya atau setelah rilis membutuhkan banyak sumber daya. Alat analisa statis PVS-Studio memungkinkan Anda untuk mendeteksi bug pada tahap pengkodean, sehingga membuatnya lebih mudah dan lebih murah. Banyak
perusahaan telah membuat hidup pengembang mereka lebih mudah dengan mulai menggunakan analisa secara teratur. Jika Anda ingin benar-benar menikmati pekerjaan Anda, cobalah
PVS-Studio .
Kami tidak akan berhenti pada hal itu dan berencana untuk terus meningkatkan dan meningkatkan alat kami. Nantikan diagnostik dan artikel baru dengan bug yang lebih menarik di tahun berikutnya.
Saya melihat Anda suka petualangan! Pertama, Anda mengalahkan
10 bug teratas dalam proyek C # pada tahun 2019 dan sekarang Anda berhasil dengan Java! Selamat datang di tingkat berikutnya - artikel tentang
kesalahan terbaik dalam proyek C ++ pada 2019 .