﻿# Разработка нового статического анализатора: PVS\-Studio Java

Статический анализатор PVS\-Studio известен в мире C, C\+\+ и C\# как инструмент для выявления ошибок и потенциальных уязвимостей\. Однако у нас мало клиентов из финансового сектора, так как выяснилось, что сейчас там востребованы Java и IBM RPG\(\!\)\. Нам же всегда хотелось стать ближе к миру Enterprise, поэтому, после некоторых раздумий, мы приняли решение заняться созданием Java анализатора\.

![0572_Java_analyzer_ru/image1.png](https://import.viva64.com/docx/blog/0572_Java_analyzer_ru/image1.png)

## Введение

Конечно, были и опасения\. Легко занять рынок анализаторов в IBM RPG\. Я вообще не уверен, что существуют достойные инструменты для статического анализа этого языка\. В мире Java дела обстоят совсем иначе\. Уже есть линейка инструментов для статического анализа, и чтобы вырваться вперед, нужно создать действительно мощный и крутой анализатор\.

Тем не менее, в нашей компании был опыт использования нескольких инструментов для статического анализа Java, и мы уверены, что многие вещи мы сможем сделать лучше\. 

Кроме того, у нас была идея, как задействовать всю мощь нашего С\+\+ анализатора в Java анализаторе\. Но обо всём по порядку\.

## Дерево

![0572_Java_analyzer_ru/image2.png](https://import.viva64.com/docx/blog/0572_Java_analyzer_ru/image2.png)

В первую очередь нужно было определиться с тем, каким образом мы будем получать синтаксическое дерево и семантическую модель\. 

Синтаксическое дерево является базовым элементом, вокруг которого строится анализатор\. При выполнении проверок анализатор перемещается по синтаксическому дереву и исследует его отдельные узлы\. Без такого дерева производить серьезный статический анализ практически невозможно\. Например, поиск ошибок с помощью регулярных выражений является [бесперспективным](https://pvs-studio.ru/ru/blog/posts/cpp/0087/)\.

При этом стоит отметить, что одного лишь синтаксического дерева недостаточно\. Анализатору необходима и семантическая информация\. Например, нам нужно знать типы всех элементов дерева, иметь возможность перейти к объявлению переменной и т\.п\.

Мы рассмотрели несколько вариантов получения синтаксического дерева и семантической модели:

* [ANTLR](https://www.antlr.org/) \(с грамматикой для Java\)
* [JavaParser и JavaSymbolSolver](https://javaparser.org/)
* [Eclipse's ASTParser из Eclipse JDT](https://www.eclipse.org/jdt/)
* [Spoon](http://spoon.gforge.inria.fr/)

От идеи использования ANTLR мы отказались практически сразу, так как это неоправданно усложнило бы разработку анализатора \(семантический анализ пришлось бы реализовывать своими силами\)\. В конечном итоге решили остановиться на библиотеке Spoon:

* Является не просто парсером, а целой экосистемой \- предоставляет не только дерево разбора, но и возможности для семантического анализа, например, позволяет получить информацию о типах переменных, перейти к объявлению переменной, получить информацию о родительском классе и так далее\.
* Основывается на Eclipse JDT и умеет компилировать код\.
* Поддерживает последнюю версию Java и постоянно обновляется\.
* Неплохая документация и понятный API\.

Вот пример метамодели, которую предоставляет Spoon, и с которой мы работаем при создании диагностических правил:

![0572_Java_analyzer_ru/image3.png](https://import.viva64.com/docx/blog/0572_Java_analyzer_ru/image3.png)

Эта метамодель соответствует следующему коду:

```cpp
class TestClass
{
  void test(int a, int b)
  {
    int x = (a + b) * 4;
    System.out.println(x);
  }
}
```

Одной из приятных особенностей Spoon является то, что он упрощает синтаксическое дерево \(удаляя и добавляя узлы\) для того, чтобы с ним было проще работать\. При этом гарантируется семантическая эквивалентность упрощенной метамодели исходной\.

Для нас это означает, например, что не нужно больше заботиться о пропуске лишних скобок при обходе дерева\. Помимо этого, каждое выражение помещается в блок, раскрываются импорты, производятся еще некоторые похожие упрощения\.

Например, такой код:

```cpp
for (int i = ((0)); (i < 10); i++)
  if (cond)
    return (((42)));
```

будет представлен следующим образом:

```cpp
for (int i = 0; i < 10; i++)
{
  if (cond)
  {
    return 42;
  }
}
```

На основе синтаксического дерева производится так называемый pattern\-based анализ\. Это поиск ошибок в исходном коде программы по известным шаблонам кода с ошибкой\. В простейшем случае анализатор ищет в дереве места, похожие на ошибку, согласно правилам, описанным в соответствующей диагностике\. Количество подобных шаблонов велико и их сложность может сильно варьироваться\.

Простейшим примером ошибки, обнаруживаемой с помощью pattern\-based анализа, может служить следующий код из проекта jMonkeyEngine:

```cpp
if (p.isConnected()) {
    log.log(Level.FINE, "Connection closed:{0}.", p);
}
else {
    log.log(Level.FINE, "Connection closed:{0}.", p);
}
```

Блоки _then_ и _else_ оператора _if_ полностью совпадают, скорее всего, имеется логическая ошибка\.

Вот еще один подобный пример из проекта Hive:

```cpp
if (obj instanceof Number) {
  // widening conversion
  return ((Number) obj).doubleValue();
} else if (obj instanceof HiveDecimal) {     // <=
  return ((HiveDecimal) obj).doubleValue();
} else if (obj instanceof String) {
  return Double.valueOf(obj.toString());
} else if (obj instanceof Timestamp) {
  return new TimestampWritable((Timestamp)obj).getDouble();
} else if (obj instanceof HiveDecimal) {     // <=
  return ((HiveDecimal) obj).doubleValue();
} else if (obj instanceof BigDecimal) {
  return ((BigDecimal) obj).doubleValue();
}
```

В данном коде находятся два одинаковых условия в последовательности вида _if \(\.\.\.\.\) else  if \(\.\.\.\.\) else if \(\.\.\.\.\)_\. Стоит проверить этот участок кода на наличие логической ошибки, либо убрать дублирующий код\.

## Data\-flow analysis

Помимо синтаксического дерева и семантической модели, анализатору необходим механизм [анализа потока данных](https://pvs-studio.ru/ru/blog/terms/7004/)\.

Анализ потока данных позволяет вычислять допустимые значения переменных и выражений в каждой точке программы и, благодаря этому, находить ошибки\.  Мы называем эти допустимые значения виртуальными значениями\.

Виртуальные значения создаются для переменных, полей классов, параметров методов и прочего при первом упоминании\. Если это присваивание, механизм Data Flow вычисляет виртуальное значение путем анализа выражения стоящего справа, в противном случае в качестве виртуального значения берется весь допустимый диапазон значений для данного типа переменной\. Например:

```cpp
void func(byte x)  // x: [-128..127]
{
  int y = 5;       // y: [5]
  ...
}
```

При каждом изменении значения переменной механизм Data Flow пересчитывает виртуальное значение\. Например:

```cpp
void func()
{
  int x = 5; // x: [5]
  x += 7;    // x: [12]
  ...
}
```

Механизм Data Flow также выполняет обработку управляющих операторов:

```cpp
void func(int x) // x: [-2147483648..2147483647]
{
  if (x > 3)
  {
    // x: [4..2147483647]
    if (x < 10)
    {
      // x: [4..9]
    }
  }
  else
  {
    // x: [-2147483648..3]
  }
  ...
}
```

В данном примере при входе в функцию о диапазоне значений переменной _x_ нет никакой информации, поэтому он устанавливается в соответствии с типом переменной \(от \-2147483648 до 2147483647\)\. Затем первый условный блок накладывает ограничение _x_ \> 3, и диапазоны объединяются\. В результате в _then_ блоке диапазон значений для _x_: от 4 до 2147483647, а в _else_ блоке от \-2147483648 до 3\. Аналогичным образом обрабатывается и второе условие _x_ < 10\.

Кроме того, необходимо иметь возможность производить и чисто символьные вычисления\. Простейший пример:

```cpp
void f1(int a, int b, int c)
{
  a = c;
  b = c;
  if (a == b)   // <= always true
    ....
}
```

Здесь переменной _а_ присваивается значение _c_, переменной _b_ также присваивается значение _c_, после чего _а_ и _b_ сравниваются\. В этом случае, чтобы найти ошибку, достаточно просто запомнить кусок дерева, соответствующий правой части\.

Вот чуть более сложный пример с символьными вычислениями:

```cpp
void f2(int a, int b, int c)
{
  if (a < b)
  {
    if (b < c)
    {
      if (c < a)   // <= always false
        ....
    }
  }
}
```

В таких случаях уже приходится решать систему неравенств в символьном виде\.

Механизм Data Flow помогает анализатору находить ошибки, которые с помощью pattern\-based анализа найти весьма затруднительно\.

К таким ошибкам относятся:

* Переполнения;
* Выход за границу массива;
* Доступ по нулевой или потенциально нулевой ссылке;
* Бессмысленные условия \(always true/false\);
* Утечки памяти и ресурсов;
* Деление на 0;
* И некоторые другие\.

Data Flow анализ особенно важен при поиске уязвимостей\. Например, в случае, если некая программа получает ввод от пользователя, есть вероятность, что ввод будет использован для того, чтобы вызвать отказ в обслуживании, или для получения контроля над системой\. Примерами могут служить ошибки, приводящие к переполнениям буфера при некоторых входных данных, или, например, SQL\-инъекции\. В обоих случаях для того, чтобы статический анализатор мог выявлять подобные ошибки и уязвимости, необходимо отслеживать поток данных и возможные значения переменных\. 

Должен сказать, что механизм анализа потока данных \- это сложный и обширный механизм, а в этой статье я коснулся лишь самых основ\.

Рассмотрим несколько примеров ошибок, которые можно обнаружить с помощью механизма Data Flow\. 

Проект Hive:

```cpp
public static boolean equal(byte[] arg1, final int start1,
                            final int len1, byte[] arg2,
                            final int start2, final int len2) {
    if (len1 != len2) { // <=
      return false;
    }
    if (len1 == 0) {
      return true;
    }
    ....
    if (len1 == len2) { // <=
      ....
    }
}
```

Условие _len1 \=\= len2_ выполняется всегда, поскольку противоположная проверка уже была выполнена выше\.

Еще один пример из этого же проекта:

```cpp
if (instances != null) { // <=
  Set<String> oldKeys = new HashSet<>(instances.keySet());
  if (oldKeys.removeAll(latestKeys)) {
    ....
  }
  this.instances.keySet().removeAll(oldKeys);
  this.instances.putAll(freshInstances);
} else {
  this.instances.putAll(freshInstances); // <=
}
```

Здесь в блоке _else_ гарантированно происходит разыменование нулевого указателя\. Примечание: здесь _instances_ \- это то же самое, что и _this\.instances_\.

Пример из проекта JMonkeyEngine:

```cpp
public static int convertNewtKey(short key) {
    ....
    if (key >= 0x10000) {
        return key - 0x10000;
    }
    return 0;
}
```

Здесь переменная _key_ сравнивается с числом 65536, однако, она имеет тип _short_, а максимально возможное значение для _short_ \- это 32767\. Соответственно, условие никогда не выполняется\.

Пример из проекта Jenkins:

```cpp
public final R getSomeBuildWithWorkspace() 
{
    int cnt = 0;
    for (R b = getLastBuild();
         cnt < 5 && b ! = null;
         b = b.getPreviousBuild()) 
    {
        FilePath ws = b.getWorkspace();
        if (ws != null) return b;
    }
    return null;
}
```

В данном коде ввели переменную _cnt_, чтобы ограничить количество проходов пятью, но забыли ее инкрементировать, в результате чего проверка бесполезна\.

## Механизм аннотаций

Кроме того, анализатору нужен механизм аннотаций\. Аннотации \- это система разметки, которая предоставляет анализатору дополнительную информацию об используемых методах и классах, помимо той, что может быть получена путем анализа их сигнатуры\. Разметка производится вручную, это долгий и трудоемкий процесс, так как для достижения наилучших результатов необходимо проаннотировать большое количество стандартных классов и методов языка Java\. Также имеет смысл выполнить аннотирование популярных библиотек\. В целом аннотации можно рассматривать как базу знаний анализатора о контрактах стандартных методов и классов\.

Вот небольшой пример ошибки, которую можно обнаружить с помощью аннотаций:

```cpp
int test(int a, int b) {
  ...
  return Math.max(a, a);
}
```

В данном примере из\-за опечатки в качестве второго аргумента метода _Math\.max_ была передана та же переменная, что и в качестве первого аргумента\. Такое выражение является бессмысленным и подозрительным\.

Зная о том, что аргументы метода _Math\.max_ всегда должны быть различны, статический анализатор сможет выдать предупреждение на подобный код\.

Забегая вперед, приведу несколько примеров нашей разметки встроенных классов и методов \(код на C\+\+\):

```cpp
Class("java.lang.Math")
  - Function("abs", Type::Int32)
      .Pure()
      .Set(FunctionClassification::NoDiscard)
      .Returns(Arg1, [](const Int &v) 
      { return v.Abs(); })

  - Function("max", Type::Int32, Type::Int32)
      .Pure()
      .Set(FunctionClassification::NoDiscard)
      .Requires(NotEquals(Arg1, Arg2)
      .Returns(Arg1, Arg2, [](const Int &v1, const Int &v2)
      { return v1.Max(v2); })

Class("java.lang.String", TypeClassification::String)
  - Function("split", Type::Pointer)
      .Pure()
      .Set(FunctionClassification::NoDiscard)
      .Requires(NotNull(Arg1))
      .Returns(Ptr(NotNullPointer))

Class("java.lang.Object")
    - Function("equals", Type::Pointer)
        .Pure()
        .Set(FunctionClassification::NoDiscard)
        .Requires(NotEquals(This, Arg1))

Class("java.lang.System")
  - Function("exit", Type::Int32)
      .Set(FunctionClassification::NoReturn)
```

Пояснения:

* _Class_ – аннотируемый класс;
* _Function_ – метод аннотируемого класса;
* _Pure_ – аннотация, показывающая, что метод является чистым, т\.е\. детерминированным и не имеющим побочных эффектов;
* _Set_ – установка произвольного флага для метода\.
* _FunctionClassification::NoDiscard_ – флаг, означающий, что возвращаемое значение метода обязательно должно быть использовано;
* _FunctionClassification::NoReturn_ – флаг, означающий, что метод не возвращает управление;
* _Arg1_, _Arg2_, _\.\.\._, _ArgN_ – аргументы метода;
* _Returns_ – возвращаемое значение метода;
* _Requires_ – контракт на метод\.

Стоит отметить, что помимо ручной разметки существует еще один поход к аннотированию – автоматический вывод контрактов на основе байт\-кода\. Понятно, что такой подход позволяет вывести только определенные виды контрактов, но зато он дает возможность получать дополнительную информацию вообще из всех зависимостей, а не только из тех, что были проаннотированы вручную\.

К слову, уже существует инструмент, который умеет выводить контракты вроде _@Nullable_, _@NotNull_ на основе байт\-кода – [FABA](https://github.com/ilya-klyuchnikov/faba)\. Насколько я понимаю, производная от FABA используется в IntelliJ IDEA\.

Сейчас мы тоже рассматриваем возможность добавления анализа байт\-кода для получения контрактов для всех методов, так как эти контракты могли бы хорошо дополнить наши ручные аннотации\.

Диагностические правила при работе часто обращаются к аннотациям\. Помимо диагностик, аннотации использует механизм Data Flow\. Например, используя аннотацию метода _java\.lang\.Math\.abs_, он может точно вычислять значение для модуля числа\. При этом писать какой\-либо дополнительный код не приходится – только правильно разметить метод\.

Рассмотрим пример ошибки из проекта Hibernate, которую можно обнаружить благодаря аннотации:

```cpp
public boolean equals(Object other) {
    if (other instanceof Id) {
        Id that = (Id) other;
        return purchaseSequence.equals(this.purchaseSequence) &&
            that.purchaseNumber == this.purchaseNumber;
    }
    else {
        return false;
    }
}
```

В данном коде метод _equals\(\)_ сравнивает объект _purchaseSequence_ с самим собой\. Наверняка это опечатка и справа должно быть _that\.purchaseSequence_, а не _purchaseSequence_\.

## Как доктор Франкенштейн собирал из частей анализатор

![0572_Java_analyzer_ru/image4.png](https://import.viva64.com/docx/blog/0572_Java_analyzer_ru/image4.png)

Поскольку сами по себе механизмы Data Flow и аннотаций не сильно привязаны к определенному языку, было принято решение переиспользовать эти механизмы из нашего C\+\+ анализатора\. Это позволило нам в короткие сроки получить всю мощь ядра C\+\+ анализатора в нашем Java анализаторе\. Кроме того, на это решение также повлияло и то, что эти механизмы были написаны на современном C\+\+ с кучей метапрограммирования и шаблонной магии, а, соответственно, не очень хорошо подходят для переноса на другой язык\.

Для того, чтобы связать Java часть с ядром на C\+\+, мы решили использовать [SWIG \(Simplified Wrapper and Interface Generator\)](http://www.swig.org/) – средство для автоматической генерации врапперов и интерфейсов для связывания программ на C и C\+\+ с программами, написанными на других языках\. Для Java SWIG генерирует код на [JNI \(Java Native Interface\)](https://en.wikipedia.org/wiki/Java_Native_Interface)\. 

SWIG отлично подходит для случаев, когда имеется уже большой объем C\+\+ кода, который необходимо интегрировать в Java проект\.

Приведу минимальный пример работы со SWIG\. Предположим, у нас есть C\+\+ класс, который мы хотим использовать в Java проекте:

CoolClass\.h

```cpp
class CoolClass
{
public:
  int val;
  CoolClass(int val);
  void printMe();
};
```

CoolClass\.cpp

```cpp
#include <iostream>
#include "CoolClass.h"

CoolClass::CoolClass(int v) : val(v) {}

void CoolClass::printMe()
{
  std::cout << "val: " << val << '\n';
}
```

Сперва нужно создать [интерфейсный файл SWIG](http://www.swig.org/Doc3.0/SWIG.html#SWIG_nn47) с описанием всех экспортируемых функций и классов\. Также в этом файле при необходимости производятся дополнительные настройки\.

Example\.i

```cpp
%module MyModule
%{
#include "CoolClass.h"
%}
%include "CoolClass.h"
```

После этого можно запускать SWIG:

```cpp
$ swig -c++ -java Example.i
```

Он сгенерирует следующие файлы: 

* CoolClass\.java – класс, с которым мы будем непосредственно работать в Java проекте;
* MyModule\.java – класс модуля, в который помещаются все свободные функции и переменные;
* MyModuleJNI\.java – Java врапперы;
* Example\_wrap\.cxx – С\+\+ врапперы\.

Теперь нужно просто добавить получившиеся \.java файлы в Java проект и \.cxx файл в C\+\+ проект\.

Наконец, нужно скомпилировать C\+\+ проект в виде динамической библиотеки и подгрузить ее в Java проект с помощью _System\.loadLibary\(\)_:

App\.java

```cpp
class App {
  static {
    System.loadLibary("example");
  }
  public static void main(String[] args) {
    CoolClass obj = new CoolClass(42);
    obj.printMe();
  }
}
```

Схематически это можно представить следующим образом:

![0572_Java_analyzer_ru/image5.png](https://import.viva64.com/docx/blog/0572_Java_analyzer_ru/image5.png)

Конечно, в реальном проекте все не настолько просто и приходится приложить чуть больше усилий:

* Для того, чтобы использовать шаблонные классы и методы из C\+\+, их нужно инстанцировать для всех принимаемых шаблонных параметров с помощью директивы _%template_;
* В некоторых случаях может понадобиться перехватывать исключения, которые выбрасываются из C\+\+ части в Java части\. По умолчанию SWIG не перехватывает исключения из C\+\+ \(возникает segfault\), однако, имеется возможность это сделать, используя директиву _%exception_;
* SWIG позволяет расширять плюсовый код на стороне Java, используя директиву _%extend_\. Мы, например, в нашем проекте добавляем к виртуальным значениям метод _toString\(\)_, чтобы можно было их просмотреть в отладчике Java;
* Для того, чтобы эмулировать RAII поведение из C\+\+, во всех интересующих классах реализуется интерфейс _AutoClosable_;
* Механизм директоров позволяет использовать кросс\-языковый полиморфизм;
* Для типов, которые аллоцируются только внутри C\+\+ \(на своём пуле памяти\), убираются конструкторы и финализаторы, чтобы повысить производительность\. Сборщик мусора будет игнорировать эти типы\.

Подробнее обо всех этих механизмах можно почитать в [документации SWIG](http://www.swig.org/Doc3.0/SWIGDocumentation.html)\.

Наш анализатор собирается при помощи gradle, который зовет CMake, который, в свою очередь, зовет SWIG и собирает C\+\+ часть\. Для программистов это происходит практически незаметно, так что никаких особых неудобств при разработке мы не испытываем\.

Ядро нашего C\+\+ анализатора собирается под Windows, Linux, macOS, так что Java анализатор также работает в этих ОС\.

## Что из себя представляет диагностическое правило?

Сами диагностики и код для анализа мы пишем на Java\. Это обусловлено тесным взаимодействием со Spoon\. Каждое диагностическое правило является визитором, у которого перегружаются методы, в которых обходятся интересующие нас элементы:

![0572_Java_analyzer_ru/image6.png](https://import.viva64.com/docx/blog/0572_Java_analyzer_ru/image6.png)

Например, так выглядит каркас диагностики V6004:

```cpp
class V6004 extends PvsStudioRule
{
  ....
  @Override
  public void visitCtIf(CtIf ifElement)
  {
    // if ifElement.thenStatement statement is equivalent to
    //    ifElement.elseStatement statement => add warning V6004
  }
}
```

## Плагины

Для максимально простой интеграции статического анализатора в проект мы разработали плагины для сборочных систем Maven и Gradle\. Пользователю остается только добавить наш плагин к проекту\.

Для Gradle:

```cpp
....
apply plugin: com.pvsstudio.PvsStudioGradlePlugin
pvsstudio {
    outputFile = 'path/to/output.json'
    ....
}
```

Для Maven:

```cpp
....
<plugin>
    <groupId>com.pvsstudio</groupId>
    <artifactId>pvsstudio-maven-plugin</artifactId>
    <version>0.1</version>
    <configuration>
        <analyzer>
            <outputFile>path/to/output.json</outputFile>
            ....
        </analyzer>
    </configuration>
</plugin>
```

После этого плагин самостоятельно получит структуру проекта и запустит анализ\.

Помимо этого, мы разработали прототип плагина для IntelliJ IDEA\.

![0572_Java_analyzer_ru/image7.png](https://import.viva64.com/docx/blog/0572_Java_analyzer_ru/image7.png)

Также этот плагин работает и в Android Studio\.

Плагин для Eclipse сейчас находится в стадии разработки\.

## Инкрементальный анализ

Мы предусмотрели режим инкрементального анализа, который позволяет проверять только измененные файлы и тем самым значительно сокращает время, необходимое для анализа кода\. Благодаря этому, разработчики смогут запускать анализ так часто, как это необходимо\.

Инкрементальный анализ включает в себя несколько этапов:

* Кеширование метамодели Spoon;
* Перестроение изменившейся части метамодели;
* Анализ изменившихся файлов\.

## Наша система тестирования

Для тестирования Java анализатора на реальных проектах мы написали специальный инструментарий, позволяющий работать с базой открытых проектов\. Он был написан на коленке^W Python \+ Tkinter и является кроссплатформенным\.

Он работает следующим образом:

* Тестируемый проект определенной версии выкачивается с репозитория на GitHub;
* Выполняется сборка проекта;
* В _pom\.xml_ или _build\.gradle_ добавляется наш плагин \(при помощи git apply\);
* Выполняется запуск статического анализатора при помощи плагина;
* Получившийся отчет сравнивается с эталоном для этого проекта\.

Такой подход гарантирует, что хорошие срабатывания не пропадут в результате изменения кода анализатора\. Ниже показан интерфейс нашей утилиты для тестирования\.

![0572_Java_analyzer_ru/image8.png](https://import.viva64.com/docx/blog/0572_Java_analyzer_ru/image8.png)

Красным отмечаются те проекты, в отчетах которых есть какие\-либо различия с эталоном\. Кнопка Approve позволяет сохранить текущую версию отчета в качестве эталона\.

## Примеры ошибок

По традиции приведу по несколько ошибок из разных открытых проектов, которые нашел наш Java анализатор\. В будущем планируется написание статей с более детальным отчетом по каждому проекту\.

### Проект Hibernate

**Предупреждение PVS\-Studio:** V6009 Function 'equals' receives odd arguments\. Inspect arguments: this, 1\. PurchaseRecord\.java 57

```cpp
public boolean equals(Object other) {
    if (other instanceof Id) {
        Id that = (Id) other;
        return purchaseSequence.equals(this.purchaseSequence) &&
            that.purchaseNumber == this.purchaseNumber;
    }
    else {
        return false;
    }
}
```

В данном коде метод _equals\(\)_ сравнивает объект _purchaseSequence_ с самим собой\. Скорее всего, это опечатка и справа должно быть _that\.purchaseSequence_, а не _purchaseSequence_\.

**Предупреждение PVS\-Studio:** V6009 Function 'equals' receives odd arguments\. Inspect arguments: this, 1\. ListHashcodeChangeTest\.java 232

```cpp
public void removeBook(String title) {
    for( Iterator<Book> it = books.iterator(); it.hasNext(); ) {
        Book book = it.next();
        if ( title.equals( title ) ) {
            it.remove();
        }
    }
}
```

Срабатывание, аналогичное предыдущему – справа должно быть _book\.title_, а не _title_\.

### Проект Hive

**Предупреждение** **PVS\-Studio:** V6007 Expression 'colOrScalar1\.equals\("Column"\)' is always false\. GenVectorCode\.java 2768

**Предупреждение** **PVS\-Studio:** V6007 Expression 'colOrScalar1\.equals\("Scalar"\)' is always false\. GenVectorCode\.java 2774

**Предупреждение** **PVS\-Studio:** V6007 Expression 'colOrScalar1\.equals\("Column"\)' is always false\. GenVectorCode\.java 2785

```cpp
String colOrScalar1 = tdesc[4];
....
if (colOrScalar1.equals("Col") &&
    colOrScalar1.equals("Column")) {
    ....
} else if (colOrScalar1.equals("Col") &&
           colOrScalar1.equals("Scalar")) {
    ....
} else if (colOrScalar1.equals("Scalar") &&
           colOrScalar1.equals("Column")) {
    ....
}
```

Здесь явно перепутали операторы и вместо '_\|\|'_ использовали '_&&'_\.

### Проект JavaParser

**Предупреждение PVS\-Studio:** V6001 There are identical sub\-expressions 'tokenRange\.getBegin\(\)\.getRange\(\)\.isPresent\(\)' to the left and to the right of the '&&' operator\. Node\.java 213

```cpp
public Node setTokenRange(TokenRange tokenRange)
{
  this.tokenRange = tokenRange;
  if (tokenRange == null || 
      !(tokenRange.getBegin().getRange().isPresent() &&
        tokenRange.getBegin().getRange().isPresent()))
  {
    range = null;
  }
  else 
  {
    range = new Range(
        tokenRange.getBegin().getRange().get().begin,
        tokenRange.getEnd().getRange().get().end);
  }
  return this;
}
```

Анализатор обнаружил, что слева и справа от оператора _&&_ стоят одинаковые выражения \(при этом все методы в цепочке вызовов являются чистыми\)\. Скорее всего, во втором случае необходимо использовать _tokenRange\.getEnd\(\)_, а не _tokenRange\.getBegin\(\)_\.

**Предупреждение PVS\-Studio:** V6016 Suspicious access to element of 'typeDeclaration\.getTypeParameters\(\)' object by a constant index inside a loop\. ResolvedReferenceType\.java 265

```cpp
if (!isRawType()) {
  for (int i=0; i<typeDeclaration.getTypeParams().size(); i++) {
    typeParametersMap.add(
       new Pair<>(typeDeclaration.getTypeParams().get(0),
                  typeParametersValues().get(i)));
    }
}
```

Анализатор обнаружил подозрительный доступ к элементу коллекции по константному индексу внутри цикла\. Возможно, в данном коде присутствует ошибка\.

### Проект Jenkins

**Предупреждение** **PVS\-Studio:** V6007 Expression 'cnt < 5' is always true\. AbstractProject\.java 557

```cpp
public final R getSomeBuildWithWorkspace() 
{
    int cnt = 0;
    for (R b = getLastBuild();
         cnt < 5 && b ! = null;
         b = b.getPreviousBuild()) 
    {
        FilePath ws = b.getWorkspace();
        if (ws != null) return b;
    }
    return null;
}
```

В данном коде ввели переменную _cnt_, чтобы ограничить количество проходов пятью, но забыли ее инкрементировать, в результате чего проверка бесполезна\.

### Проект Spark

**Предупреждение PVS\-Studio:** V6007 Expression 'sparkApplications \!\= null' is always true\. SparkFilter\.java 127

```cpp
if (StringUtils.isNotBlank(applications)) 
{
  final String[] sparkApplications = applications.split(",");

  if (sparkApplications != null && sparkApplications.length > 0)
  {
    ...
  }
}
```

Проверка на _null_ результата, возвращаемого методом _split_, бессмысленна, так как этот метод всегда возвращает коллекцию и никогда не возвращает _null_\.

### Проект Spoon

**Предупреждение PVS\-Studio:** V6001 There are identical sub\-expressions '\!m\.getSimpleName\(\)\.startsWith\("set"\)' to the left and to the right of the '&&' operator\. SpoonTestHelpers\.java 108

```cpp
if (!m.getSimpleName().startsWith("set") &&
    !m.getSimpleName().startsWith("set")) {
    continue;
}
```

В данном коде слева и справа от оператора && стоят одинаковые выражения \(при этом все методы в цепочке вызовов являются чистыми\)\. Скорее всего, в коде присутствует логическая ошибка\.

**Предупреждение PVS\-Studio:** V6007 Expression 'idxOfScopeBoundTypeParam \>\= 0' is always true\. MethodTypingContext\.java 243

```cpp
private boolean
isSameMethodFormalTypeParameter(....) {
  ....
  int idxOfScopeBoundTypeParam = getIndexOfTypeParam(....);
  if (idxOfScopeBoundTypeParam >= 0) { // <=
    int idxOfSuperBoundTypeParam = getIndexOfTypeParam(....);
    if (idxOfScopeBoundTypeParam >= 0) { // <=
      return idxOfScopeBoundTypeParam == idxOfSuperBoundTypeParam;
    }
  }
  ....
}
```

Здесь опечатались во втором условии и вместо _idxOfSuperBoundTypeParam_ написали _idxOfScopeBoundTypeParam_\.



### Проект Spring Security

**Предупреждение PVS\-Studio:** V6001 There are identical sub\-expressions to the left and to the right of the '\|\|' operator\. Check lines: 38, 39\. AnyRequestMatcher\.java 38

```cpp
@Override
@SuppressWarnings("deprecation")
public boolean equals(Object obj) {
    return obj instanceof AnyRequestMatcher ||
    obj instanceof security.web.util.matcher.AnyRequestMatcher;
}
```

Срабатывание аналогично предыдущему – здесь имя одного и того же класса записано разными способами\.

**Предупреждение PVS\-Studio:** V6006 The object was created but it is not being used\. The 'throw' keyword could be missing\. DigestAuthenticationFilter\.java 434

```cpp
if (!expectedNonceSignature.equals(nonceTokens[1])) {
  new BadCredentialsException(
       DigestAuthenticationFilter.this.messages
       .getMessage("DigestAuthenticationFilter.nonceCompromised",
                    new Object[] { nonceAsPlainText },
                    "Nonce token compromised {0}"));
}
```

В данном коде забыли добавить _throw_ перед исключением\. В результате объект исключения _BadCredentialsException_ создается, но никак не используется, т\.е\. исключение не выбрасывается\.

**Предупреждение PVS\-Studio:** V6030 The method located to the right of the '\|' operators will be called regardless of the value of the left operand\. Perhaps, it is better to use '\|\|'\. RedirectUrlBuilder\.java 38

```cpp
public void setScheme(String scheme) {
    if (!("http".equals(scheme) | "https".equals(scheme))) {
        throw new IllegalArgumentException("...");
    }
    this.scheme = scheme;
}
```

В данном коде использование оператора _\|_ неоправданно, так как при его использовании правая часть будет вычислена даже в случае, если левая часть уже является истинной\. В данном случае это не имеет практического смысла, поэтому оператор _\|_ стоит заменить на _\|\|_\.

### Проект IntelliJ IDEA

**Предупреждение PVS\-Studio:** V6008 Potential null dereference of 'editor'\. IntroduceVariableBase\.java:609

```cpp
final PsiElement nameSuggestionContext = 
  editor == null ? null : file.findElementAt(...);  // <=
final RefactoringSupportProvider supportProvider =
  LanguageRefactoringSupport.INSTANCE.forLanguage(...);
final boolean isInplaceAvailableOnDataContext =
  supportProvider != null &&
  editor.getSettings().isVariableInplaceRenameEnabled() &&  // <=
...
```

Анализатор обнаружил, что в данном коде может произойти разыменование нулевого указателя _editor_\. Стоит добавить дополнительную проверку\.

**Предупреждение PVS\-Studio:** V6007 Expression is always false\. RefResolveServiceImpl\.java:814

```cpp
@Override
public boolean contains(@NotNull VirtualFile file) {
    ....
    return false & !myProjectFileIndex.isUnderSourceRootOfType(....);
}
```

Мне сложно сказать, что имел в виду автор, но такой код выглядит весьма подозрительно\. Даже если вдруг ошибки здесь нет, думаю, стоит переписать это место так, чтобы не смущать анализатор и других программистов\.

**Предупреждение** **PVS\-Studio:** V6007 Expression 'result\[0\]' is always false\. CopyClassesHandler\.java:298

```cpp
final boolean[] result = new boolean[] {false};   // <=
Runnable command = () -> {
  PsiDirectory target;
  if (targetDirectory instanceof PsiDirectory) {
    target = (PsiDirectory)targetDirectory;
  } else {
    target = WriteAction.compute(() -> 
      ((MoveDestination)targetDirectory).getTargetDirectory(
          defaultTargetDirectory));
  }
  try {
    Collection<PsiFile> files = 
        doCopyClasses(classes, map, copyClassName, target, project);
    if (files != null) {
      if (openInEditor) {
        for (PsiFile file : files) {
          CopyHandler.updateSelectionInActiveProjectView(
              file,
              project,
              selectInActivePanel);
        }
        EditorHelper.openFilesInEditor(
            files.toArray(PsiFile.EMPTY_ARRAY));
      }
    }
  }
  catch (IncorrectOperationException ex) {
    Messages.showMessageDialog(project,
        ex.getMessage(),
        RefactoringBundle.message("error.title"),
        Messages.getErrorIcon());
  }
};
CommandProcessor processor = CommandProcessor.getInstance();
processor.executeCommand(project, command, commandName, null);

if (result[0]) {   // <=
  ToolWindowManager.getInstance(project).invokeLater(() -> 
      ToolWindowManager.getInstance(project)
          .activateEditorComponent());
}
```

Подозреваю, что здесь забыли как\-либо изменить значение в _result_\. Из\-за этого анализатор сообщает о том, что проверка _if \(result\[0\]\)_ является бессмысленной\.

## Заключение

Java направление является весьма разносторонним – это и desktop, и android, и web, и многое другое, поэтому у нас есть большой простор для деятельности\. В первую очередь, конечно, будем развивать те направления, что будут наиболее востребованы\.

Вот наши планы на ближайшее будущее:

* Вывод аннотаций на основе байт\-кода;
* Интеграция в проекты на Ant \(кто\-то его еще использует в 2018?\);
* Плагин для Eclipse \(в процессе разработки\);
* Еще больше диагностик и аннотаций;
* Совершенствование механизма Data Flow\.

Также предлагаю желающим поучаствовать в тестировании альфа\-версии нашего Java анализатора, когда она станет доступной\. Для этого напишите нам [в поддержку](https://pvs-studio.ru/ru/about-feedback/)\. Мы внесем ваш контакт в список и напишем вам, когда подготовим первую альфа\-версию\.