Java-разработчик и предпочитаешь работать в VS Code? Для тебя есть хорошая новость! Теперь ты можешь писать ещё более надёжный код вместе с расширением PVS-Studio, которое помогает находить ошибки в Java-проектах и не только.
Анализатор PVS-Studio — это инструмент для автоматического поиска потенциальных ошибок и угроз безопасности в C, C++, C# и Java коде. Он состоит из нескольких компонентов:
Один из таких плагинов — 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:
Если ваш проект включает в себя проекты или папки, расположенные вне открытой директории, выполните команду "Java: Reload Projects". После этого должен появиться список со всеми включёнными проектами. Отметьте те, которые должны участвовать в анализе.
Теперь можно смело запускать анализ. Это можно сделать разными способами в зависимости от того, хотите ли вы проанализировать весь проект, его отдельные файлы или папки. Для запуска полного анализа можно кликнуть по соответствующей кнопке во вкладке PVS-Studio нижней панели VS Code.
При первом анализе перед вами появится оповещение о создании файла, в котором можно указать некоторые необязательные параметры анализа, аналогичные параметрам консольной версии анализатора.
Клик по кнопке Edit откроет в области редактора этот файл, при этом анализ не начнётся. При клике по Continue он запустится без параметров.
Не стоит пугаться, если прогресс анализа какое-то время висит на 0. Как правило, это происходит по следующим причинам:
Вскоре после запуска вы увидите первые предупреждения, которые будут постепенно выводиться в таблицу.
Как только анализ будет завершён, можно переходить к самой непростой и в тоже время интересной части — исследованию полученных предупреждений с целью поиска ошибок.
Не стоит пугаться, если было получено много предупреждений. Не за каждым из них скрывается реальная ошибка. Все предупреждения делятся на 3 уровня, отражающих вероятность того, что они окажутся истинными:
При просмотре срабатываний не стоит пренебрегать доступными функциями, такими как фильтрация и сортировка по столбцам.
Например, сортировка по столбцу Code сгруппирует похожие срабатывания вместе. Проверять несколько однотипных срабатываний подряд проще, чем если бы они располагались в случайном порядке.
Также вы можете исключить некоторые из них. Например, срабатывания внутри тестовых файлов. Для этого выберите соответствующую опцию в контекстном меню предупреждения:
Или укажите путь в соответствующей вкладке настроек (кнопка с шестерёнкой):
Проанализировав проект 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 или нет.
Проблема 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")) // <=
{
....
}
}
Предупреждения:
В этом примере в каждом условном выражении переменная 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.
Первая функция отличается от второй тем, что подавляет предупреждение путём добавления комментария вида //-V[Код предупреждения] в первую строку кода, на который указывает предупреждение. Вторая функция, в свою очередь, сохраняет информацию о подавленных предупреждениях в специальном файле.
Стоит отметить, что для подавления ненужных срабатываний предпочтительно использовать функцию Mark as a False Alarm.
Функция добавления предупреждений в suppress-файл является удобным способом отложить технический долг на потом и сосредоточиться на качестве нового кода.
В этом случае стандартный сценарий использования функции следующий:
Подробнее эта тема рассматривается в статье "Как внедрить статический анализатор кода в legacy проект и не демотивировать команду".
Если в будущем вы планируете миграцию вашего проекта на более свежую версию Java, стоит следить за тем, чтобы уже сейчас не завязываться на API, которое будет удалено в целевом выпуске. В этом вам поможет специальная диагностика V6078. При работе с расширением VS Code PVS-Studio эту диагностику можно включить в файле JavaAnalyzerConfig.jsonc в папке .PVS-Studio рабочей директории следующим образом:
Здесь указываются следующие аргументы:
При тестировании на крупном проекте Elasticsearch (более 18000 файлов с кодом) мы обнаружили, что работа расширения Project Manager for Java может привести к сильному перерасходу оперативной памяти, что в конечном счёте приводит к падению окна VS Code. Если у вас есть идеи, как можно оптимизировать работу этого расширения в подобных случаях, мы будем рады получить советы в комментариях.
На этом наш небольшой обзор подошёл к концу. Надеюсь, у меня получилось показать вам, что расширение PVS-Studio для VS Code — это хороший инструмент, который стоит использовать в своей работе.
Чистого кода и успешных проектов! До встречи в следующих статьях!