2019 akan segera berakhir, dan tim PVS-Studio merangkum hasil tahun yang akan datang. Pada awal 2019, kami memperluas kemampuan penganalisa dengan mendukung bahasa Jawa. Oleh karena itu, daftar publikasi kami tentang pengecekan proyek terbuka diisi dengan ulasan proyek Jawa. Banyak kesalahan ditemukan sepanjang tahun, dan kami memutuskan untuk menyiapkan 10 besar yang paling menarik dari mereka.
Tempat kesepuluh: byte ikonik
Sumber:
Analisis kode sumber kerangka kerja RPC Apache Dubbo oleh analyzer statis PVS-StudioEkspresi
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 yang disebut
byte akan tidak ditandatangani. Dan memang, seringkali dalam bahasa yang berbeda inilah masalahnya. Misalnya, dalam C #, tipe
byte tidak ditandai. Di Jawa, ini bukan masalahnya.
Dalam kondisi
endKey [i] <0xff, penulis metode membandingkan variabel tipe
byte dengan angka 255 (0xff) yang diwakili dalam representasi heksadesimal. Rupanya, saat menulis metode, pengembang lupa bahwa kisaran nilai-nilai tipe
byte di Jawa adalah [-128, 127]. Kondisi ini selalu benar, jadi
untuk loop akan selalu memproses hanya elemen terakhir dari array
endKey .
Tempat kesembilan: dua dalam satu
Sumber:
PVS-Studio untuk Java dikirim ke jalur. 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]; }
Hari ini kami memiliki penawaran khusus! Dua kesalahan dalam satu metode sekaligus. Penyebab kesalahan pertama adalah tipe
char , yang tidak ditandatangani di Jawa, itulah sebabnya kondisi
(int) x <0 selalu salah. Kesalahan kedua adalah dangkal keluar dari
batas array
index_64 ketika
(int) x == index_64.length . Situasi ini dimungkinkan karena kondisi
(int) x> index_64.length . Untuk menghilangkan batas array, perlu untuk mengganti kondisi '>' dengan '> ='. Kondisi yang benar adalah:
(int) x> = index_64.length .
Tempat kedelapan: keputusan dan konsekuensinya
Sumber:
Analisis 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 -1 yang dikembalikan oleh metode
indexOf jika daftar
menuItemWidgets tidak mengandung
currentItem . Untuk melakukan ini, ia menambahkan satu ke hasil
indexOf (variabel
menuItemFlatIndex ) dan menyimpan nilai yang dihasilkan dalam variabelMenuItemFlatIndex
sebelumnya , yang selanjutnya digunakan dalam metode ini. Solusi untuk masalah -1 ini tidak berhasil karena menyebabkan beberapa kesalahan sekaligus:
- return null code tidak akan pernah dieksekusi, karena ekspresi beforeMenuItemFlatIndex > = 0 selalu benar, yang berarti bahwa pengembalian dari metode findNextMenuItem akan selalu terjadi di dalam if ;
- IndexOutOfBoundsException akan dilemparkan ketika daftar menuItemWidgets kosong, karena elemen pertama dari daftar kosong akan diakses;
- pengecualian IndexOutOfBoundsException akan terjadi ketika argumen currentItem adalah yang terakhir dalam daftar menuItemWidget .
Tempat ketujuh: membuat file dari ketiadaan
Sumber:
Huawei Cloud: hari ini berawan di PVS-StudioV6008 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
dataTmpFile.createNewFile () baru. Salah ketik adalah penggunaan operator '==' bukan '! ='. Kesalahan ketik ini akan melempar
NullPointerException saat memanggil metode
createNewFile . Kondisi setelah mengoreksi kesalahan ketik terlihat seperti ini:
if (dataTmpFile != null || !dataTmpFile.exists())
“Kesalahan ditemukan, diperbaiki. Anda bisa santai, ”Anda akan berpikir. Tapi bagaimanapun caranya!
Setelah memperbaiki satu kesalahan, kami menemukan yang lain. Sekarang,
NullPointerException dapat terjadi saat memanggil
dataTmpFile.exists () . Sekarang, untuk menyingkirkan pengecualian, perlu untuk mengganti operator '||' dalam kondisi di '&&'. Kondisi di mana semua kesalahan hilang adalah sebagai berikut:
if (dataTmpFile != null && !dataTmpFile.exists())
Tempat keenam: kesalahan logis 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)) {
Metode ini menarik karena mengandung kesalahan logis yang jelas. Jika metode
puas dengan tidak mengembalikan nilai setelah yang pertama
jika , diketahui bahwa string
teks terdiri dari setidaknya dua karakter. Karena itu, centang pertama
"0". Sama dengan (teks) di berikutnya
jika tidak ada artinya. Apa yang sebenarnya dimaksud pengembang tetap menjadi misteri.
Tempat kelima: ini giliran!
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 dari pergeseran bit
(1 << (bitShiftsInWord - 1)) , akan berubah menjadi angka negatif. Mungkin pengembang tidak mengharapkan perilaku ini.
Tempat keempat: apakah pengecualian akan berjalan-jalan?
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 berbohong kepada kami, mengatakan bahwa itu bisa melempar pengecualian. Ketika pengecualian terjadi dalam
percobaan , variabel yang
dikomit tetap
salah , oleh karena itu, di blok
akhirnya ,
pernyataan kembali mengembalikan nilai dari metode, dan semua pengecualian yang dilemparkan hilang dan tidak dapat diproses di luar metode. Dengan demikian, setiap pengecualian yang muncul dalam metode ini tidak akan pernah bisa keluar darinya.
Tempat ketiga: Saya memutar, berputar, saya ingin 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);
Kesalahan lain terkait dengan pergeseran bitwise, tetapi kali ini tidak hanya dia terlibat dalam kasus ini. Di dalam
untuk loop, variabel
j [0 ... 63] digunakan sebagai penghitung loop. Penghitung ini terlibat dalam sedikit pergeseran
1 << j . Tidak ada yang menandakan masalah, bagaimanapun, integer literal '1' dari tipe
int (nilai 32-bit) ikut bermain di sini. Oleh karena itu, hasil dari bit shift akan mulai diulang setelah
j lebih dari 31. Jika perilaku yang dijelaskan tidak diinginkan, maka unit harus direpresentasikan sebagai
panjang , misalnya,
1L << j atau
(panjang) 1 << j .
Tempat Kedua: Urutan Inisialisasi
Sumber:
Huawei Cloud: hari ini berawan di PVS-StudioSiklus 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 adalah penting karena bidang diinisialisasi dalam urutan mereka dideklarasikan. Namun, ketika mereka melupakannya, kesalahan kecil terjadi, seperti ini.
Penganalisis menunjukkan bahwa bidang
LOG statis direferensikan dalam konstruktor ketika diinisialisasi ke
nol , yang mengarah ke
NullPointerException -> rantai pengecualian
ExceptionInInitializerError .
"Mengapa, pada saat panggilan konstruktor, apakah bidang
LOG statis
nol ?" Anda bertanya.
Pengecualian ExceptionInInitializerError adalah petunjuk. Faktanya adalah bahwa konstruktor ini digunakan untuk menginisialisasi bidang statis
INSTANCE dideklarasikan di kelas lebih awal daripada bidang
LOG . Oleh karena itu, pada saat panggilan konstruktor, bidang
LOG masih belum diinisialisasi. Agar kode berfungsi dengan benar, perlu menginisialisasi bidang
LOG sebelum memanggil konstruktor.
Tempat pertama: pemrograman berorientasi copy-paste
Sumber:
Apache Hadoop Code Quality: produksi 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 { ....
Dan tempat pertama diambil dengan copy-paste, atau lebih tepatnya, kesalahan yang muncul karena kecerobohan orang yang melakukan hal berdosa ini. Sangat mungkin bahwa yang kedua
jika dibuat dengan copy-paste yang pertama dengan penggantian variabel:
- Arsip lokal tentang Berkas lokal ;
- MRJobConfig.CACHE_LOCALARCHIVES di MRJobConfig.CACHE_LOCALFILES .
Namun, bahkan dengan operasi sederhana seperti itu, kesalahan telah dibuat, karena variabel
localArchives masih digunakan di baris kedua
jika di
penganalisa kedua, meskipun penggunaan
localFiles kemungkinan besar tersirat.
Kesimpulan
Koreksi kesalahan yang ditemukan pada tahap pengembangan selanjutnya atau setelah rilis proyek membutuhkan sumber daya yang signifikan. Analyzer statis PVS-Studio menyederhanakan deteksi kesalahan saat menulis kode, yang secara signifikan mengurangi jumlah sumber daya yang dihabiskan untuk memperbaikinya. Penggunaan analisa yang konstan telah menyederhanakan kehidupan pengembang dari banyak
perusahaan . Jika Anda ingin memprogram dengan senang hati, cobalah
penganalisa kami.
Tim kami tidak akan berhenti di situ dan akan terus meningkatkan dan meningkatkan alat analisis. Harapkan diagnostik dan artikel baru dengan bug yang lebih menarik tahun depan.
Saya melihat Anda suka petualangan! Pertama,
10 kesalahan teratas dalam proyek C # untuk 2019 dimenangkan, dan sekarang Java dapat diatasi! Selamat datang di level berikutnya dalam artikel tentang
kesalahan terbaik 2019 dalam proyek C ++ .

Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Valery Komarov.
Top 10 Bug Ditemukan di Proyek Java pada tahun 2019 .