Расширение PVS-Studio для Visual Studio Code: поиск ошибок в Java-коде

Java-разработчик и предпочитаешь работать в VS Code? Для тебя есть хорошая новость! Теперь ты можешь писать ещё более надёжный код вместе с расширением PVS-Studio, которое помогает находить ошибки в Java-проектах и не только.

526e847a5eaf206279b4e5dc4beaead4.png

Введение

Анализатор PVS-Studio — это инструмент для автоматического поиска потенциальных ошибок и угроз безопасности в C, C++, C# и Java коде. Он состоит из нескольких компонентов:

  1. ядра, выполняющего анализ кода;

  2. плагинов для различных IDE — интерфейсов, значительно облегчающих взаимодействие пользователя с ядром и обработку результатов анализа.

Один из таких плагинов — PVS-Studio для VS Code. Недавно он получил крупное обновление, и теперь с помощью расширения можно запускать анализ Java-проектов.

В честь этого события я хотел бы продемонстрировать вам принцип работы с ним, как уже делал в одной из своих предыдущих статей. Но теперь наша цель — найти ошибки не в C#, а в Java-проекте. Поехали!

Установка анализатора

Для начала нужно установить ядро Java-анализатора и расширение для VS. Небольшую инструкцию по установке вы можете найти в одной из моих предыдущих статей.

Также важно отметить, что для анализа Java-проектов расширению PVS-Studio требуется, чтобы в VS Code были установлены стандартные Java-расширения: Language Support for Java by Red Hat и Project Manager for Java.

Обратите внимание, что для автоматического обнаружения ядра расширением его стоит установить в директорию по умолчанию для вашей операционной системы. Вы также можете установить ядро в произвольную директорию, при этом указав путь к нему в настройках VS Code (File → Preferences → Settings → PVS-Studio: Java Projects Analyzer Path).

Для работы анализатора требуется JDK 11–19 версии. Если вы используете другую, то можете скачать нужный JDK и указать путь к его исполняемому файлу в соответствующей настройке (File → Preferences → Settings → PVS-Studio: Java For Running Analyzer).

Запуск анализа проекта

Откройте проект и дождитесь, пока его структура загрузится в разделе инспектора Java projects:

cf8bde202bab839470554e1f82701526.png

Если ваш проект включает в себя проекты или папки, расположенные вне открытой директории, выполните команду «Java: Reload Projects». После этого должен появиться список со всеми включёнными проектами. Отметьте те, которые должны участвовать в анализе.

519124508031c6d1b7358922c6643708.png

Теперь можно смело запускать анализ. Это можно сделать разными способами в зависимости от того, хотите ли вы проанализировать весь проект, его отдельные файлы или папки. Для запуска полного анализа можно кликнуть по соответствующей кнопке во вкладке PVS-Studio нижней панели VS Code.

84c5407408c3e057f723382efc7a01e4.png

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

3a694910ae768a31a6aa8eeddf9ec823.png

Клик по кнопке Edit откроет в области редактора этот файл, при этом анализ не начнётся. При клике по Continue он запустится без параметров.

Не стоит пугаться, если прогресс анализа какое-то время висит на 0. Как правило, это происходит по следующим причинам:

  1. Подготовка к анализу на стороне расширения. После открытия директории с проектом расширение в фоновом режиме начинает собирать всю необходимую информацию для анализа. Если начать его до завершения процесса — запуск отложится. Стоит отметить, что все последующие запуски анализа будут выполняться быстрее;

  2. Подготовка анализа на стороне анализатора. Этот этап включает, к примеру, эвалюацию проекта, построение или обновление его модели, что тоже может занять некоторое время.

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

626acdc8fda66a758cf434a5f72bc331.png

Как только анализ будет завершён, можно переходить к самой непростой и в тоже время интересной части — исследованию полученных предупреждений с целью поиска ошибок.

Поиск ошибок в коде

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

  • первый уровень (красный) — эти предупреждения не обязательно могут быть самыми критичными, однако они наиболее точные;

  • второй уровень (оранжевый) — часто предупреждения этого уровня указывают на не самые простые и интересные ошибки, однако и обеспечить точность их нахождения сложнее;

  • третий уровень (жёлтый) — предупреждения этого уровня чаще всего бывают спорными или незначительными. Рекомендуется обращать на них внимание в последнюю очередь, но и полностью игнорировать не стоит. Среди них также могут оказаться несколько предупреждений, указывающих на ошибки в вашем коде.

При просмотре срабатываний не стоит пренебрегать доступными функциями, такими как фильтрация и сортировка по столбцам.

Например, сортировка по столбцу Code сгруппирует похожие срабатывания вместе. Проверять несколько однотипных срабатываний подряд проще, чем если бы они располагались в случайном порядке.

Также вы можете исключить некоторые из них. Например, срабатывания внутри тестовых файлов. Для этого выберите соответствующую опцию в контекстном меню предупреждения:

167e1c63bf3b7d2bf764e4156467f95e.png

Или укажите путь в соответствующей вкладке настроек (кнопка с шестерёнкой):

3540237c2801e81940ec62c39c1ef72c.png

Проанализировав проект Apache Hive и проверив часть предупреждений из отчёта, мне удалось найти несколько потенциальных ошибок.

Ошибка при битовом смещении

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

Предупреждение: V6034. Shift by the value of 'j' could be inconsistent with the size of type: 'j' = [0 … 63]. IoTrace.java: 264.

Обратите внимание на выражение 1 << j, результат которого используется при вычислении нового значения переменной val. В этом выражении выполняется побитовое смещение единицы на j битов влево. Из определения цикла становится понятно, что j может иметь значения в диапазоне от 0 до 64. Однако числовой литерал по умолчанию имеет тип int, который ограничен 32 битами.

При смещении этого значения на 32 бита и более вместо расширения этого предела до 64 бит произойдёт зацикливание смещения. Так, при смещении единицы на 32 бита будет снова получена 1, при смещении на 33 бита — 2 и т. д.

Когда разница между побитовым и логическим ИЛИ имеет значение

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

Предупреждение: V6030. The method located to the right of the '|' operator will be called regardless of the value of the left operand. Perhaps, it is better to use '||'. OperatorUtils.java:555.

Здесь мы имеем потенциальную возможность поймать NullPointerException в выражении parents.isEmpty (). Дело в том, что в условном выражении был использован побитовый оператор ИЛИ вместо логического. В результате выражение parents.isEmpty () будет выполняться независимо от того, имеет ли parents значение null или нет.

Небрежный copy-paste

Проблема 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"))  // <=
  {
    ....
  }
}

Предупреждения:

  • 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.

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

Проблема 2

@Override
public List getAllInstancesOrdered(....) {
  ....
  Collections.sort(list, new Comparator() {
    @Override
    public int compare(LlapServiceInstance o1, LlapServiceInstance o2) {
      return o2.getWorkerIdentity().compareTo(o2.getWorkerIdentity()); // <=
    }
  });
  ....
}

Предупреждение: V6009. Function 'compareTo' receives an odd argument. An object 'o2.getWorkerIdentity ()' is used as an argument to its own method. LlapFixedRegistryImpl.java:260.

А в этом случае метод compare вместо сравнения двух разных объектов сравнивает второй объект с самим собой.

Подавление предупреждений

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

Для этого нужно выделить их в таблице (с помощью комбинации клавиш Ctrl + A можно выделить сразу все), открыть контекстное меню и выбрать опцию Mark as a False Alarm или Add selected messages to suppression file. Обратите внимание, применить последнюю функцию сразу ко всем предупреждениям или только к той части, что не была отфильтрована, можно с помощью кнопки в виде молнии в верхнем правом углу окна PVS-Studio.

c1a64be67517f54d9fcd049b40d09e14.png

Первая функция отличается от второй тем, что подавляет предупреждение путём добавления комментария вида //-V[Код предупреждения] в первую строку кода, на который указывает предупреждение. Вторая функция, в свою очередь, сохраняет информацию о подавленных предупреждениях в специальном файле.

Стоит отметить, что для подавления ненужных срабатываний предпочтительно использовать функцию Mark as a False Alarm.

Функция добавления предупреждений в suppress-файл является удобным способом отложить технический долг на потом и сосредоточиться на качестве нового кода.

В этом случае стандартный сценарий использования функции следующий:

  1. Выполняется общий анализ проекта;

  2. Полученные предупреждения подавляются, после чего они уже не будут присутствовать в результатах последующих анализов;

  3. Позже подавленные предупреждения просматриваются разработчиком. Чтобы вернуть их, нужно отредактировать или удалить файл подавления (по умолчанию называемый suppress_base.json), который можно найти в папке .PVS-Studio в директории решения.

Подробнее эта тема рассматривается в статье «Как внедрить статический анализатор кода в legacy проект и не демотивировать команду».

Обнаружение потенциальных проблем с совместимостью между разными версиями Java SE

Если в будущем вы планируете миграцию вашего проекта на более свежую версию Java, стоит следить за тем, чтобы уже сейчас не завязываться на API, которое будет удалено в целевом выпуске. В этом вам поможет специальная диагностика V6078. При работе с расширением VS Code PVS-Studio эту диагностику можно включить в файле JavaAnalyzerConfig.jsonc в папке .PVS-Studio рабочей директории следующим образом:

d982b907969955b8ff738f92a5ef33d3.png

Здесь указываются следующие аргументы:

  1. compatibility — активация диагностики V6078;

  2. source-java — номер версии JDK, на котором ваш проект работает в данный момент;

  3. target-java — номер версии целевого JDK.

Известные проблемы

При тестировании на крупном проекте Elasticsearch (более 18000 файлов с кодом) мы обнаружили, что работа расширения Project Manager for Java может привести к сильному перерасходу оперативной памяти, что в конечном счёте приводит к падению окна VS Code. Если у вас есть идеи, как можно оптимизировать работу этого расширения в подобных случаях, мы будем рады получить советы в комментариях.

Заключение

На этом наш небольшой обзор подошёл к концу. Надеюсь, у меня получилось показать вам, что расширение PVS-Studio для VS Code — это хороший инструмент, который стоит использовать в своей работе.

Чистого кода и успешных проектов! До встречи в следующих статьях!

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Moskalev. PVS-Studio extension for Visual Studio Code: searching for errors in Java code.

© Habrahabr.ru