PVS-Studio访问Apache Hive

图1

在过去的十年中,开源运动一直是IT行业发展的关键因素之一,也是其中的重要组成部分。 定量指标的增长不仅增强了开源的作用和地位,而且其定性在整个IT市场中的定位也发生了变化。 不用袖手旁观,PVS-Studio的勇敢团队会积极地巩固开源项目的位置,在厚厚的代码库中发现隐藏的错误,并为这些项目提供免费许可证。 本文也不例外! 今天我们将讨论Apache Hive! 报告已收到-值得一看!

关于PVS-Studio


PVS-Studio静态代码分析器在IT市场中已经存在了10多年,它是一种多功能且易于实施的软件解决方案。 目前,分析仪支持C,C ++,C#,Java语言,并且可以在Windows,Linux和macOS平台上运行。

PVS-Studio是一种付费的B2B解决方案,已被各公司的众多团队使用。 如果要查看分析仪的功能,请下载分发套件并在此处索取试用密钥。

如果您是开源怪胎,或者例如您是学生,则可以使用PVS-Studio的免费许可选项之一

关于Apache Hive


近年来,数据量正在高速增长。 标准数据库再也无法以这样的速度保持可操作性,而信息量的增长正是术语“大数据”及其相关事物的出现(处理,存储以及随之而来的大量数据)。

当前, Apache Hadoop被认为是大数据的基本技术之一。 该技术的主要目标是存储,处理和管理大量数据。 该框架的主要组件是Hadoop Common, HDFSHadoop MapReduceHadoop YARN 。 随着时间的流逝,围绕Hadoop形成了一个完整的相关项目和技术生态系统,其中许多项目最初是作为项目的一部分开发的,后来成为独立的。 这些项目之一是Apache Hive

Apache Hive是一个分布式数据仓库。 它管理存储在HDFS中的数据,并提供基于SQL的查询语言(HiveQL)来处理这些数据。 要详细了解此项目,可以在此处学习信息。

关于分析


分析步骤的顺序非常简单,不需要很多时间:

  • GitHub上获得Apache Hive;
  • 我按照说明启动了Java分析器并开始了分析。
  • 我收到了分析器报告,对其进行了分析并重点介绍了一些有趣的案例。

分析结果:对6500多个文件发出了1456个高和中置信度警告(分别为602和854)。

并非所有警告都是错误。 这是正常情况,在定期使用分析仪之前,需要对其进行配置。 然后,我们可以预期误报的百分比会非常低( 例如 )。

在警告中,未考虑每个测试文件的407警告(177高和230中)。 没有考虑诊断规则V6022 (很难用不熟悉的代码将错误的情况与正确的情况分开),该规则有多达482个警告。 也未考虑带有179个警告的V6021

最后,尽管如此,仍然有足够数量的警告。 而且由于我没有配置分析仪,因此其中还有一些误报。 在文章中描述大量警告是没有意义的:)。 考虑一下引起我注意并看起来很有趣的东西。

预定条件


诊断规则V6007是所有其余分析器警告中的记录保存者。 刚刚超过200条警告! 有些,例如无害,有些是可疑的,而有些则完全是真正的错误! 让我们看看其中的一些。

V6007表达式'key.startsWith(“ hplsql。”)'始终为true。 执行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.")) { // <= .... } } 

一个相当长的if-else-if构造! 分析器最后发誓if(key.startsWith(“ hplsql。”)) ,如果程序到达此代码段,则表明其真相。 确实,如果您查看if-else-if构造的最开始,则检查已经完成。 并且如果我们的行不是以子字符串“ hplsql”开头。 ,那么代码的执行立即跳到了下一个迭代。

V6007表达式'columnNameProperty.length()== 0'始终为false。 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>() : ....; .... } } } .... } 

columnNameProperty的字符串长度与零进行比较将始终返回false 。 这是因为我们的比较正在测试中!Strings.isNullOrEmpty(columnNameProperty) 。 如果程序的状态达到了我们所讨论的条件,则columnNameProperty行将保证为非零且不为空。

对于columnTypeProperty行也是如此。 以下警告线:

  • V6007表达式'columnTypeProperty.length()== 0'始终为false。 OrcRecordUpdater.java(239)

V6007表达式'colOrScalar1.equals(“ Column”)'始终为false。 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")) { .... } 

}

这是一个琐碎的复制粘贴。 原来, colOrScalar1行应同时等于不同的值,这是不可能的。 显然,应在左侧检查变量colOrScalar1 ,在右侧检查colOrScalar2

以下各行中的更多类似警告:

  • V6007表达式'colOrScalar1.equals(“ Scalar”)'始终为false。 GenVectorCode.java(3475)
  • V6007表达式'colOrScalar1.equals(“ Column”)'始终为false。 GenVectorCode.java(3486)

结果,if-else-if构造中的任何操作都不会执行。

V6007的其他一些警告:

  • V6007表达式'characters == null'始终为false。 RandomTypeUtil.java(43)
  • V6007表达式'writeIdHwm> 0'始终为false。 TxnHandler.java(1603)
  • V6007表达式'fields.equals(“ *”)'始终为true。 Server.java(983)
  • V6007表达式“ currentGroups!= Null”始终为true。 GenericUDFCurrentGroups.java(90)
  • V6007表达式'this.wh == null'始终为false。 新的返回非空引用。 StorageBasedAuthorizationProvider.java(93),StorageBasedAuthorizationProvider.java(92)
  • 等等...

NPE


V6008可能会取消引用“ 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(); ..... } } 

抓住了零目标,保证并继续工作。 这导致以下事实:在检查对象后,将发生零对象取消引用的情况。 悲伤!

如果引用为空,则很可能应该立即退出该函数或引发一些特殊异常。

V6008函数“ unlockSingleBuffer”中对“ buffer”的空引用。 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) { .... } } 

再次是潜在的NPE。 如果程序到达unlockSingleBuffer方法,则缓冲区对象将为零。 假设发生了! 让我们看一下unlockSingleBuffer方法,并在第一行中立即看到我们的对象已被取消引用。 我们到了!

没有跟随转变


V6034按“ bitShiftsInWord-1”的值进行移位可能与以下类型的大小不一致:“ 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)); // <= .... } 

可能的偏移量为-1。 例如,如果wordShifts == 3bitShiftsInWord == 0进入相关方法的输入,则在指定的行中将出现1 << -1。 这有计划吗?

V6034移位'j'的值可能与类型的大小不一致:'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); // <= } .... } .... } 

在指定的行中,变量j可以采用[0 ... 63]范围内的值。 因此,循环中val值的计算可能不会像开发人员预期的那样发生。 在表达式(1 << j)中,该单位的类型为int ,并且将其从32或更大范围移开,我们超出了允许范围。 要纠正这种情况,您必须写出((long)1 << j)

热衷于伐木


V6046格式错误。 预计会有不同数量的格式项。 未使用的参数: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; } .... } 

通过String.format()格式化字符串时开发人员混淆了语法。 底线:传递的参数未进入结果字符串。 我可以假设开发人员在上一个任务中是从日志借用语法的地方开始进行日志记录的。

偷了例外


V6051在“ finally”块中使用“ return”语句可能会导致丢失未处理的异常。 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(); } } } 

从finally块返回一些东西是很不好的做法,在这个示例中,我们将看到这一点。

try块中,形成请求并访问存储。 提交的变量默认为false,并且仅在try块中所有成功完成的操作之后才更改其状态。 这意味着,如果发生异常,我们的变量将始终为false捕获块捕获到异常,对其进行了更正并进一步抛出。 并且,当到了finally块的时间到了执行将进入一个条件,我们从该条件向外返回空列表。 此退货给我们带来了什么费用? 但是值得一提的是,所有捕获的异常都不会以适当的方式抛出和处理。 方法签名中指示的所有那些异常将永远不会被扔掉,而只会令人困惑。

类似的警告:

  • V6051在“ finally”块中使用“ return”语句可能会导致丢失未处理的异常。 ObjectStore.java(808)

...其他


V6009函数'compareTo'收到一个奇数参数。 对象'o2.getWorkerIdentity()'用作其自身方法的参数。 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()); // <= } }); .... } 

复制粘贴,粗心,仓促和许多其他原因导致此愚蠢的错误。 在检查开源项目时,这种错误非常普遍。 甚至有整篇文章

V6020除以零。 “除数”分母值的范围包括零。 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 { .... } } 

这里的一切都很简单。 多项检查并未警告不要除以0。

更多警告:

  • V6020 Mod归零。 “除数”分母值的范围包括零。 SqlMathUtil.java(309)
  • V6020除以零。 “除数”分母值的范围包括零。 SqlMathUtil.java(276)
  • V6020除以零。 “除数”分母值的范围包括零。 SqlMathUtil.java(312)

V6030该方法位于“ |”右侧 无论左操作数的值如何,都会调用操作符。 也许最好使用“ ||”。 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; } .... } 

代替逻辑运算符|| 写了一个按位运算符|。 这意味着无论左侧结果如何,都将执行右侧。 在父母== null的情况下,这样的错字将立即导致下一个逻辑子表达式中的NPE。

V6042已检查表达式是否与类型'A'兼容,但将其强制转换为类型'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; } 

问题的类是LongColumnVector扩展ColumnVectorTimestampColumnVector扩展ColumnVector 。 检查我们的destCol对象是否具有LongColumnVector所有权可以清楚地告诉我们,此类的对象将在条件语句中。 尽管如此,我们还是强制转换为TimestampColumnVector ! 如您所见,这些类是不同的,不包括它们的公共父类。 结果是ClassCastException

关于将类型转换为IntervalDayTimeColumnVector的说明可以全部相同:

  • V6042已检查表达式是否与类型'A'兼容,但将其强制转换为类型'B'。 VectorColumnAssignFactory.java(390)

V6060在对null进行验证之前,已使用'var'参考。 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; } .... } 

解引用后,将var对象与null进行了奇怪的比较。 在这种情况下, varobj是同一个对象( var =(Var)obj )。 检查null意味着可能会出现null对象。 在equals(null)的情况下我们立即获得第一行NPE而不是预期的false 。 las,有支票,但没有。

在检查发生之前,使用该对象的类似可疑时刻:

  • V6060在验证是否为空之前,已使用“值”参考。 ParquetRecordReaderWrapper.java(168),ParquetRecordReaderWrapper.java(166)
  • V6060在对null进行验证之前,已使用'defaultConstraintCols'引用。 HiveMetaStore.java(2539),HiveMetaStore.java(2530)
  • V6060在对null进行验证之前,已使用'projIndxLst'引用。 RelOptHiveTable.java(683),RelOptHiveTable.java(682)
  • V6060在对null进行验证之前使用了“ oldp”参考。 ObjectStore.java(4343),ObjectStore.java(4339)
  • 等等...

结论


任何对大数据感兴趣的人都不会错过Apache Hive的意义。 该项目非常受欢迎,而且规模很大,其组成中有6500多个源代码文件(* .java)。 该代码已经由许多开发人员编写了很多年,因此,静态分析器可以找到一些东西。 这再次证实了静态分析在大中型项目的开发中非常重要和有用!

注意事项 这样的一次性检查演示了静态代码分析器的功能,但是使用它是完全错误的方式。 这个想法在这里这里都有更详细的介绍。 定期使用分析!

检查蜂巢时,检测到足够数量的缺陷和可疑时刻。 如果本文引起了Apache Hive开发团队的注意,我们将很高兴为这项艰巨的任务做出贡献。

无法想象没有Apache Hadoop的Apache Hive,因此PVS-Studio的独角兽也可能会出现在那。 但这就是今天的全部,但现在下载分析器并检查您自己的项目。



如果您想与讲英语的人分享这篇文章,请使用以下链接:Maxim Stefanov。 PVS-Studio访问Apache Hive

Source: https://habr.com/ru/post/zh-CN463759/


All Articles