Интеграция PVS-Studio в uVision Keil

Kate

Administrator
Команда форума
Я занимаюсь разработкой для встраиваемых систем (в основном, под STM32 и Миландр), в качестве основной среды я использую uVision Keil. И, поскольку пишу я на С и С++, уже долгое время меня мучает вопрос – правильно ли я пишу код? Можно ли так?


Не, он конечно компилируется, но это же С++, язык, где «program is ill-formed, no diagnostic required» — это норма.


Соответственно, на протяжении нескольких лет я донимал руководство просьбами купить нам лицензию PVS-Studio и, наконец, когда моя просьба неожиданно совпала с моментом, когда нужно было срочно потратить выделенные на закупку ПО деньги, нам ее все-таки купили!


Радости моей с одной стороны не было предела, но с другой оказалось, что не все так хорошо; сходу PVS-Studio встраивается только в Visual Studio (что порадовало отдел разработки под десктопы) и продукты от Jetbrains (CLion, Rider, Idea, Android Studio), для некоторых других систем сборки тоже предусмотрены готовые сценарии, а вот для Keil’a заявлена только поддержка компилятора – и все. А значит, нужно заниматься интеграцией. Кто будет этим заниматься? Ну, мне же больше всех надо…


rpthfkn3a03cdnto_gzdimkmaxg.png


Введение​


Поскольку я не придумал отдельный кусок текста до ката, я просто повторю его тут в качестве введения :)
Я занимаюсь разработкой для встраиваемых систем (в основном, под STM32 и Миландр), в качестве основной среды я использую uVision Keil. И, поскольку пишу я на С и С++, уже долгое время меня мучает вопрос – правильно ли я пишу код? Можно ли так?
Не, он конечно компилируется, но это же С++, язык, где «program is ill-formed, no diagnostic required» — это норма.


Соответственно, на протяжении нескольких лет я донимал руководство просьбами купить нам лицензию PVS-Studio и, наконец, когда моя просьба неожиданно совпала с моментом, когда нужно было срочно потратить выделенные на закупку ПО деньги, нам ее все-таки купили!


Радости моей с одной стороны не было предела, но с другой оказалось, что не все так хорошо; сходу PVS-Studio встраивается только в Visual Studio (что порадовало отдел разработки под десктопы) и продукты от Jetbrains (CLion, Rider, Idea, Android Studio), для некоторых других систем сборки тоже предусмотрены готовые сценарии, а вот для Keil’a заявлена только поддержка компилятора – и все. А значит, нужно заниматься интеграцией. Кто будет этим заниматься? Ну, мне же больше всех надо…


Разумеется, рабочие задачи при этом с меня не спали, поэтому процесс затягивался довольно сильно. Поначалу я, вопреки всем рекомендациям, занимался проверками «только по праздникам», без всякой автоматизации по самому универсальному сценарию – запускать PVS-Studio Standalone, нажимать «Мониторить запуск компилятора», компилировать проект и читать результаты анализа.


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


Быстренько убедившись, что PVS-Studio находит этот баг, я понял, что хватит это терпеть! – и решил-таки заняться интеграцией с Keil’ом.


И давайте сразу же определимся, что я понимаю под интеграцией:


  • анализ запускается автоматически, после нажатия на кнопку «скомпилировать»
  • результаты анализа выдаются автоматически, желательно в то же окно, что и обычные ошибки компиляции
  • двойной клик на ошибку или предупреждение должен автоматически перемещать нас к проблемному месту

Как вы узнаете к концу статьи, в принципе, более-менее получилось – но с нюансами :)


Наивная первая попытка​


Keil, насколько мне известно, не предусматривает никаких «нормальных» способов кастомизации типа плагинов или расширений, поэтому единственный способ встроиться в сборку – это Custom Build Steps, которые в Keil’e называются «User Scripts».
В опциях проекта во вкладке Users предусмотрена возможность запуска сторонних программ (только .bat или .exe, даже .cmd нет!) по трем событиям:


  • перед сборкой всего проекта
  • перед компиляцией каждого файла
  • после сборки всего проекта

Вроде бы, первого и последнего должно быть достаточно. План вырисовывается вроде бы несложный:


  1. перед сборкой всего проекта нужно запустить мониторинг
  2. после сборки нужно мониторинг остановить
  3. запустить анализ
  4. вывести результаты в окно Build Output

Быстрые эксперименты показали, что Build Output (ожидаемо) ловит весь вывод в stout и stderr для пользовательских скриптов, правда кириллицу показывать отказывается напрочь, поэтому ошибки в этих скриптах превращаются в нечитаемые козябры. Я это быстро подкостылил с помощью смены кодовой страницы на какую-то англоязычную, чтобы ошибки выдавались на английском.


Окей, пройдемся по шагам.


  1. Запустить мониторинг можно с помощью консольной утилиты CLMonitor
  2. После того, как сборка завершена, запускаем анализ и сохраняем его результат в формате обычного текста.
  3. А потом выводим результаты просто с помощью more.
  4. И вуаля, вроде бы все работает!

Благодаря удачному совпадению (а может быть и не совпадению, а разработчики PVS-Studio сделали это специально?), формат строк с предупреждениями совпадает с форматом, который использует Keil, поэтому переход к проблемной строке по двойному клику просто заработал.
Казалось бы, пост на этом можно завершать?
К сожалению, нет.


Через некоторое время я заметил небольшую странность – пересобираю один и тот же проект без изменений, целиком, а результаты анализа PVS-Studio – разные! В них то пропадала, то появлялась ошибка в одном из файлов.


И началась эпическая переписка с техподдержкой, которая – исключительно по моей вине – растянулась почти на год (!). Вот честное слово — техподдержка у PVS-Studio – натурально лучшая из всех, с кем я общался, а общался я со многими, от российских производителей микросхем, где человек поздравлял меня с «днём пирожков с малиновым вареньем» (нет, это не шутка) до крупнейших зарубежных компаний, где меня месяцами футболили от человека к человеку :)
Тут же я со стыдом признаюсь, что отвечал существенно медленнее, чем отвечали мне… частично меня оправдывает необходимость заниматься основными рабочими задачами, но только частично.


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


Окей. Что же с этим делать?


Поиски​


Решение "в лоб"​


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


  • В Keil’e это делается глобально, не для каждого проекта в отдельности; т.е. замедлена будет сборка всех проектов вообще
  • Замедление сборки весьма ощутимое; конечно, кому-то время в 1.5-2 минуты кажется не очень большим, но все же это достаточно неприятно, успеваешь отвлечься и потерять фокус
    Если же параллельную сборку выключать только иногда, то мы по сути возвращаемся к сценарию «проверки только по праздникам», которого хотим избежать.

Парсинг файл проекта​


Едем дальше. Мне довольно быстро показалось, что использовать мониторинг вообще как-то глупо, ведь в файле проекта содержится вся нужная информация – какие файлы будут скомпилированы, с какими ключами и тому подобным. Почему бы просто не парсить этот файл?
Но этот вариант хорош только на бумаге. С одной стороны, непонятно, кто должен этим парсингом заниматься? Конечно, мы купили лицензию, но это не значит, что представителей PVS-Studio можно теперь бесконечно эксплуатировать. Для них мы со своим Keil’ом явно не в приоритете, интегрироваться в каждую встречную-поперечную среду экономически нецелесообразно, собственно, поэтому и предлагается универсальное решение с мониторингом.


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


Batch-файл​


В Keil’е есть странная функция, которой я так и не смог найти применения – создание batch-файла сборки. В этот файл попадает вся необходимая PVS-Studio информация и включается этот файл одной галкой!


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


Замедлитель компиляции​


Мониторинг не успевает отловить запуск компилятора? Так давайте заставим его компилировать подольше!


  • Можно просто запускать Process Explorer вместе с Keil’ом. Но не очень понятно, насколько это помогает и почему.
  • Поскольку один мой коллега упарывался по шаблонам, я попросил его накидать что-нибудь, что хорошенько грузило компилятор, не оказывая никакого влияния на бинарный файл; он мне предложил свое шаблонное вычисление табличного синуса, которое я, пожалуй не рискну выкладывать на всеобщее обозрение дабы не шокировать почтенную публику (и потому что код не я писал :)

С помощью флага --preinclude это принудительно инклудилось в каждый.срр-файл в проекте.


Эти варианты отметаются, поскольку замедляют компиляцию (а еще, потому что это наркомания).


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


Дамп​


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


Таким образом, этот вариант делится на две стадии:


  • как-то детектировать, что набор файлов в проекте поменялся; в таком случае запускать мониторинг и сохранять результат мониторинга (не анализа)
  • если набор файлов не менялся, то просто запускать анализ по сохраненному результату

Как детектировать изменение списка файлов? Наверняка можно разными способами, я же пошел первым пришедшим мне на ум путем – через git, поскольку все равно все проекты должны гитоваться.
Если файл проекта менялся с последнего коммита, значит, добавлялись файлы!


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


was_changed=$(git diff *.uvproj* | grep "[+,-]\s*<FileName>" | sed -e 's#</*FileName>##g')


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


Окей, мы поняли, что набор файлов поменялся – как запускать мониторинг?
А вот тут я не придумал ничего лучше, как выдать пользователю предупреждение и потребовать некоторых ручных манипуляций:


  1. Выключить параллельную сборку (зайти в Edit->Configuration->Other и поставить галку Disable parallel build)
  2. Сменить «обычные» скрипты на «мониторящие» — снять и поставить еще две галки в Options->User
  3. Выполнить полную пересборку проекта
  4. Вернуть галки обратно

Минусы этого подхода:


  • Необходимость ручных манипуляций при изменении набора файлов. В принципе, файлы в проект добавляются не так уж часто, но все равно неприятно.
  • Тут мы неявно надеемся, что выключения параллельной сборки будет достаточно для безошибочного мониторинга.
  • Если в проекте несколько конфигураций сборки (в Keil это называется “Targets”), то при переключении может быть нужно перегенирировать дамп – если в конфигурациях участвуют разные файлы, разные ключи компиляции, активны разные дефайны и т.д. И вот за этим приходится следить самостоятельно, к сожалению, из Кейла автоматически имя текущей конфигурации никак не вытащить (ну, или я не смог найти как).

Всякие не очень интересные подробности:
  • Для проверки измененности нужен git, bash и sed – к счастью, все это входит в комплект поставки git for Windows; но ограничивает применимость скрипта. Плюс, чтобы проверять измененность файлов через git, нужно, чтобы проект лежал в репозитории; проверить просто произвольную папку не получится.
  • Поскольку Keil умеет вызывать только .bat и .exe, приходится оборачивать вызов shell-скрипта в батник.
  • Git может быть установлен где попало, но может быть и прописан в Path. Чтобы закрыть оба случая я придумал такой немножко упоротый вариант: "%GIT_BASH_PATH%bash.exe" – если пукть к bash.exe прописан в Path, то это просто прокатит, но опционально можно создать переменную окружения GIT_BASH_PATH и глобальный Path не засорять. Только в GIT_BASH_PATH нужно будет поставить слеш в конце пути.
  • С PVS-Studio та же история
  • Если проект не скомпилировался, то clmonitor может остаться запущенным, нужно не забывать его прибивать при запуске компиляции. Это означает, что компилировать сразу два проекта со сбором дампа – нельзя. Впрочем, вроде не очень-то и хочется.
Собственно собирание дампа делается вот так:
CLMonitor.exe saveDump -d "путь_к_дампу\pvs_dump.zip"
Когда дамп уже есть, анализ запускается вот так:
CLMonitor.exe analyzeFromDump -d "путь_к_дампу\pvs_dump.zip"
-l "путь_к_результату\pvs.plog"
-t "путь_к_конфигу\pvs_settings.xml"
-c "путь_к_конфигу\ignore_warnings.pvsconfig"

PlogConverter.exe "путь_к_результату\pvs.plog" --renderTypes=Txt -o "путь_к_результату"

more "путь_к_результату\pvs.plog.txt"

Конфиги pvs_settings.xml и ignore_warnings.pvsconfig позволяют подавлять предупреждения, о них подробнее позже.
Собственно вся суть этих действий в том, чтобы из дампа получить результат, отрендерить его в простой текст и вывести текстовый файл в терминал. Как уже говорилось, формат вывода совпадает с ожидаемым для Keil’a, поэтому переход по двойному клику на предупреждение просто работает :)

Утилита CLMonitorDumpFilter​


Поскольку ручные манипуляции – это все-таки довольно грустно, после перебрасывания идеями, команда PVS-Studio разработала для нас специальную утилиту и несколько скриптов.
Основная идея состоит в следующем:


  1. Запуск скрипта перед сборкой (с одним набором параметров), чтобы сформировать дамп окружения, ключей компиляции и т.д. Этот запуск создает копию файла проекта, включает в нем формирование Batch-файла, собирает этот проект, анализирует batch-файл и удаляет копию.
  2. Запуск скрипта перед компиляцией каждого файла вместо мониторинга запусков компилятора.
  3. Запуск скрипта (с другим параметром) после сборки проекта, чтобы выполнить анализ и вывести результат.
    Собственно основной скрипт достаточно объемный и я его здесь целиком приводить не буду (но вот он на гитхабе); к тому же, его мне предоставили представители PVS-Studio :) Я немножко подправил его, в частности, убрал необходимость вручную указывать путь до папки Keil’a.

Соответственно, вызовы в данном случае выглядят так:


  • Before Compile .\scripts\_before_compile.bat #X #E
  • Before Build/Rebuild .\scripts\_before_build_dump.bat #X #P "Target 1"

Здесь "Target 1" — имя вашего текущего таргета, должно быть в кавычках


  • After Build .\scripts\_after_build.bat #X #P

Буквы с решетками – Keil называет это «Key Sequence» — по сути это build variables, переменные окружения для сборки.


  • #X – это путь до папки Keil’a,
  • #E – путь до текущего файла
  • #P – путь до файла проекта

Плюсы по сравнению с предыдущим:


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

Минусы:


  • В данный момент не поддерживается ARM Compiler version 6 (т.е. armclang)
  • Название текущей конфигурации нужно указывать вручную в строке вызова скрипта.

Это минус только по сравнению с предыдущим вариантом, где это название указывать не нужно вообще :) К счастью, делать это нужно только при создании конфигурации – но вручную.


  • Окно Build Output замусоривается

aobyr_blycweawj7j-i3dibuuvm.png



Убрать эти надписи у меня не получилось :(


А поскольку в Keil нет нормального окна “Errors”, как в большинстве других IDE, окно Build Output приходится читать постоянно, фильтровать его невозможно; соответственно, эти надписи мешают визуально находить ошибки компиляции и предупреждения от компилятора, особенно если в проекте много файлов.


  • Поскольку спецутилита трогает файл проекта, после компиляции Keil решает, что файл проекта изменился, и предлагает проект перезагрузить. Если вы согласитесь, то все надписи из Build Output (в т.ч. результаты анализа) пропадут.

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


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


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


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


Итак, подавлять предупреждения можно на нескольких уровнях:


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

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


Один конкретный варнинг для одной конкретной строчки​


До нужной строчки или справа от нее пишется комментарий вида


// -V::XXX - Пояснение, почему варнинг подавлен

Здесь ХХХ — это номер варнинга.
Пояснение, на мой взгляд, крайне важно писать; иначе совершенно непонятно, почему варнинг подавлен – потому что он ложный или потому, что он просто раздражал программиста, который не смог понять, в чем проблема.


Все варнинги для какой-нибудь папки только в текущем проекте и классы предупреждений для текущего проекта​


Это делается с помощью xml-файла (который я обычно называю pvs_settings.xml). Этот файл путешествует вместе с проектом.


Пример:

Конкретные варнинги в текущем проекте​


Это делается с помощью файла ignore_warnings.pvsconfig. Этот файл тоже путешествует вместе с проектом. Разумеется, пояснения о причинах игнорирования приветствуются!


Пример

Конкретные папки на всем компе​


Это делается с помощью xml-файлов в папке текущего пользователя. Чтобы они применялись вместе с локальным xml-файлом, в локальном файле должна быть строка <AutoSettingsImport>true</AutoSettingsImport>. PVS-Studio просто смотрит в папку %APPDATA%\PVS-Studio\SettingsImports и применяет все файлы оттуда подряд.


Пример:
<?xml version="1.0" encoding="utf-8"?>
<ApplicationSettings xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<PathMasks>
<!-- Exclude this paths from analysis -->
<string>\boost\</string>
<string>\zlib\</string>
<string>\png\</string>
<string>\libpng\</string>
<string>\pnglib\</string>
<string>\freetype\</string>
<string>\ImageMagick\</string>
<string>\jpeglib\</string>
<string>\libxml\</string>
<string>\libxslt\</string>
<string>\tifflib\</string>
<string>\wxWidgets\</string>
<string>\libtiff\</string>
<string>\mesa\</string>
<string>\cximage\</string>
<string>\bzip2\</string>
</PathMasks>
</ApplicationSettings>

Подведем итоги​


Интегрировать PVS-Studio в Кейл можно, хотя все полученные решения не отличаются элегантностью и требует некоторых ручных манипуляций.
Я пользуюсь ей уже несколько лет и в целом доволен, потому что чувствую себя в большей безопасности от собственной глупости :)


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


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


Отдельные вопросы, которые стоило задать себе перед тем, как начинать этот процесс:


  • Не проще ли было интегрироваться в Eclipse?
  • Не проще ли было встроиться в CI, а не в IDE?
  • Может быть стоило выработать рефлекс "есть баг — сегодня праздник запусти PVS, а сам думай потом".

И, разумеется, примеры на гитхабе.


P.S. Если кто-нибудь знает, как теперь на хабре делать работающее оглавление, подскажите, пожалуйста, я пост поправлю.

Источник статьи: https://habr.com/ru/post/568474/
 
Сверху