java项目中出现的bug_2019年在Java项目中发现的十大bug

java项目中出现的bug

2019 is drawing to an end, and the PVS-Studio team is looking back at the accomplishments of this year. In the beginning of 2019, we enhanced our analyzer's diagnostic capabilities by adding Java support, which enabled us to check and review Java projects as well. We have found lots of bugs over this year, and here's our Top 10 bugs found in Java projects.

2019年即将结束,PVS-Studio团队正在回顾今年的成就。 在2019年初,我们通过添加Java支持来增强分析仪的诊断能力,这也使我们能够检查和审查Java项目。 在今年,我们发现了许多错误,这是在Java项目中发现的十大错误。

10号:签名字节 (No. 10: Signed byte)

Source: Analysis of the Apache Dubbo RPC Framework by the PVS-Studio Static Code Analyzer

资料来源: PVS-Studio静态代码分析器对Apache Dubbo RPC框架的分析

V6007 Expression 'endKey[i] < 0xff' is always true. OptionUtil.java(32) 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) {                                           // <=
      endKey[i] = (byte) (endKey[i] + 1);
      return ByteSequence.from(Arrays.copyOf(endKey, i + 1));
    }
  }
  return ByteSequence.from(NO_PREFIX_END);
}

Many programmers believe that the byte type is unsigned. This is indeed the case in many programming languages. For example, this is true for C#. But that's not so in Java.

许多程序员认为字节类型是无符号的。 在许多编程语言中确实确实如此。 例如,对于C#,这是正确的。 但这在Java中并非如此。

In the endKey[i] < 0xff condition, a variable of type byte is compared with the number 255(0xff) represented in hexadecimal form. The developer probably forgot that the range of the Java byte type is [-128, 127]. This condition will be always true, and the for loop will be always processing only the last element of the endKey array.

endKey [i] <0xff条件下,将字节类型的变量与以十六进制形式表示的数字255(0xff)进行比较。 开发人员可能忘记了Java 字节类型的范围是[-128,127]。 此条件将始终为true,并且for循环将始终仅处理endKey数组的最后一个元素。

第9名:二合一 (No. 9: Two in one)

Source: PVS-Studio for Java hits the road. Next stop is Elasticsearch

资料来源: PVS-Studio for Java上路了。 下一站是Elasticsearch

V6007 Expression '(int)x < 0' is always false. BCrypt.java(429) V6007表达式'(int)x <0'始终为false。 BCrypt.java(429) V6025 Possibly index '(int) x' is out of bounds. BCrypt.java(431) 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];
}

Discount! One method – two bugs! The first has to do with the char type: since it's unsigned in Java, the (int)x < 0 condition will be always false. The second bug is the ordinary indexing beyond the bounds of the index_64 array when (int)x == index_64.length. This happens because of the condition (int)x > index_64.length. The bug can be fixed by changing the '>' operator to '>=': (int)x >= index_64.length.

折扣! 一种方法–两个错误! 第一个与char类型有关:由于在Java中为unsigned,因此(int)x <0条件始终为false。 第二个错误是当(int)x == index_64.length时,超出index_64数组范围的普通索引。 发生这种情况是由于条件(int)x> index_64.length 。 可以通过将'>'运算符更改为'> ='来修复该错误: (int)x> = index_64.length

第8条:解决方案及其含义 (No. 8: A solution and its implications)

Source: Analyzing the Code of CUBA Platform with PVS-Studio

资料来源: 使用PVS-Studio分析CUBA平台的代码

V6007 Expression 'previousMenuItemFlatIndex >= 0' is always true. CubaSideMenuWidget.java(328) 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) {                          // <=
      return menuItemWidgets.get(previousMenuItemFlatIndex);
  }
  return null;
}

The author of the findNextMenuItem method wanted to get rid of the value -1 returned by the indexOf method when the menuItemWidgets list doesn't contain currentItem. To do that, the programmer is adding 1 to the result of indexOf (the menuItemFlatIndex variable) and writing the resulting value to the variable previousMenuItemFlatIndex, which is then used in the method. The adding of -1 proves to be a poor solution because it leads to several errors at once:

menuItemWidgets列表不包含currentItem时, findNextMenuItem方法的作者希望摆脱indexOf方法返回的值-1。 为此,程序员要在indexOf的结果( menuItemFlatIndex变量)的结果上加1,然后将结果值写入变量previousMenuItemFlatIndex ,然后在该方法中使用该变量。 加上-1被证明是一个不好的解决方案,因为它会立即导致多个错误:

  • The return null statement will never be executed because the previousMenuItemFlatIndex >= 0 expression will be always true; therefore, the findNextMenuItem method will always return from within the if statement;

    return null语句将永远不会执行,因为previousMenuItemFlatIndex> = 0表达式将始终为true; 因此, findNextMenuItem方法将始终从if语句中返回;

  • an IndexOutOfBoundsException will be raised when the menuItemWidgets list has become empty since the program will attempt to access the first element of the empty list;

    menuItemWidgets列表变为空时,将引发IndexOutOfBoundsException,因为程序将尝试访问空列表的第一个元素;

  • an IndexOutOfBoundsException will be raised when the currentItem argument has become the last element of the menuItemWidget list.

    currentItem参数已成为menuItemWidget列表的最后一个元素时,将引发IndexOutOfBoundsException

No. 7:一无所获 (No. 7: Making a file out of nothing)

Source: Huawei Cloud: It's Cloudy in PVS-Studio Today

资料来源: 华为云:如今PVS-Studio多云

V6008 Potential null dereference of 'dataTmpFile'. CacheManager.java(91) V6008可能取消对“ dataTmpFile”的引用。 CacheManager.java(91)
@Override
public void putToCache(PutRecordsRequest putRecordsRequest)
{
  .... 
  if (dataTmpFile == null || !dataTmpFile.exists())
  {
    try
    {
      dataTmpFile.createNewFile();  // <=
    }
    catch (IOException e)
    {
      LOGGER.error("Failed to create cache tmp file, return.", e);
      return;
    }
  }
  ....
}

When writing the putToCache method, the programmer made a typo in the condition dataTmpFile == null || !dataTmpFile.exists() before creating a new file dataTmpFile.createNewFile(): they wrote the '==' operator instead of '!='. This typo will lead to throwing a NullPointerException when calling the createNewFile method. This is what the condition looks like with the typo fixed:

编写putToCache方法时,程序员在条件dataTmpFile == null ||中输入了错误。 !dataTmpFile.exists()创建一个新的文件dataTmpFile.createNewFile()之前:他们写的'=='操盘手'!='。 键入createNewFile方法时,此错字将导致引发NullPointerException 。 这是固定错字的情况:

if (dataTmpFile != null || !dataTmpFile.exists())

You might think, «Well, okay, we can relax now.» Not yet!

您可能会想,“好吧,我们现在可以放松一下。” 还没!

Now that one bug has been fixed, another one showed up. A NullPointerException may be thrown when calling the dataTmpFile.exists() method. To fix this, we need to replace the '||' operator with '&&'. This is the final version of the condition, after all the fixes:

既然已经修复了一个错误,则出现了另一个错误。 调用dataTmpFile.exists()方法时,可能会引发NullPointerException 。 要解决此问题,我们需要替换“ ||” 运算符“ &&”。 经过所有修复之后,这是该条件的最终版本:

if (dataTmpFile != null && !dataTmpFile.exists())

第6条:非常奇怪的逻辑错误 (No. 6: A very weird logic error)

Source: PVS-Studio for Java

资料来源: PVS-Studio for Java

V6007 [CWE-570] Expression '«0».equals(text)' is always false. ConvertIntegerToDecimalPredicate.java 46 V6007 [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)) {// <=
    return false;
  }
  return text.charAt(0) == '0';
}

The interesting thing about this method is that it contains an obvious logic error. If the satisfiedBy method doesn't return after the first if statement, it will mean that the text string is at least two characters long. This also means that the very first check «0».equals(text) in the next if statement is meaningless. What the programmer actually meant by that remains a mystery.

关于此方法的有趣之处在于它包含一个明显的逻辑错误。 如果满意的方法在第一个if语句之后没有返回,则表示文本字符串至少有两个字符长。 这也意味着下一个if语句中的第一个检查«0».equals(text)毫无意义。 程序员实际上的含义仍然是个谜。

5号:真是个转折! (No. 5: What a twist!)

Source: PVS-Studio Visits Apache Hive

资料来源: PVS-Studio访问Apache Hive

V6034 Shift by the value of 'bitShiftsInWord — 1' could be inconsistent with the size of type: 'bitShiftsInWord — 1' = [-1… 30]. UnsignedInt128.java(1791) 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));  // <=
  ....
}

With the input arguments wordShifts = 3 and bitShiftsInWord = 0, the roundCarryMask variable, which stores the result of the bitwise shift (1 << (bitShiftsInWord — 1)), will become a negative number. The developer didn't probably expect that.

在输入参数wordShifts = 3bitShiftsInWord = 0情况下 ,存储按位移位结果(1 <<((bitShiftsInWord_1))roundCarryMask变量将变为负数。 开发人员可能没想到这一点。

No. 4:我们能看到例外吗? (No. 4: Can we see the exceptions please?)

Source: PVS-Studio Visits Apache Hive

资料来源: PVS-Studio访问Apache Hive

V6051 The use of the 'return' statement in the 'finally' block can lead to the loss of unhandled exceptions. ObjectStore.java(9080) 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();
    }
  }
}

The declaration of the getMPartitionColumnStatistics method is misleading as it says it could throw an exception. In reality, whatever exception is generated in the try block, the committed variable will be always false, so the return statement in the finally block will return a value, while all the thrown exceptions will be lost, unable to be processed outside the method. So, none of the exceptions thrown in this method will be able to leave it.

getMPartitionColumnStatistics方法的声明具有误导性,因为它表示可能会引发异常。 实际上,无论try块中生成了什么异常, 提交的变量将始终为false ,因此, finally块中的return语句将返回一个值,而所有引发的异常将丢失,无法在方法外进行处理。 因此,此方法中抛出的任何异常都不能离开它。

No. 3:坐立不安,或尝试换个新口罩 (No. 3: Hocus-pocus, or trying to get a new mask)

Source: PVS-Studio Visits Apache Hive

资料来源: PVS-Studio访问Apache Hive

V6034 Shift by the value of 'j' could be inconsistent with the size of type: 'j' = [0...63]. IoTrace.java(272) 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);                // <=
    }
    ....
  }
  ....
}

This bug, too, has to do with a bitwise shift, but not only that. The j variable is used as a counter over the range [0...63] in the inner for loop. This counter participates in a bitwise shift 1 << j. Everything seems OK, but here's where the integer literal '1' of type int (a 32-bit value) comes into play. Because of it, the bitwise shift will start returning the previously returned values when the j variable's value has exceeded 31. If this behavior is not what the programmer wanted, the value 1 must be represented as long: 1L << j or (long)1 << j.

该错误也与按位移位有关,但不仅限于此。 j变量在内部for循环中的[0 ... 63]范围内用作计数器。 该计数器参与按位移位1 << j 。 一切似乎都很好,但是这里是int类型(32位值)的整数文字“ 1”起作用的地方。 因此,当j变量的值超过31时,按位移位将开始返回先前返回的值。如果此行为不是程序员想要的,则值1必须表示为long1L << j(long) 1 << j

第二号:初始化顺序 (No. 2: Initialization order)

Source: Huawei Cloud: It's Cloudy in PVS-Studio Today

资料来源: 华为云:如今PVS-Studio多云

V6050 Class initialization cycle is present. Initialization of 'INSTANCE' appears before the initialization of 'LOG'. UntrustedSSL.java(32), UntrustedSSL.java(59), UntrustedSSL.java(33) 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);           // <=
    }
  }
}

The order in which fields are declared in a class makes a difference since they are initialized in the same order they are declared. Forgetting this fact leads to elusive bugs like the one above.

在类中声明字段的顺序有所不同,因为它们的初始化顺序与声明的顺序相同。 忘记这一事实会导致难以捉摸的错误,如上面的错误。

The analyzer points out that the static field LOG is dereferenced in the constructor at the moment when it is initialized to the value null, which leads to throwing a series of exceptions NullPointerException -> ExceptionInInitializerError.

分析器指出,静态字段LOG在初始化为值时会在构造函数中取消引用,这将导致引发一系列异常NullPointerException- > ExceptionInInitializerError

But why does the static field LOG have the value null at the moment of calling the constructor?

但是,为什么在调用构造函数时静态字段LOG的值为null

The ExceptionInInitializerError is the clue. The constructor is initializing the static field INSTANCE, which is declared before the LOG field. That's why the LOG field is not initialized yet when the constructor is called. To make this code work properly, the LOG field must be initialized before the call.

ExceptionInInitializerError是线索。 构造函数正在初始化静态字段INSTANCE ,该字段在LOG字段之前声明。 这就是为什么在调用构造函数时尚未初始化LOG字段的原因。 为了使此代码正常工作,必须在调用之前初始化LOG字段。

第一:面向复制粘贴的编程 (First: copy-paste oriented programming)

Source: Apache Hadoop Code Quality: Production VS Test

来源: Apache Hadoop代码质量:生产VS测试

V6072 Two similar code fragments were found. Perhaps, this is a typo and 'localFiles' variable should be used instead of 'localArchives'. LocalDistributedCacheManager.java(183), LocalDistributedCacheManager.java(178), LocalDistributedCacheManager.java(176), LocalDistributedCacheManager.java(181) 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 {
  ....
  // Update the configuration object with localized data.
  if (!localArchives.isEmpty()) {
    conf.set(MRJobConfig.CACHE_LOCALARCHIVES, StringUtils
        .arrayToString(localArchives.toArray(new String[localArchives  // <=
            .size()])));
  }
  if (!localFiles.isEmpty()) {
    conf.set(MRJobConfig.CACHE_LOCALFILES, StringUtils
        .arrayToString(localFiles.toArray(new String[localArchives     // <=
            .size()])));
  }
  ....
}

The first place on our Top 10 list is awarded to copy-paste, or, rather, a bug that stems from the careless use of this technique. The second if statement looks very much like a copy of the first, with some of the variables modified:

我们在“十佳”列表中的第一名是复制粘贴,或者是由于不小心使用此技术而导致的错误。 第二个if语句看起来很像第一个if语句的副本,但其中一些变量已修改:

  • localArchives was changed to localFiles;

    localArchives已更改为localFiles ;

  • MRJobConfig.CACHE_LOCALARCHIVES was changed to MRJobConfig.CACHE_LOCALFILES.

    MRJobConfig.CACHE_LOCALARCHIVES已更改为MRJobConfig.CACHE_LOCALFILES

But the programmer managed to make a mistake even in this simple operation: the if statement in the line pointed out by the analyzer is still using the localArchives variable instead of the apparently intended localFiles variable.

但是程序员即使在此简单操作中也设法犯了一个错误:分析器指出的行中的if语句仍在使用localArchives变量,而不是表面上预期的localFiles变量。

结论 (Conclusion)

Fixing bugs found at the later development stages or after the release takes a good deal of resources. The static analyzer PVS-Studio allows you to detect bugs at the coding stage, thus making it much easier and cheaper. Many companies have already made their developers' lives easier by starting to use the analyzer on a regular basis. If you want to really enjoy your job, try PVS-Studio.

修复在后期开发阶段或发行后发现的错误需要大量资源。 静态分析仪PVS-Studio允许您在编码阶段检测错误,从而使其更容易,更便宜。 许多公司已经开始定期使用分析仪,从而使开发人员的生活更轻松。 如果您想真正享受工作,请尝试PVS-Studio

We are not going to stop at that and are planning to keep improving and enhancing our tool. Stay tuned for new diagnostics and articles with even more interesting bugs in the next year.

我们不会止步于此,并计划继续改进和增强我们的工具。 敬请关注明年的新诊断和文章,以及更多有趣的错误。

I see you like adventures! First, you defeated top 10 bugs in С# project in 2019 and now you coped with Java as well! Welcome to the next level — the article about best errors in C++ projects in 2019.

我看到你喜欢冒险! 首先,您在2019年击败了С#项目中的前10个错误 ,现在您也应对Java! 欢迎来到下一个级别—有关2019年C ++项目中最佳错误的文章。

翻译自: https://habr.com/en/company/pvs-studio/blog/481184/

java项目中出现的bug

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值