PVS-Studio mengunjungi Apache Hive

Gambar 1

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.")) { // <= continue; } else if (key.compareToIgnoreCase(Conf.CONN_DEFAULT) == 0) { .... } else if (key.startsWith("hplsql.conn.init.")) { .... } else if (key.startsWith(Conf.CONN_CONVERT)) { .... } else if (key.startsWith("hplsql.conn.")) { .... } else if (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")) // <= { .... } else if (colOrScalar1.equals("Col") && colOrScalar1.equals("Scalar")) { .... } else if (colOrScalar1.equals("Scalar") && 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) { // <= return lockOneBuffer(buffer, doNotifyPolicy); } LlapAllocatorBuffer[] bufferArray = buffers.getMultipleLlapBuffers(); for (int i = 0; i < bufferArray.length; ++i) { if (lockOneBuffer(bufferArray[i], doNotifyPolicy)) continue; for (int j = 0; j < i; ++j) { unlockSingleBuffer(buffer, true); // <= } .... } .... } .... private void unlockSingleBuffer(LlapAllocatorBuffer buffer, ....) { boolean isLastDecref = (buffer.decRef() == 0); // <= if (isLastDecref) { .... } } 

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; // check this because "123 << 32" will be 123. final boolean noRestore = bitShiftsInWord == 0; final int roundCarryNoRestoreMask = 1 << 31; final int roundCarryMask = (1 << (bitShiftsInWord - 1)); // <= .... } 

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 {}-{} ;", // <= stat.size(), sig.size() )); .... } .... if (e.getAll(OperatorStats.IncorrectRuntimeStatsMarker.class).size() > 0) { LOG.debug( "Ignoring {}, marked with OperatorStats.IncorrectRuntimeStatsMarker", sig.get(0) ); continue; } .... } 

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 { .... /*some actions*/ 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) { /*some comments*/ return (compareUnsignedLong(dividend, divisor)) < 0 ? 0L : 1L; } if (dividend >= 0) { // Both inputs non-negative return dividend / divisor; // <= } else { .... } } 

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()) { // reached end eg TS operator return null; } .... } 

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); // <= break; case DATE: outVCA = new VectorLongColumnAssign() { .... } .init(...., (LongColumnVector) destCol); break; case INTERVAL_YEAR_MONTH: outVCA = new VectorLongColumnAssign() { .... }.init(...., (LongColumnVector) destCol); break; case INTERVAL_DAY_TIME: outVCA = new VectorIntervalDayTimeColumnAssign() { .... }.init(...., (IntervalDayTimeColumnVector) destCol);// <= break; default: throw new HiveException(....); } } else if (destCol instanceof DoubleColumnVector) { .... } .... else { throw new HiveException(....); } return outVCA; } 

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()) { // <= return false; } Var var = (Var)obj; if (this == var) { return true; } else if (var == null || // <= var.value == null || this.value == null) { return false; } .... } 

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 .

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


All Articles