2019年即将结束,PVS-Studio团队正在总结即将过去的一年的结果。 在2019年初,我们通过支持Java语言扩展了分析仪的功能。 因此,关于检查打开的项目的出版物列表中充斥着Java项目的评论。 在这一年中发现了很多错误,我们决定准备其中最有趣的10个错误。
第十名:标志性字节
来源:
静态分析器PVS-Studio分析RPC框架Apache Dubbo的源代码V6007表达式'endKey [i] <0xff'始终为true。 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) {
许多程序员认为,名为
byte的类型将是无符号的。 确实,经常是使用不同语言的情况。 例如,在C#中,
字节类型是无符号的。 在Java中,情况并非如此。
在条件
endKey [i] <0xff中,该方法
的作者将
字节类型的变量与以十六进制表示形式表示的数字255(0xff)进行比较。 显然,在编写该方法时,开发人员忘记了Java中
byte类型的值的范围是[-128,127]。 此条件始终为true,因此
for循环将始终仅处理
endKey数组的最后一个元素。
第九名:二合一
来源:
PVS-Studio for Java已发送到该路径。 下一站是ElasticsearchV6007表达式'(int)x <0'始终为false。 BCrypt.java(429)
V6025可能索引'(int)x'超出范围。 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]; }
今天我们有特价! 一种方法一次出现两个错误。 第一个错误的原因是
char类型,该类型在Java中是无符号的,这就是为什么条件
(int)x <0始终为false。 第二个错误是当
(int)x == index_64.length时
,banal超出了index_64数组的
范围 。 由于条件
(int)x> index_64.length,这种情况是可能的。 为了摆脱数组的界限,必须将条件'>'替换为'> ='。 正确的条件是:
(int)x> = index_64.length 。
第八名:决策及其后果
来源:
使用PVS-Studio的CUBA平台代码分析V6007表达式'previousMenuItemFlatIndex> = 0'始终为true。 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) {
如果
menuItemWidgets列表
不包含
currentItem ,则
findNextMenuItem方法的作者希望摆脱
indexOf方法返回的-1。 为此,他将一个值添加到
indexOf结果(
menuItemFlatIndex变量)中,并将结果值存储在
previousMenuItemFlatIndex变量中,该变量将在方法中进一步使用。 解决-1问题的方法失败,因为它一次导致多个错误:
- return null代码将永远不会执行,因为表达式previousMenuItemFlatIndex> = 0始终为true,这意味着findNextMenuItem方法的返回将始终在if内发生;
- 当menuItemWidgets列表为空时,将抛出IndexOutOfBoundsException ,因为将访问空列表的第一个元素;
- 当currentItem参数是menuItemWidget列表中的最后一个参数时,将发生IndexOutOfBoundsException异常。
第七名:从零开始创建文件
资料来源:
华为云:今天PVS-Studio多云V6008可能会取消引用“ dataTmpFile”。 CacheManager.java(91)
@Override public void putToCache(PutRecordsRequest putRecordsRequest) { .... if (dataTmpFile == null || !dataTmpFile.exists()) { try { dataTmpFile.createNewFile();
编写
putToCache方法时
,程序员在条件
dataTmpFile == null ||中输入了错误。
!dataTmpFile.exists()在创建新的
dataTmpFile.createNewFile()文件之前
。 一个错字是使用运算符'=='而不是'!='。 调用
createNewFile方法时,此错字将引发
NullPointerException 。 纠正错字后的情况如下:
if (dataTmpFile != null || !dataTmpFile.exists())
“发现了错误,已更正。 您可以放松,“您会想。 但是无论如何!
更正了一个错误之后,我们发现了另一个错误。 现在,调用
dataTmpFile.exists()时可能会发生
NullPointerException 。 现在,要摆脱异常,有必要在以下情况下替换'||'运算符 在“ &&”上。 所有错误消失的条件如下:
if (dataTmpFile != null && !dataTmpFile.exists())
第六名:一个非常奇怪的逻辑错误
资料来源:
PVS-Studio for JavaV6007 [CWE-570]表达式'“ 0” .equals(text)'始终为false。 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)) {
此方法很有趣,因为它包含明显的逻辑错误。 如果
满意的方法在第一个
if之后没有返回值,则已知
文本字符串至少包含两个字符。 因此,下一个
if中的第一个校验
“ 0” .equals(文本)是没有意义的。 开发人员真正的意思仍然是个谜。
第五名:这是转身!
资料来源:
PVS-Studio访问Apache HiveV6034按'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;
在输入参数
wordShifts = 3和
bitShiftsInWord = 0的情况下 ,变量
roundCarryMask用来存储位移的结果
(1 <<(bitShiftsInWord-1))将变为负数。 也许开发人员没有想到这种行为。
第四名:例外会散散步吗?
资料来源:
PVS-Studio访问Apache HiveV6051在“ finally”块中使用“ return”语句可能会导致丢失未处理的异常。 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(); } } }
getMPartitionColumnStatistics方法的声明对我们说谎,说它可能引发异常。 当try中发生任何异常时,
提交的变量仍为
false ,因此,在
finally块中,
return语句返回方法中的值,并且所有引发的异常都将丢失并且无法在方法外部进行处理。 因此,此方法中引发的任何异常将永远无法摆脱它。
第三名:我扭曲,扭曲,我想要一个新的面具
资料来源:
PVS-Studio访问Apache HiveV6034移位'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);
另一个错误与按位转换有关,但这次不仅他参与了此案。 在内部
for循环中,变量
j [0 ... 63]用作循环计数器。 此计数器涉及
1 << j的位移。 没有任何预兆,但是
int类型的整数文字“ 1”(32位值)在这里起作用。 因此,在
j大于31之后,将开始重复移位的结果。如果所描述的行为不理想,则必须将单位表示为
long ,例如
1L << j或
(long)1 << << j 。
第二名:初始化顺序
资料来源:
华为云:今天PVS-Studio多云V6050存在类初始化周期。 在初始化“ LOG”之前出现“ INSTANCE”的初始化。 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);
在类中声明字段的顺序很重要,因为按照声明它们的顺序初始化字段。 但是,当他们忘记它时,就会发生细微的错误,就像这样。
分析器指出,将静态
LOG字段初始化为
null时 ,将在构造函数中取消引用,这将导致
NullPointerException- >
ExceptionInInitializerError异常链。
“为什么在构造函数调用时,静态
LOG字段为
null ?”您问。
ExceptionInInitializerError
异常是一个提示。 事实是,此构造函数用于初始化在类中声明的
INSTANCE静态字段,早于
LOG字段。 因此,在构造函数调用时,
LOG字段仍未初始化。 为了使代码正常工作,必须在调用构造函数之前初始化
LOG字段。
第一名:面向复制粘贴的编程
来源:
Apache Hadoop代码质量:生产VS测试V6072找到两个相似的代码片段。 也许这是一个错字,应该使用“ localFiles”变量而不是“ localArchives”。 LocalDistributedCacheManager.java(183),LocalDistributedCacheManager.java(178),LocalDistributedCacheManager.java(176),LocalDistributedCacheManager.java(181)
public synchronized void setup(JobConf conf, JobID jobId) throws IOException { ....
首先是复制粘贴,或者说是由于犯有此罪的人的粗心而引起的错误。 很可能第二个
if由第一个
if的复制粘贴创建并替换了变量:
- localFiles上的 localArchives ;
- MRJobConfig.CACHE_LOCALFILES上的MRJobConfig.CACHE_LOCALARCHIVES 。
但是,即使使用这种简单的操作,也会出错,因为在第二个
分析器的第二个
if行中仍然使用
localArchives变量,尽管很可能暗示了使用
localFiles 。
结论
纠正在开发的后期或项目发布后发现的错误需要大量资源。 PVS-Studio静态分析器简化了编写代码时的错误检测,从而显着减少了用于修复错误的资源。 不断使用分析仪已经简化了许多
公司的开发人员的生活。 如果您想愉快地编程,请尝试使用我们的
分析仪 。
我们的团队不会止步于此,并将继续改进和改进分析仪。 预计明年会有更多有趣的错误的新诊断和文章。
我看着你喜欢冒险! 首先,赢得
了2019年C#项目中的
前10个错误 ,现在Java可以克服了! 欢迎来到有关
C ++项目中2019年最佳错误的文章的下一级别。

如果您想与说英语的读者分享这篇文章,请使用以下链接:Valery Komarov。
2019年在Java项目中发现的十大bug