Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
>
>
>
Расширение PVS-Studio для Visual Studio…

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

09 Фев 2024

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

1101_JavaAnalysisInVSCode_ru/image1.png

Введение

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

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

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

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

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

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

Также важно отметить, что для анализа Java-проектов расширению PVS-Studio требуется, чтобы в VS Code были установлены стандартные Java-расширения: Language Support for Java(TM) 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:

1101_JavaAnalysisInVSCode_ru/image2.png

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

1101_JavaAnalysisInVSCode_ru/image3.png

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

1101_JavaAnalysisInVSCode_ru/image4.png

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

1101_JavaAnalysisInVSCode_ru/image5.png

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

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

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

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

1101_JavaAnalysisInVSCode_ru/image6.png

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

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

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

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

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

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

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

1101_JavaAnalysisInVSCode_ru/image7.png

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

1101_JavaAnalysisInVSCode_ru/image8.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<? extends OperatorDesc> findSourceRS(....) 
{
  ....
  List<Operator<? extends OperatorDesc>> 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<LlapServiceInstance> getAllInstancesOrdered(....) {
  ....
  Collections.sort(list, new Comparator<LlapServiceInstance>() {
    @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.

1101_JavaAnalysisInVSCode_ru/image9.png

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

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

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

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

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

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

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

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

1101_JavaAnalysisInVSCode_ru/image10.png

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

  • compatibility — активация диагностики V6078;
  • source-java — номер версии JDK, на котором ваш проект работает в данный момент;
  • target-java — номер версии целевого JDK.

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

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

Заключение

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

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

Популярные статьи по теме


Комментарии (0)

Следующие комментарии next comments
close comment form
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам