Selama sepuluh tahun terakhir, pergerakan open source telah menjadi salah satu faktor kunci dalam pengembangan industri TI dan merupakan bagian penting darinya. Peran dan tempat open source tidak hanya ditingkatkan oleh pertumbuhan indikator kuantitatif, tetapi ada juga perubahan dalam posisi kualitatifnya di pasar TI secara keseluruhan. Tanpa duduk diam, tim pemberani dari PVS-Studio secara aktif berkontribusi untuk mengkonsolidasikan posisi proyek sumber terbuka, menemukan bug tersembunyi dalam ketebalan besar basis kode dan menawarkan lisensi gratis untuk proyek-proyek tersebut. Artikel ini tidak terkecuali! Hari ini kita akan berbicara tentang Apache Hive! Laporan diterima - ada sesuatu untuk dilihat!
Tentang PVS-Studio
Alat analisis kode statis
PVS-Studio telah ada selama lebih dari 10 tahun di pasar TI dan merupakan solusi perangkat lunak yang multifungsi dan mudah diimplementasikan. Saat ini, alat analisa mendukung C, C ++, C #, bahasa Java dan bekerja pada platform Windows, Linux dan macOS.
PVS-Studio adalah solusi B2B berbayar dan digunakan oleh sejumlah besar tim di berbagai perusahaan. Jika Anda ingin melihat apa yang mampu dilakukan oleh alat analisa, maka unduh kit distribusi dan minta kunci percobaan di
sini .
Jika Anda seorang geek sumber terbuka atau, misalnya, adalah seorang siswa, maka Anda dapat menggunakan salah satu
opsi lisensi gratis
dari PVS-Studio.
Tentang Apache Hive
Volume data dalam beberapa tahun terakhir tumbuh dengan kecepatan tinggi. Database standar tidak lagi dapat mempertahankan operabilitas pada tingkat pertumbuhan dalam jumlah informasi yang berfungsi sebagai kemunculan istilah Big Data dan segala sesuatu yang terkait dengannya (pemrosesan, penyimpanan, dan semua tindakan selanjutnya dengan volume data yang demikian).
Saat ini,
Apache Hadoop dianggap sebagai salah satu teknologi dasar Big Data. Tujuan utama dari teknologi ini adalah penyimpanan, pemrosesan dan pengelolaan volume data yang besar. Komponen utama dari kerangka kerja adalah Hadoop Common,
HDFS ,
Hadoop MapReduce ,
Hadoop YARN . Seiring waktu, seluruh ekosistem proyek dan teknologi terkait telah terbentuk di sekitar Hadoop, banyak di antaranya yang awalnya dikembangkan sebagai bagian dari proyek, dan kemudian menjadi mandiri. Salah satu proyek ini adalah
Apache Hive .
Apache Hive adalah gudang data terdistribusi. Ini mengelola data yang disimpan dalam HDFS dan menyediakan bahasa query berbasis SQL (HiveQL) untuk bekerja dengan data ini. Untuk kenalan terperinci dengan proyek ini, Anda dapat mempelajari informasi di
sini .
Tentang analisis
Urutan langkah-langkah untuk analisis ini cukup sederhana dan tidak memerlukan banyak waktu:
- Mendapat Apache Hive dengan GitHub ;
- Saya menggunakan instruksi untuk memulai penganalisis Java dan memulai analisis;
- Saya menerima laporan analisa, menganalisisnya dan menyoroti kasus-kasus menarik.
Hasil analisis: 1456 peringatan tingkat kepercayaan Tinggi dan Menengah (masing-masing 602 dan 854) dikeluarkan untuk 6500+ file.
Tidak semua peringatan adalah kesalahan. Ini adalah situasi yang normal, dan sebelum menggunakan penganalisa secara teratur, konfigurasinya diperlukan. Maka kita dapat mengharapkan persentase positif palsu yang cukup rendah (
contoh ).
Di antara peringatan, 407 peringatan (177 Tinggi dan 230 Medium) per file uji tidak dipertimbangkan. Aturan diagnostik
V6022 tidak dipertimbangkan (sulit untuk memisahkan situasi yang salah dari yang benar dalam kode asing), yang memiliki sebanyak 482 peringatan.
V6021 dengan 179 peringatan juga tidak dipertimbangkan.
Pada akhirnya, semua sama, sejumlah peringatan tetap ada. Dan karena saya tidak mengonfigurasi analisa, di antara mereka lagi ada positif palsu. Tidak masuk akal untuk menggambarkan sejumlah besar peringatan dalam sebuah artikel :). Pertimbangkan apa yang menarik perhatian saya dan tampak menarik.
Kondisi yang ditentukan sebelumnya
Aturan diagnostik
V6007 adalah pemegang rekor di antara semua peringatan penganalisa yang tersisa. Lebih dari 200 peringatan !!! Beberapa, seperti, tidak berbahaya, beberapa mencurigakan, sementara yang lain benar-benar kesalahan! Mari kita lihat beberapa di antaranya.
V6007 Expression 'key.startsWith ("hplsql.")' Selalu benar. Exec.java (675)
void initOptions() { .... if (key == null || value == null || !key.startsWith("hplsql.")) {
Konstruksi if-else-if yang cukup panjang! Penganalisis bersumpah pada akhirnya
if (key.startsWith ("hplsql.")) , Menunjukkan kebenarannya jika program mencapai fragmen kode ini. Memang, jika Anda melihat awal konstruksi if-else-if, maka cek sudah selesai. Dan seandainya baris kami tidak dimulai dengan substring
"hplsql." , maka eksekusi kode langsung melompat ke iterasi berikutnya.
Ekspresi
V6007 '
columnNameProperty.length () == 0' selalu salah. OrcRecordUpdater.java (238)
private static TypeDescription getTypeDescriptionFromTableProperties(....) { .... if (tableProperties != null) { final String columnNameProperty = ....; final String columnTypeProperty = ....; if ( !Strings.isNullOrEmpty(columnNameProperty) && !Strings.isNullOrEmpty(columnTypeProperty)) { List<String> columnNames = columnNameProperty.length() == 0 ? new ArrayList<String>() : ....; List<TypeInfo> columnTypes = columnTypeProperty.length() == 0 ? new ArrayList<TypeInfo>() : ....; .... } } } .... }
Membandingkan panjang string dari
columnNameProperty dengan nol akan selalu menghasilkan
false . Ini karena perbandingan kami sedang diuji
! Strings.isNullOrEmpty (columnNameProperty) . Jika keadaan program mencapai kondisi kita yang dimaksud, maka baris
columnNameProperty dijamin tidak nol dan tidak kosong.
Ini juga berlaku untuk baris
columnTypeProperty . Baris peringatan di bawah ini:
- Ekspresi V6007 'columnTypeProperty.length () == 0' selalu salah. OrcRecordUpdater.java (239)
Ekspresi
V6007 'colOrScalar1.equals ("Column")' selalu salah. GenVectorCode.java (3469)
private void generateDateTimeArithmeticIntervalYearMonth(String[] tdesc) throws Exception { .... String colOrScalar1 = tdesc[4]; .... String colOrScalar2 = tdesc[6]; .... if (colOrScalar1.equals("Col") && colOrScalar1.equals("Column"))
}
Inilah copy-paste yang sepele. Ternyata garis
colOrScalar1 harus sama dengan nilai yang berbeda pada saat yang sama, dan ini tidak mungkin. Rupanya, variabel
colOrScalar1 harus diperiksa di sebelah kiri, dan
colOrScalar2 di sebelah kanan.
Lebih banyak peringatan serupa di baris di bawah ini:
- Ekspresi V6007 'colOrScalar1.equals ("Scalar")' selalu salah. GenVectorCode.java (3475)
- Ekspresi V6007 'colOrScalar1.equals ("Column")' selalu salah. GenVectorCode.java (3486)
Akibatnya, tidak ada tindakan dalam konstruksi if-else-if yang akan dieksekusi.
Beberapa peringatan lain untuk
V6007 :
- Ekspresi V6007 'karakter == null' selalu salah. RandomTypeUtil.java (43)
- Ekspresi V6007 'writeIdHwm> 0' selalu salah. TxnHandler.java (1603)
- Ekspresi V6007 'fields.equals ("*")' selalu benar. Server.java (983)
- Ekspresi V6007 'currentGroups! = Null' selalu benar. GenericUDFCurrentGroups.java (90)
- Ekspresi V6007 'this.wh == null' selalu salah. Pengembalian baru bukan-null referensi. StorageBasedAuthorizationProvider.java (93), StorageBasedAuthorizationProvider.java (92)
- dan seterusnya ...
NPE
V6008 Potensi null dereference dari 'dagLock'. QueryTracker.java (557), QueryTracker.java (553)
private void handleFragmentCompleteExternalQuery(QueryInfo queryInfo) { if (queryInfo.isExternalQuery()) { ReadWriteLock dagLock = getDagLock(queryInfo.getQueryIdentifier()); if (dagLock == null) { LOG.warn("Ignoring fragment completion for unknown query: {}", queryInfo.getQueryIdentifier()); } boolean locked = dagLock.writeLock().tryLock(); ..... } }
Tertangkap objek nol, berjanji dan ... terus bekerja. Ini mengarah pada fakta bahwa setelah memeriksa objek dereferencing objek nol terjadi. Kesedihan!
Kemungkinan besar, dalam kasus referensi nol, Anda harus segera keluar dari fungsi atau membuang beberapa pengecualian khusus.
V6008 Null dereference 'buffer' dalam fungsi 'unlockSingleBuffer'. MetadataCache.java (410), MetadataCache.java (465)
private boolean lockBuffer(LlapBufferOrBuffers buffers, ....) { LlapAllocatorBuffer buffer = buffers.getSingleLlapBuffer(); if (buffer != null) {
Dan lagi NPE potensial. Jika program mencapai metode
unlockSingleBuffer , objek
buffer akan menjadi nol. Katakanlah itu terjadi! Mari kita lihat metode
unlockSingleBuffer dan segera pada baris pertama kita melihat bahwa objek kita mengalami dereferensi. Kita disini!
Tidak mengikuti shift
V6034 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;
Kemungkinan diimbangi dengan -1. Jika, misalnya,
wordShifts == 3 dan
bitShiftsInWord == 0 datang ke input metode yang dimaksud, maka 1 << -1 akan muncul di baris yang ditentukan. Apakah ini direncanakan?
V6034 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);
Pada baris yang ditentukan, variabel
j dapat mengambil nilai dalam rentang [0 ... 63]. Karena itu, perhitungan nilai
val dalam loop mungkin tidak terjadi sesuai keinginan pengembang. Dalam ekspresi
(1 << j), unit bertipe
int , dan, menggesernya dari 32 atau lebih, kita melampaui batas yang diizinkan. Untuk memperbaiki situasi, Anda harus menulis
((panjang) 1 << j) .
Antusias tentang logging
V6046 Format salah. Jumlah item format yang berbeda diharapkan. Argumen tidak digunakan: 1, 2. StatsSources.java (89)
private static ImmutableList<PersistedRuntimeStats> extractStatsFromPlanMapper (....) { .... if (stat.size() > 1 || sig.size() > 1) { StringBuffer sb = new StringBuffer(); sb.append(String.format( "expected(stat-sig) 1-1, got {}-{} ;",
Saat memformat string melalui
String.format (), pengembang membingungkan sintaksisnya. Intinya: parameter yang dikirimkan tidak masuk ke string yang dihasilkan. Saya dapat berasumsi bahwa dalam tugas sebelumnya pengembang bekerja pada logging, dari mana dia meminjam sintaks.
Mencuri pengecualian
V6051 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(); } } }
Mengembalikan sesuatu dari blok terakhir adalah praktik yang sangat buruk, dan dengan contoh ini kita akan melihat ini.
Di blok
coba , permintaan terbentuk dan penyimpanan diakses. Variabel yang
dikomitmenkan secara default menjadi
false dan mengubah statusnya hanya setelah semua tindakan yang berhasil diselesaikan di blok
coba . Ini berarti bahwa jika pengecualian terjadi, variabel kami akan selalu
salah . Blok menangkap menangkap pengecualian, sedikit diperbaiki dan melemparkan lebih jauh. Dan ketika saatnya tiba untuk blok
akhirnya, eksekusi memasuki kondisi dari mana kita mengembalikan daftar kosong ke luar. Berapa pengembalian ini kepada kita? Tapi itu sepadan dengan kenyataan bahwa semua pengecualian yang tertangkap tidak akan pernah dibuang dan diproses dengan cara yang tepat. Semua pengecualian yang ditunjukkan dalam metode tanda tangan tidak akan pernah dibuang dan hanya membingungkan.
Peringatan serupa:
- V6051 Penggunaan pernyataan 'kembali' di blok 'akhirnya' dapat menyebabkan hilangnya pengecualian yang tidak ditangani. ObjectStore.java (808)
... lainnya
Fungsi
V6009 'compareTo' menerima argumen aneh. Objek 'o2.getWorkerIdentity ()' digunakan sebagai argumen untuk metode sendiri. LlapFixedRegistryImpl.java (244)
@Override public List<LlapServiceInstance> getAllInstancesOrdered(....) { .... Collections.sort(list, new Comparator<LlapServiceInstance>() { @Override public int compare(LlapServiceInstance o1, LlapServiceInstance o2) { return o2.getWorkerIdentity().compareTo(o2.getWorkerIdentity());
Copy-paste, kecerobohan, kesibukan dan banyak alasan lain untuk melakukan kesalahan bodoh ini. Saat memeriksa proyek sumber terbuka, kesalahan semacam ini cukup umum. Bahkan ada
artikel lengkap tentang itu.
V6020 Bagilah dengan nol. Kisaran nilai penyebut 'pembagi' termasuk nol. SqlMathUtil.java (265)
public static long divideUnsignedLong(long dividend, long divisor) { if (divisor < 0L) { return (compareUnsignedLong(dividend, divisor)) < 0 ? 0L : 1L; } if (dividend >= 0) {
Semuanya cukup sederhana di sini. Sejumlah cek tidak memperingatkan terhadap pembagian dengan 0.
Lebih banyak peringatan:
- V6020 Mod dengan nol. Kisaran nilai penyebut 'pembagi' termasuk nol. SqlMathUtil.java (309)
- V6020 Bagilah dengan nol. Kisaran nilai penyebut 'pembagi' termasuk nol. SqlMathUtil.java (276)
- V6020 Bagilah dengan nol. Kisaran nilai penyebut 'pembagi' termasuk nol. SqlMathUtil.java (312)
V6030 Metode yang terletak di sebelah kanan '|' operator akan dipanggil terlepas dari nilai operan kiri. Mungkin, lebih baik menggunakan '||'. OperatorUtils.java (573)
public static Operator<? extends OperatorDesc> findSourceRS(....) { .... List<Operator<? extends OperatorDesc>> parents = ....; if (parents == null | parents.isEmpty()) {
Alih-alih operator logis || menulis operator bitwise | Ini berarti bahwa sisi kanan akan dieksekusi terlepas dari hasil sisi kiri. Kesalahan seperti itu, dalam kasus
orang tua == null , akan segera mengarah ke NPE di subekspresi logis berikutnya.
V6042 Ekspresi diperiksa untuk kompatibilitas dengan tipe 'A' tetapi dilemparkan ke tipe 'B'. VectorColumnAssignFactory.java (347)
public static VectorColumnAssign buildObjectAssign(VectorizedRowBatch outputBatch, int outColIndex, PrimitiveCategory category) throws HiveException { VectorColumnAssign outVCA = null; ColumnVector destCol = outputBatch.cols[outColIndex]; if (destCol == null) { .... } else if (destCol instanceof LongColumnVector) { switch(category) { .... case LONG: outVCA = new VectorLongColumnAssign() { .... } .init(.... , (LongColumnVector) destCol); break; case TIMESTAMP: outVCA = new VectorTimestampColumnAssign() { .... }.init(...., (TimestampColumnVector) destCol);
Kelas-kelas yang
dimaksud adalah LongColumnVector extends ColumnVector dan
TimestampColumnVector extends ColumnVector . Memeriksa objek
destCol kami untuk kepemilikan
LongColumnVector dengan jelas memberi tahu kami bahwa objek kelas ini akan berada di dalam pernyataan bersyarat. Meskipun demikian, kami melakukan casting ke
TimestampColumnVector ! Seperti yang Anda lihat, kelas-kelas ini berbeda, tidak termasuk orang tua mereka. Akibatnya -
ClassCastException .
Semua yang sama dapat dikatakan tentang konversi tipe ke
IntervalDayTimeColumnVector :
- V6042 Ekspresi diperiksa untuk kompatibilitas dengan tipe 'A' tetapi dilemparkan ke tipe 'B'. VectorColumnAssignFactory.java (390)
V6060 Referensi 'var' digunakan sebelum diverifikasi terhadap nol. Var.java (402), Var.java (395)
@Override public boolean equals(Object obj) { if (getClass() != obj.getClass()) {
Perbandingan aneh dari objek
var dengan
null setelah dereferencing telah terjadi. Dalam konteks ini,
var dan
obj adalah objek yang sama (
var = (Var) obj ). Memeriksa
null menyiratkan bahwa objek nol dapat datang. Dan dalam kasus
equals (null), kita langsung mendapatkan NPE baris pertama dan bukan
false yang diharapkan. Sayangnya, ada cek, tetapi tidak ada.
Momen mencurigakan serupa menggunakan objek sebelum pemeriksaan terjadi:
- V6060 Referensi 'nilai' digunakan sebelum diverifikasi terhadap nol. ParquetRecordReaderWrapper.java (168), ParquetRecordReaderWrapper.java (166)
- V6060 Referensi 'defaultConstraintCols' digunakan sebelum diverifikasi terhadap nol. HiveMetaStore.java (2539), HiveMetaStore.java (2530)
- V6060 Referensi 'projIndxLst' digunakan sebelum diverifikasi terhadap nol. RelOptHiveTable.java (683), RelOptHiveTable.java (682)
- V6060 Referensi 'oldp' digunakan sebelum diverifikasi terhadap nol. ObjectStore.java (4343), ObjectStore.java (4339)
- dan seterusnya ...
Kesimpulan
Siapa pun yang sedikit tertarik pada Big Data, ia hampir tidak melewatkan arti dari Apache Hive. Proyek ini populer dan cukup besar, dan dalam komposisinya memiliki lebih dari 6500 file kode sumber (* .java). Kode ini telah ditulis oleh banyak pengembang selama bertahun-tahun dan, sebagai akibatnya, analisa statis memiliki sesuatu untuk ditemukan. Ini sekali lagi menegaskan bahwa analisis statis sangat penting dan berguna dalam pengembangan proyek menengah dan besar!
Catatan Pemeriksaan satu kali seperti itu menunjukkan kemampuan penganalisa kode statis, tetapi merupakan cara yang sepenuhnya salah untuk menggunakannya. Ide ini disajikan secara lebih rinci di
sini dan di
sini . Gunakan analisis secara teratur!
Saat memeriksa Hive, sejumlah kecacatan dan momen yang mencurigakan terdeteksi. Jika artikel ini menarik perhatian tim pengembang Apache Hive, kami akan senang berkontribusi pada tugas yang sulit ini.
Mustahil untuk membayangkan Apache Hive tanpa Apache Hadoop, sehingga kemungkinan unicorn dari PVS-Studio juga akan terlihat di sana. Tapi itu saja untuk hari ini, tetapi untuk sekarang
unduh penganalisa dan periksa proyek Anda sendiri.

Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Maxim Stefanov.
PVS-Studio Mengunjungi Apache Hive .