Как запустить pvs-studio в linux и macos

Работа со списком диагностических сообщений

Скорее всего, в больших реальных проектах диагностических сообщений будет не несколько десятков, а несколько сотен или даже тысяч. И просмотр всех подобных сообщений может быть непростой задачей. Для того чтобы облегчить ее, в анализаторе PVS-Studio имеются несколько механизмов, таких как фильтрация сообщений по коду ошибки, фильтрация по содержимому текста диагностического сообщения, фильтрация на основе путей к файлам и другие. Рассмотрим примеры использования систем фильтрации.

Предположим, что вы уверены, что диагностические сообщения с кодом V112 (использование магических чисел) никогда не являются реальными ошибками в вашем приложении. Тогда можно отключить показ этих диагностических сообщений с помощью настроек анализатора кода на вкладке «Detectable Errors (C++)».

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

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

Предупреждение анализатора: V678 An object is used as an argument to its own method. Consider checking the first actual argument of the ‘specialFunc’ function.

Анализатор счёл подозрительным, что в качестве аргумента методу передаётся тот же объект, у которого этот метод и вызывается. Программисту, в отличии от анализатора, может быть известно о том, что такое использование данного метода вполне допустимо. Поэтому может возникнуть необходимость отфильтровать все подобные предупреждения. Сделать это можно, добавив соответствующий фильтр в настройках «Keyword Message Filtering».

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

Ещё одним механизмом сокращения количества диагностических сообщений является фильтрация по маскам имён файлов проекта и путям к ним.

Предположим, в вашем проекте используется библиотека Boost. Анализатор будет, конечно же, сообщать и о потенциальных проблемах в этой библиотеке. Скорее всего, вас эти предупреждения интересовать не будут, поэтому целесообразно отфильтровать их. Сделать это можно, просто добавив путь к папке с Boost на странице «Don’t check files»:

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

Также в PVS-Studio имеется функция «Mark as False Alarm». Благодаря ей возможно пометить в исходном коде те строки, на которые анализатор выдал предупреждения, являющиеся ложными. После разметки анализатор более не будет выдавать диагностических сообщений на этот код.

Так в этом примере отключен вывод диагностического сообщения с кодом V640:

Подробно этот механизм описана в разделе «Подавление ложных предупреждений».

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

Принципы статического анализа

Большинство компиляторов (например, GNU C Compiler) выводят на экран «предупреждения» (англ. warnings) — сообщения о том, что код, будучи синтаксически правильным, скорее всего, содержит ошибку. Например:

int x;
int y = x+2;    // Переменная x не инициализирована!

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

Типы ошибок, обнаруживаемых статическими анализаторами

  • Неопределённое поведение — неинициализированные переменные, обращение к NULL-указателям. О простейших случаях сигнализируют и компиляторы.
  • Нарушение алгоритма пользования библиотекой. Например, для каждого нужен . И если файловая переменная теряется раньше, чем файл закрывается, анализатор может сообщить об ошибке.
  • Типичные сценарии, приводящие к недокументированному поведению. Стандартная библиотека языка Си известна большим количеством неудачных технических решений. Некоторые функции, например, , в принципе небезопасны. и безопасны лишь при определённых условиях.
  • Переполнение буфера — когда компьютерная программа записывает данные за пределами выделенного в памяти буфера.
void doSomething(const char* x)
{
    char s40];
    sprintf(s, "", x);    // sprintf в локальный буфер, возможно переполнение
    ....
}

Типичные сценарии, мешающие кроссплатформенности.

Object *p = getObject();
int pNum = reinterpret_cast<int>(p);    // на x86-32 верно, на x64 часть указателя будет потеряна; нужен intptr_t

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

dest.x = src.x + dx;
dest.y = src.y + dx;  // Ошибка, надо dy!

Ошибки форматных строк — в функциях наподобие printf могут быть ошибки с несоответствием форматной строки реальному типу параметров.

std::wstring s;
printf ("s is %s", s);

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

void doSomething(int n, bool flag)   // flag всегда равен true
{
   if (flag)
   {
       // какая-то логика
   } else
   {
       // код есть, но не задействован
   }
}

doSomething(n, true);
...
doSomething(10, true);
...
doSomething(x.size(), true);

Утечки памяти и других ресурсов. Ради справедливости следует отметить, что в целом статические анализаторы проигрывают в сфере поиска утечек динамическим анализаторам кода.

Traverser *t = new Traverser(Name);
if (!t->Valid())
{
  return FALSE; // Случайно написали return до delete.
  delete t;
}

Прочие ошибки — многие функции из стандартных библиотек не имеют побочного эффекта, и вызов их как процедур не имеет смысла.

std::string s;
...
s.empty();     // код ничего не делает; вероятно, вы хотели s.clear()?

Yasca

Сайт: www.yasca.org
Лицензия: Open Source
Платформа: Unix, Windows
Языки: С++, Java, .NET, ASP, Perl, PHP, Python и другие.

Yasca так же, как и RATS не нуждается в установке, при этом имеет не
только консольный интерфейс, но и простенький GUI. Разработчики рекомендуют
запускать утилиту через консоль — мол, так возможностей больше. Забавно, что
движок Yasca написан на PHP 5.2.5, причем интерпретатор (в самом урезанном
варианте) лежит в одной из подпапок архива с программой. Вся программа логически
состоит из фронт-енда, набора сканирующих плагинов, генератора отчета и
собственно движка, который заставляет все шестеренки вращаться вместе. Плагины
свалены в директорию plugins — туда же нужно устанавливать и дополнительные
аддоны. Важный момент! Трое из стандартных плагинов, которые входят в состав
Yasca, имеют неприятные зависимости. JLint, который сканирует Java’овские
.class-файлы, требует наличия jlint.exe в директории resource/utility. Второй
плагин — antiC, используемый для анализа сорцов Java и C/C++, требует antic.exe
в той же директории. А для работы PMD, который обрабатывает Java-код, необходима
установленная в системе Java JRE 1.4 или выше. Проверить правильность установки
можно, набрав команду «yasca ./resources/test/». Как выглядит сканирование?
Обработав скормленные программе сорцы, Yasca выдает результат в виде
специального отчета. Например, один из стандартных плагинов GREP позволяет с
помощью паттернов, описанных в .grep файлах, указать уязвимые конструкции и
легко выявлять целый ряд уязвимостей. Набор таких паттернов уже включен в
программу: для поиска слабого шифрования, авторизации по «пароль равен логину»,
возможные SQL-инъекции и много чего еще. Когда же в отчете захочется увидеть
более детальную информации, не поленись установить дополнительные плагины. Чего
стоит одно то, что с их помощью можно дополнительно просканировать код на на
.NET (VB.NET, C#, ASP.NET), PHP, ColdFusion, COBOL, HTML, JavaScript, CSS,
Visual Basic, ASP, Python, Perl.

График количества диагностик в PVS-Studio

Анализатор PVS-Studio активно развивается. Например, наша команда постоянно совершенствует его интеграцию с такими системами как PlatformIO, Azure DevOps, Travis CI, CircleCI, GitLab CI/CD, Jenkins, SonarQube и т.д. Однако лучше всего развитие возможностей анализатора показывает график количества диагностик.

Рисунок 1. Увеличение количества диагностик в PVS-Studio

Как видите, мы активно совершенствуем возможности анализатора по выявлению новых паттернов ошибок и заодно подталкиваем этим к развитию и другие инструменты :). Более подробная информация по нововведениям в различных версиях анализатора представлена ниже.

PVS-Studio 7.02 (25 апреля 2019)

  • В PVS-Studio появилась поддержка Visual Studio 2019. Visual C++, .NET Framework и .NET Core C# проекты Visual Studio 2019 можно проверять как из самой IDE, так и из командной строки с помощью утилиты PVS-Studio_Cmd.exe.
  • Добавлена поддержка нового синтаксиса из C# 8.0 в PVS-Studio C# анализатор.
  • PVS-Studio C# теперь может видеть потенциальные разыменования нулевых ссылок (диагностика V3080) внутри тел методов, когда значение потенциального null’а передаётся в качестве аргумента метода.
  • В PVS-Studio Java плагины для IDEA, Maven и Gradle добавлен режим массового подавления предупреждений, который можно использовать для скрытия предупреждений на старом коде при внедрении анализатора в процесс разработки.
  • Исправлена работа Compiler Monitoring на Windows при отслеживании сборки, запущенной из под Keil uVision в случае, когда имя пользователя в системе содержит нелатинские символы.
  • V1037. Two or more case-branches perform the same actions.
  • V2530. MISRA. The loop should be terminated with no more than one ‘break’ or ‘goto’ statement.
  • V2531. MISRA. Expression of the essential ‘foo’ type should not be cast to the essential ‘bar’ type.
  • V2532. MISRA. String literal should not be assigned to object unless it has type of pointer to const-qualified char.
  • V2533. MISRA. C-style and functional notation casts should not be performed.
  • V2534. MISRA. The loop counter should not have floating-point type.
  • V2535. MISRA. Unreachable code should not be present in the project.
  • V2536. MISRA. Function should not contain labels not used by any ‘goto’ statements.
  • V2537. MISRA. Functions should not have unused parameters.
  • V2538. MISRA. The value of uninitialized variable should not be used.
  • V2539. MISRA. Class destructor should not exit with an exception.
  • V2540. MISRA. Arrays should not be partially initialized.
  • V2541. MISRA. Function should not be declared implicitly.
  • V2542. MISRA. Function with a non-void return type should return a value from all exit paths.

Интеграция PVS-Studio в сборочные системы и IDE

Как выглядит интеграция с CLion, QtCreator и Eclipse CDT

На рисунке 1 представлен пример просмотра предупреждений анализатора в CLion:

Рисунок 1 — Просмотр предупреждений PVS-Studio в CLion

На рисунке 2 представлен пример просмотра предупреждений анализатора в QtCreator:

Рисунок 2 — Просмотр предупреждений PVS-Studio в QtCreator

На рисунке 3 представлен пример просмотра предупреждений анализатора в Eclipse CDT:

Рисунок 3 — Просмотр предупреждений PVS-Studio в Eclipse CDT

Параметры препроцессора

Анализатор проверяет не исходные файлы, а препроцессированные файлы. Такой способ позволяет проводить более глубокий и качественный анализ исходного кода.

В связи с этим, у нас есть ограничения на передаваемые параметры компиляции. К ним относятся параметры, мешающие запуску компилятора в режиме препроцессора, либо «портящие» вывод препроцессора. Ряд флажков оптимизации и отладки, например, -O2, -O3, -g3, -ggdb3 и другие, вносят изменения, «портящие» вывод препроцессора. Информация о недопустимых параметрах будет выведена анализатором при их обнаружении.

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

Конфигурационный файл *.cfg

При интеграции анализатора в сборочную систему ему необходимо передать файл с настройками (*.cfg). Имя конфигурационного файла может быть произвольным, и его необходимо передать с флагом «—cfg».

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

Возможные значения настроек в конфигурационном файле:

  • exclude-path (опционально) задаёт директорию, файлы из которой проверять не надо. Обычно это каталоги системных файлов или подключаемых библиотек. Параметров exclude-path может быть несколько.
  • platform (обязателен) задаёт используемую платформу. Возможные варианты: linux32 или linux64.
  • preprocessor (обязателен) задаёт используемый препроцессор. Возможные варианты: gcc, clang, keil.
  • language (обязателен) задаёт язык проверяемого файла, который анализатор ожидает найти при его разборе. Возможные значения: C, C++. Поскольку каждый из поддерживаемых языковых диалектов содержит специфичные ключевые слова, неправильное указание данного параметра может привести к ошибкам разбора кода V001.
  • lic-file (опционально) содержит абсолютный путь до файла с лицензией.
  • analysis-mode (опционально) задаёт тип выдаваемых предупреждений. Рекомендуется использовать значение «4» (General Analysis, подходит подавляющему большинству пользователей).
  • output-file (опционально) задаёт полный путь к файлу, в который будет записываться отчёт работы анализатора. При отсутствии этого параметра в конфигурационном файле все сообщения о найденных ошибках будут выводиться в консоль.
  • sourcetree-root (опционально) по умолчанию, при генерации диагностических сообщений, PVS-Studio выдаёт абсолютные, полные пути до файлов, в которых анализатор нашёл ошибки. С помощью данной настройки можно задать корневую часть пути, которую анализатор будет автоматически подменять на специальный маркер. Например, абсолютный путь до файла /home/project/main.cpp будет заменён на относительный путь |?|/main.cpp, если /home/project был указан в качестве корня.
  • source-file (обязателен) содержит абсолютный путь до проверяемого исходного файла.
  • i-file (обязателен) содержит абсолютный путь до препроцессированного файла.
  • no-noise (опционально) отключает выдачу сообщений низкого уровня достоверности (3-ий уровень). При работе на больших проектах анализатор может выдать большое число предупреждений. Используйте эту настройку, если вы не можете исправить все сообщения анализатора сразу, и вам нужно сконцентрироваться на правке наиболее важных срабатываний в первую очередь.

Важное примечание:

Не обязательно создавать новый конфигурационный файл для проверки каждого файла. В него достаточно сохранить постоянные настройки, например, lic-file и т.п.

Инструменты и технологии для статического анализа кода

Одним из современных статических анализаторов является инструмент PVS-Studio. Этот инструмент позволяет выявлять ошибки и потенциальные уязвимости в исходном коде программ, написанных на языках С, C++, C# и Java. Работает в 64-битных системах на Windows, Linux и macOS и может анализировать код, предназначенный для 32-битных, 64-битных и встраиваемых ARM платформ. Кратко рассмотрим, какие технологии использует PVS-Studio при анализе исходного кода.

Анализ потока данных

Начнем с анализа потока данных. Он позволяет вычислить возможные значения переменных в разных точках программы. Благодаря Data-Flow Analysis можно находить такие ошибки, как выход за границу массива, утечки памяти, разыменование нулевого указателя и т.д.

Аннотирование методов

Ручное и автоматическое аннотирование методов. Оно предоставляет больше информации об используемых методах, чем может быть получено путём анализа только их сигнатуры.

Сопоставление с шаблоном

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

Символьное выполнение

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

   
   void Foo(int A, int B, int C)
    {
     if(A<B)
      {
       if(B<C)
        {
         if(A>C)
          {
           ....
          }
        }
     }
   }

Не зная ничего о значениях переменных A, B и C, анализатор PVS-Studio способен понять, что условие (A > C) всегда ложно, и сообщить об этом разработчику. Подробнее с этим и другими принципами, положенными в основу анализатора, можно познакомиться в статье Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей.

Я знаю, о чем подумали некоторые читатели этой статьи. Это все, конечно, классно, но зачем нам статический анализ? Приведу пример из своей жизни. У меня был маленький пет-проект – светодиодные костюмы, которые светятся и моргают под музыку (при нажатии кнопки «плей» в программе на компьютере запускается таймер, который и отправляет на светодиоды значение RGB). Однажды, внеся очередные правки в код, я включила костюм и поняла, что он сходит с ума. Костюм хаотично моргал и светился теми цветами, которые я видеть вообще не ожидала, и больше напоминал кошмар эпилептика, чем светодиодную шмотку. На поиск ошибки у меня ушло, наверное, около часа, я перечитывала свой код немыслимое количество раз, а причина оказалась в банальной опечатке в одной цифре… мда.

К слову, ошибку, которую я допустила, успешно находит статический анализ.

   
   private void saveip6_Click(object sender, RoutedEventArgs e)
    {
     saveIp(ip6.Text.ToString(), 6);
      ....
    }

   private void saveip7_Click(object sender, RoutedEventArgs e)
    {
     saveIp(ip6.Text.ToString(), 6);  // Должно быть 7
      ....
    }

Предупреждение PVS-Studio: V3013 It is odd that the body of ‘saveip6_Click’ function is fully equivalent to the body of ‘saveip7_Click’ function (5254, line 5260). MainWindow.xaml.cs 5254

Тут я методом копипасты писала код, сохраняющий ip-адрес контроллеров костюмов из текстбоксов. И, дабы быть честной, признаюсь, что цифра 6 тут из головы. Я не помню, в каком конкретно обработчике событий так неудачно накопипастила

Ну и не важно, самое главное – передать суть

Однако у меня была довольно небольшая кодовая база и, следовательно, маленький объем всевозможных ошибок и опечаток. Цифры, взятые из книги Стива Макконнелла «Совершенный код», гласят, что с ростом размера проекта растёт и плотность ошибок.

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

Режим инкрементального анализа

Для утилиты pvs-studio-analyzer доступен режим инкрементального анализа (анализ только изменённых файлов). Для этого необходимо запустить утилиту с параметром —incremental:

Этот режим работает независимо от инкрементальной сборки проекта. Т.е. если Ваш проект полностью скомпилирован, то первый запуск инкрементального анализа всё равно будет анализировать все файлы. А при следующем запуске будут анализироваться только изменённые.

Для отслеживания изменённых файлов, анализатор сохраняет служебную информацию в каталоге с именем .PVS-Studio в директории запуска. Поэтому для использования этого режима необходимо всегда запускать анализатор в одной и той же директории.

PVS-Studio 7.05 (10 декабря 2019)

  • Утилита для оповещения разработчиков о результатах анализа Blame Notifier теперь доступна на всех поддерживаемых анализатором платформах (Windows, Linux, macOS). Blame Notifier использует информацию из системы контроля версий (SVN, Git, Mercurial) для определения человека, написавшего код, на который анализатор выдаёт сообщение.
  • Добавлена поддержка проверки проектов .NET Core 3 и 3.1 для C# анализатора.
  • В Windows версии анализатора появилась возможность задавать исключённые из анализа директории на уровне конкретного Visual Studio проекта или solution’а с помощью .pvsconfig файлов.
  • Оптимизирован расход памяти C# анализатором PVS-Studio при проверке крупных проектов.
  • V822. Decreased performance. A new object is created, while a reference to an object is expected.
  • V1044. Loop break conditions do not depend on the number of iterations.
  • V1045. The DllMain function throws an exception. Consider wrapping the throw operator in a try..catch block.
  • V1046. Unsafe usage of the ‘bool’ and integer types together in the operation ‘&=’.
  • V1047. Lifetime of the lambda is greater than lifetime of the local variable captured by reference.
  • V1048. Variable ‘foo’ was assigned the same value.
  • V1049. The ‘foo’ include guard is already defined in the ‘bar1.h’ header. The ‘bar2.h’ header will be excluded from compilation.
  • V2558. MISRA. A pointer/reference parameter in a function should be declared as pointer/reference to const if the corresponding object was not modified.
  • V2559. MISRA. Subtraction, >, >=, <, <= should be applied only to pointers that address elements of the same array.
  • V2560. MISRA. There should be no user-defined variadic functions.
  • V2561. MISRA. The result of an assignment expression should not be used.
  • V3146. Possible null dereference. A method can return default null value.
  • V3147. Non-atomic modification of volatile variable.
  • V3148. Casting potential ‘null’ value to a value type can lead to NullReferenceException.
  • V3149. Dereferencing the result of ‘as’ operator can lead to NullReferenceException.
  • V6072. Two similar code fragments were found. Perhaps, this is a typo and ‘X’ variable should be used instead of ‘Y’.
  • V6073. It is not recommended to return null or throw exceptions from ‘toString’ / ‘clone’ methods.
  • V6074. Non-atomic modification of volatile variable.
  • V6075. The signature of method ‘X’ does not conform to serialization requirements.
  • V6076. Recurrent serialization will use cached object state from first serialization.

Дерево кода

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

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

При разработке вершин дерева в первую очередь обычно определяется уровень модульности. Иными словами, определяется, будут ли все конструкции языка представлены вершинами одного типа, различаемыми по значениям. В качестве примера рассмотрим представление бинарных арифметических операций. Один вариант — использовать для всех бинарных операций одинаковые вершины, одним из атрибутов которых будет тип операции, например, «+». Другой вариант — использовать для разных операций вершины различного типа. В объектно-ориентированном языке это могут быть классы вроде AddBinary, SubstractBinary, MultipleBinary, и т. п., наследуемые от абстрактного базового класса Binary.

В качестве примера разберем два выражения: 1 + 2 * 3 + 4 * 5 и 1+ 2 * (3 + 4) * 5 (см. рисунок 1).

Рисунок 1 — Разобранные выражения: 1 + 2 * 3 + 4 * 5 (слева) и 1 + 2 * (3 + 4) * 5 (справа).

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

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

Применение

В последнее время статический анализ всё больше используется в верификации свойств ПО, используемого в компьютерных системах высокой надёжности, особенно критичных для жизни (safety-critical (англ.)русск.). Также применяется для поиска кода, потенциально содержащего уязвимости (иногда это применение называется Static Application Security Testing, SAST).

Статический анализ постоянно применяется для критического ПО в следующих областях:

  1. ПО для медицинских устройств.
  2. ПО для атомных станций и систем защиты реактора (Reactor Protection Systems)
  3. ПО для авиации (в комбинации с динамическим анализом)
  4. ПО на автомобильном или железнодорожном транспорте

По данным VDC на 2012 год, примерно 28 % разработчиков встраиваемого ПО применяют средства статического анализа, а 39 % собираются начать их использование в течение 2 лет.

Начало

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

Однако как это бывает в жизни, всё решил не глубокий анализ, а эксперимент :). Да, мы размышляли, в какую сторону дальше развивать анализатор PVS-Studio. Рассматривались такие языки программирования, как: Java, PHP, Python, JavaScript, IBM RPG. Причём мы склонялись именно к языку Java, но окончательный выбор так ещё и не был сделан. Тех, у кого взгляд застрял на незнакомом IBM RPG, отсылаю к вот этой заметке, из которой всё станет ясно.

Спасибо Егору за его начинание и активную работу, проделанную им над Java анализатором. Как именно шла разработка он описал в статье «Разработка нового статического анализатора: PVS-Studio Java».

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector