Независимый обзор PVS-Studio (Linux, C++)

в 17:46, , рубрики: c++, cmake, devops, pvs-studio, Проектирование и рефакторинг, Разработка под Linux, статический анализ

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


Содержание

  1. Плюсы
  2. Минусы
  3. Итоги
  4. Послесловие

Плюсы

Отзывчивая поддержка

Я запросил пробный ключ, в тот же день мне его прислали.

Достаточно понятная документация

Запустить анализатор удалось без особых проблем. Справка к консольным командам также имеется (хотя тут есть нарекания, см. раздел Минусы).

Возможность многопоточного анализа

У анализатора есть "стандартная" опция -j, позволяющая производить анализ параллельно в нескольких задачах. Это сильно экономит время.

Хорошая визуализация

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

Простая интеграция в сборку

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

Хорошие описания диагностик

Если генерировать вывод в режиме fullhtml, то у каждого сообщения есть ссылка на описание диагностики, с разъяснениями, примерами кода и дополнительными ссылками.

Минусы

Незнание анализатором языка C++

К сожалению, PVS иногда ошибается в синтаксисе и генерирует ложноположительные сообщения при совершенно корректном коде.

Например, есть функция, возвращающая void:

template <typename T>
auto copy (const void * source, void * destination)
    ->
        std::enable_if_t
        <
            std::is_copy_constructible<T>::value
        >
{
    new (destination) T(*static_cast<const T *>(source));
}

Да, ключевое слово auto может означать void, на то оно и auto. Но PVS выдал вот такие сообщения:

dynamic_tuple_management.hpp:29:1: error: V591 Non-void function should return a value.
dynamic_tuple_management.hpp:29:1: error: V2542 Function with a non-void return type should return a value from all exit paths.

Очень тормознутый сайт

Да, в веб-морде рядом с каждым сообщением есть ссылка на соответствующее описание диагностики с примерами. Но при нажатии на ссылку ждать приходится достаточно долго, а иногда бывает и 504 Gateway Time-out.

Язык

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

Неудобно работать с уровнями диагностик через консоль

Начнём с того, что у двух используемых команд (это pvs-studio-analyzer и plog-converter) разные форматы задания диагностик.

Справка к pvs-studio-analyzer гласит:

-a [MODE], --analysis-mode [MODE]
    MODE defines the type of warnings:
    1 - 64-bit errors;
    2 - reserved;
    4 - General Analysis;
    8 - Micro-optimizations;
    16 - Customers Specific Requests;
    32 - MISRA.
    Modes can be combined by adding the values
    Default: 4

Долго пытался понять, куда же нужно добавлять ("adding the values") ключи. Пытался перечислять через запятую:

pvs-studio-analyzer analyze ... -a 1,4,16

Пытался прописывать ключ несколько раз:

pvs-studio-analyzer analyze ... -a 1 -a 4 -a 16

И только потом я догадался, что это же битовые маски! И нужно суммировать, а не добавлять значения. Например, чтобы получить общие диагностики, диагностики по микрооптимизациям и MISRA, нужно их просуммировать (4 + 8 + 32 = 44):

pvs-studio-analyzer analyze ... -a 44

Использование битовых масок в пользовательских интерфейсах это, как правило, дурной тон. Всё это просуммировать можно было и внутри, а пользователю выставить набор флагов.

Кроме того, есть ещё утилита plog-converter, которая генерирует человекочитаемую информацию о статическом анализе. У неё другие заморочки.

Справка к программе plog-converter сообщает:

-a, --analyzer            Specifies analyzer(s) and level(s) to be
                          used for filtering, i.e.
                          'GA:1,2;64:1;OP:1,2,3;CS:1;MISRA:1,2'
                          Default: GA:1,2

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

В общем, непонятно. Поэтому я всё выставлял по максимуму.

Куча бестолковой ругани на Catch

В двух из трёх проектов, которые я анализировал, используется библиотека модульного тестирования Catch2. И львиная доля сообщений (!!! 90 из 138 в одном и 297 из 344 в другом !!!) имеют следующий вид:

Catch2

Не учитывает многопоточность

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

Впрочем, может ли вообще такое учесть статический анализатор? Не знаю.

Итоги

PVS не нашёл ни одной настоящей ошибки в моих открытых проектах Burst и Proxima, а также в рабочем проекте, который я, по понятным причинам, предъявить не могу. Правда, стоит иметь в виду, что кое-какие недоработки уже были отловлены и исправлены ранее с помощью Cppcheck и scan-build.

В целом впечатление от всех этих анализаторов примерно одинаковое: да, что-то они ловят, иногда даже что-то важное, но в целом компилятора хватает.

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

Поэтому беру на себя смелость дать несколько советов о том, как писать на языке C++ так, чтобы не отстреливать никому ноги и не огребать граблями по лбу.

Используйте диагностики компилятора по максимуму

Наша команда использует (и вам советует) следующие опции компиляции:

-Werror

-Wall
-Wextra
-Wpedantic

-Wcast-align
-Wcast-qual
-Wconversion
-Wctor-dtor-privacy
-Wenum-compare
-Wfloat-equal
-Wnon-virtual-dtor
-Wold-style-cast
-Woverloaded-virtual
-Wredundant-decls
-Wsign-conversion
-Wsign-promo

Включите их в своём проекте, узнаете много нового о своём коде.

Придерживайтесь стандарта

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

Придерживайтесь стандартной семантики операций

Сложение должно быть сложением, умножение — умножением, вызов функции — вызовом функции, копирование должно копировать, перенос — переносить, контейнер должен быть итерируем, итератор должен иметь продвижение ++ и разыменование *. И так далее, и так далее.

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

Пишите совместимый код

В первую очередь я имею в виду стандартную библиотеку. Очень желательно, чтобы интерфейсы ваших классов и функций могли быть использованы со стандартными и другими библиотеками (например, Бустом).

Не стесняйтесь подсматривать в интерфейсы STL и Boost. За редкими исключениями там вы увидите достойный пример для подражания.

Используйте по максимуму открытые инструменты

Для того же статического анализа существуют как минимум два открытых бесплатных инструмента, которые на счёт "раз" подключаются к любому проекту с системой сборки CMake.

Подробнее об этом можно почитать в моей недавней публикации.

Послесловие

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

Это лишь следствие. Нужно искать и устранять причину.

Автор: izvolov

Источник


* - обязательные к заполнению поля