search for errors in Java code

search for errors in Java code

Are you a Java developer and prefer to work in VS Code? There is good news for you! Now you can write even more reliable code together with the PVS-Studio extension, which helps you find errors in Java projects and more.

Introduction

PVS-Studio Analyzer is a tool for automatically finding potential bugs and security threats in C, C++, C# and Java code. It consists of several components:

  1. cores that perform code analysis;

  2. plug-ins for various IDEs – interfaces that greatly facilitate user interaction with the core and processing of analysis results.

One such plugin is PVS-Studio for VS Code. It recently received a major update and now you can use the extension to run analysis of Java projects.

In honor of this event, I would like to demonstrate to you the principle of working with it, as I already did in one of my previous articles. But now our goal is to find errors not in C#, but in a Java project. Let’s go!

Installing the analyzer

First, you need to install the Java parser core and VS extensions. You can find a small installation guide in one of my previous articles.

It is also important to note that for the analysis of Java projects, the PVS-Studio extension requires VS Code to have standard Java extensions installed: Language Support for Java (TM) by Red Hat and Project Manager for Java.

Note that for the extension to automatically detect the kernel, it should be installed in the default directory for your operating system. You can also install the kernel to an arbitrary directory by specifying its path in the VS Code preferences (File -> Preferences -> Settings -> PVS-Studio: Java Projects Analyzer Path).

JDK version 11-19 is required for the analyzer to work. If you use another one, you can download the required JDK and specify the path to its file in the corresponding setting (File -> Preferences -> Settings -> PVS-Studio: Java For Running Analyzer).

Launching the project analysis

Open the project and wait for its structure to load in the inspector section Java projects:

If your project includes projects or folders located outside the open directory, run the “Java: Reload Projects” command. After that, a list with all included projects should appear. Mark those that should participate in the analysis.

Now you can safely run the analysis. This can be done in different ways depending on whether you want to analyze the entire project, its individual files or folders. To start a full analysis, you can click on the corresponding button in the tab PVS-Studio bottom panel of VS Code.

During the first analysis, you will be prompted to create a file in which you can specify some optional analysis parameters, similar to the parameters of the console version of the analyzer.

Click on the button Edit will open the file in the editor area, while the analysis will not start. When clicking on Continue it will run with no parameters.

Do not be alarmed if the analysis progress hangs at 0 for some time. As a rule, this happens for the following reasons:

  1. Preparation for analysis for expansion. After opening the directory with the project, the extension in the background begins to collect all the necessary information for analysis. If you start it before the process is complete, the launch will be delayed. All subsequent runs of the analysis will be performed faster;

  2. Preparation of the analysis for the analyzer. This stage includes, for example, the evolution of the project, building or updating its model, which can also take some time.

Shortly after launch, you will see the first warnings, which will gradually be displayed in a table.

As soon as the analysis is completed, you can proceed to the most difficult and at the same time interesting part – the examination of the received warnings in order to find errors.

Search for errors in the code

Don’t panic if you’ve received many warnings. Not every one of them hides a real error. All warnings are divided into 3 levels, reflecting the probability that they will turn out to be true:

  • first level (red) – these warnings may not necessarily be the most critical, but they are the most accurate;

  • second level (orange) – often warnings of this level indicate not the simplest and most interesting errors, but it is more difficult to ensure the accuracy of their finding;

  • third level (yellow) – warnings of this level are often controversial or minor. It is recommended to pay attention to them last, but you should not completely ignore them. They may also include a few warnings that indicate errors in your code.

When viewing the triggers, you should not neglect the available functions, such as filtering and sorting by columns.

For example, sorting by column Code will group similar triggers together. It is easier to check several triggers of the same type in a row than if they were located in a random order.

You can also exclude some of them. For example, triggering inside test files. To do this, select the appropriate option in the context menu of the warning:

Or specify the path in the corresponding settings tab (gear button):

After analyzing the Apache Hive project and checking some of the warnings from the report, I was able to find a few potential bugs.

Bit offset error

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);                // <=
    }
    ....
  }
  ....
}

Warning: V6034. Shift by the value of ‘j’ could be inconsistent with the size of type: ‘j’ = [0 .. 63]. IoTrace.java: 264.

Note the expression 1 << jthe result of which is used when calculating the new value of the variable val. This expression performs a bitwise shift of one by J bits to the left. From the definition of the cycle it becomes clear that J can have a value in the range 0 to 64. However, a numeric literal defaults to type intwhich is limited to 32 bits.

Shifting this value by 32 bits or more will loop the shift instead of extending this limit to 64 bits. So, when the unit is shifted by 32 bits, 1 will be obtained again, when shifted by 33 bits – 2, etc.

When the difference between bitwise and logical OR matters

public static Operator<? extends OperatorDesc> findSourceRS(....) 
{
  ....
  List<Operator<? extends OperatorDesc>> parents = ....;
  if (parents == null | parents.isEmpty()) {
    // reached end e.g. TS operator
    return null;
  }
  ....
}

Warning: V6030. Method located to right of the ‘|’ The operator will be called regardless of the left mode value. Perhaps, it is better to use ‘||’. OperatorUtils.java:555.

Here we have a potential catch NullPointerException in expression parents.isEmpty(). The fact is that a bitwise operator was used in the conditional expression OR instead of logical. As a result, the expression parents.isEmpty() will be executed regardless of whether it has parents value null or not.

Careless copy-paste

Problem 1

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"))  // <=
  {
    ....
  }
}

Warning:

  • V6007. Expression ‘colOrScalar1.equals(“Column”)’ is always false. GenVectorCode.java:3543.

  • V6007. Expression ‘colOrScalar1.equals(“Scalar”)’ is always false. GenVectorCode.java:3550.

  • V6007. Expression ‘colOrScalar1.equals(“Column”)’ is always false. GenVectorCode.java:3561.

In this example, each conditional expression has a variable colOrScalar1 is compared twice to different values ​​via a conditional operator &&. As a result, none of the conditions will ever be true.

Problem 2

@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()); // <=
    }
  });
  ....
}

Warning: V6009. Function ‘compareTo’ receives an odd argument. This ‘o2.getWorkerIdentity()’ object is used as an argument to its method. LlapFixedRegistryImpl.java:260.

And in this case, the method compare instead of comparing two different objects, it compares the second object to itself.

Suppressing warnings

After reviewing the warnings and correcting the errors, it is worth suppressing false or irrelevant warnings so that they are not distracted during subsequent analyses.

To do this, you need to select them in the table (using the Ctrl+A key combination, you can select everything at once), open the context menu and select the option Mark as a False Alarm or Add selected messages to deletion file. Please note that you can apply the latter function to all warnings at once or only to the part that has not been filtered, using the lightning button in the upper right corner of the PVS-Studio window.

The first function differs from the second in that it suppresses the warning by adding a comment //-V[Код предупреждения] in the first line of code that indicates the warning. The second function, in turn, stores information about suppressed warnings in a special file.

It is worth noting that it is better to use the function to suppress unnecessary triggers Mark as a False Alarm.

The function of adding warnings in suppress-File is a convenient way to postpone technical debt for later and focus on the quality of new code.

In this case, the standard scenario for using the function is as follows:

  1. A general analysis of the project is performed;

  2. Received warnings are suppressed, after which they will no longer be in the results of subsequent analyses;

  3. Suppressed warnings are later viewed by the developer. To get them back, you need to edit or delete the suppression file (by default called suppress_base.json), which can be found in the folder .PVS-Studio in the solution directory.

This topic is discussed in more detail in the article “How to implement a static code analyzer in a legacy project and not demotivate the team”.

Identify potential compatibility issues between different versions of Java SE

If you plan to migrate your project to a newer version of Java in the future, you should be careful not to commit now to an API that will be removed in the target release. Special diagnostics V6078 will help with this. When working with the VS Code PVS-Studio extension, you can include this diagnostic in the file JavaAnalyzerConfig.jsonc in the folder .PVS-Studio working directory as follows:

The following arguments are noted here:

  1. compatibility – Activation of diagnostics V6078;

  2. source-java – The version number of the JDK on which your project is currently running;

  3. target-java – The version number of the target JDK.

Known issues

When testing on a large Elasticsearch project (more than 18,000 code files), we found that the operation of the Project Manager for Java extension can lead to a strong overuse of RAM, which ultimately leads to a crash of the VS Code window. If you have any ideas on how to optimize the performance of this extension in such cases, we would be happy to receive your advice in the comments.

Conclusion

This concludes our short review. I hope I managed to show you that the PVS-Studio extension for VS Code is a good tool to use in your work.

Clean code and successful projects! See you in the next articles!

If you would like to share this article with an English-speaking audience, please use the translation link: Andrey Moskalev. PVS-Studio Extension for Visual Studio Code: searching for errors in Java code.

Related posts