Я занимаюсь разработкой для встраиваемых систем (в основном, под STM32 и Миландр), в качестве основной среды я использую uVision Keil. И, поскольку пишу я на С и С++, уже долгое время меня мучает вопрос – правильно ли я пишу код? Можно ли так? Не, он конечно компилируется, но это же С++, язык, где «program is ill-formed, no diagnostic required» — это норма. Соответственно, на протяжении нескольких лет я донимал руководство просьбами купить нам лицензию PVS-Studio и, наконец, когда моя просьба неожиданно совпала с моментом, когда нужно было срочно потратить выделенные на закупку ПО деньги, нам ее все-таки купили! Радости моей с одной стороны не было предела, но с другой оказалось, что не все так хорошо; сходу PVS-Studio встраивается только в Visual Studio (что порадовало отдел разработки под десктопы) и продукты от Jetbrains (CLion, Rider, Idea, Android Studio), для некоторых других систем сборки тоже предусмотрены готовые сценарии, а вот для Keil’a заявлена только поддержка компилятора – и все. А значит, нужно заниматься интеграцией. Кто будет этим заниматься? Ну, мне же больше всех надо…  Поскольку я не придумал отдельный кусок текста до ката, я просто повторю его тут в качестве введения :)
Я занимаюсь разработкой для встраиваемых систем (в основном, под 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 нет!) по трем событиям: Вроде бы, первого и последнего должно быть достаточно. План вырисовывается вроде бы несложный: Быстрые эксперименты показали, что Build Output (ожидаемо) ловит весь вывод в stout и stderr для пользовательских скриптов, правда кириллицу показывать отказывается напрочь, поэтому ошибки в этих скриптах превращаются в нечитаемые козябры. Я это быстро подкостылил с помощью смены кодовой страницы на какую-то англоязычную, чтобы ошибки выдавались на английском. Окей, пройдемся по шагам.  Благодаря удачному совпадению (а может быть и не совпадению, а разработчики PVS-Studio сделали это специально?), формат строк с предупреждениями совпадает с форматом, который использует Keil, поэтому переход к проблемной строке по двойному клику просто заработал.
Казалось бы, пост на этом можно завершать?
К сожалению, нет. Через некоторое время я заметил небольшую странность – пересобираю один и тот же проект без изменений, целиком, а результаты анализа PVS-Studio – разные! В них то пропадала, то появлялась ошибка в одном из файлов. И началась эпическая переписка с техподдержкой, которая – исключительно по моей вине – растянулась почти на год (!). Вот честное слово — техподдержка у PVS-Studio – натурально лучшая из всех, с кем я общался, а общался я со многими, от российских производителей микросхем, где человек поздравлял меня с «днём пирожков с малиновым вареньем» (нет, это не шутка) до крупнейших зарубежных компаний, где меня месяцами футболили от человека к человеку :)
Тут же я со стыдом признаюсь, что отвечал существенно медленнее, чем отвечали мне… частично меня оправдывает необходимость заниматься основными рабочими задачами, но только частично. Энивей, проблема оказалось достаточно проста – мониторинг запусков компилятора не волшебный. Если компилятор слишком быстро скомпилировал файл, то факт запуска может быть пропущен. Разумеется, слишком быстро – это понятие относительное, на это влияет множество параметров окружения, количество уже запущенных сторонних процессов и тому подобное, но судя по всему, ключевым фактором является распараллеливание сборки. Если включена параллельная сборка, то шансы пропустить какой-нибудь запуск достаточно велики. Если она выключена, то пропусков – по крайней мере, на доступных мне машинах и на нескольких проектах – не наблюдалось. Окей. Что же с этим делать? Вариант «в лоб» — выключить параллельную сборку (ну или выключать ее иногда, для анализа). Плохой вариант, потому что: Едем дальше. Мне довольно быстро показалось, что использовать мониторинг вообще как-то глупо, ведь в файле проекта содержится вся нужная информация – какие файлы будут скомпилированы, с какими ключами и тому подобным. Почему бы просто не парсить этот файл?
Но этот вариант хорош только на бумаге. С одной стороны, непонятно, кто должен этим парсингом заниматься? Конечно, мы купили лицензию, но это не значит, что представителей PVS-Studio можно теперь бесконечно эксплуатировать. Для них мы со своим Keil’ом явно не в приоритете, интегрироваться в каждую встречную-поперечную среду экономически нецелесообразно, собственно, поэтому и предлагается универсальное решение с мониторингом. К тому же формат проекта хоть и представляет собой по сути xml, является закрытым, а значит, может в любой момент измениться кардинальным и непредсказуемым образом по желанию левой пятки вендора.
Плюс, если я правильно понял, для запуска анализа информации, содержащейся только в файле проекта, все-таки недостаточно.  В Keil’е есть странная функция, которой я так и не смог найти применения – создание batch-файла сборки. В этот файл попадает вся необходимая PVS-Studio информация и включается этот файл одной галкой! К сожалению, эта галка заодно ломает инкрементальную сборку, то есть любая компиляция становится полной перекомпиляцией. Это опять-таки очень печально сказывается на времени сборки, поэтому этот вариант мы тоже с грустью отметаем. Мониторинг не успевает отловить запуск компилятора? Так давайте заставим его компилировать подольше! С помощью флага --preinclude это принудительно инклудилось в каждый.срр-файл в проекте. Эти варианты отметаются, поскольку замедляют компиляцию (а еще, потому что это наркомания). Остались два варианта, которыми мы в итоге и пользуемся. Оба обладают плюсами и минусами, поэтому идеальными я их назвать не могу, но, как говорится, лучшее – враг хорошего. Первый вариант – не мониторить компиляцию каждый раз. Вполне достаточно ведь получить набор файлов, которые компилируются. Меняется этот набор не так уж часто – только когда в проект добавляют новые файлы (ну, или убирают старые). Таким образом, этот вариант делится на две стадии: Как детектировать изменение списка файлов? Наверняка можно разными способами, я же пошел первым пришедшим мне на ум путем – через git, поскольку все равно все проекты должны гитоваться.
Если файл проекта менялся с последнего коммита, значит, добавлялись файлы! Но в файле проекта может меняться много чего, там ведь и опции компиляции и черт знает что еще, поэтому я накидал вот такой однострочник: was_changed=$(git diff *.uvproj* | grep "[+,-]\s*<FileName>" | sed -e 's#</*FileName>##g') Помните, я чуть раньше говорил, как нехорошо парсить закрытый и недокументированный формат? Так вот, забудьте :D
Ну или можно натурально просто палить все изменения в файле проекта, не вникая в их суть; это будет давать больше ложноположительных срабатываний, но не ложноотрицательных. Окей, мы поняли, что набор файлов поменялся – как запускать мониторинг?
А вот тут я не придумал ничего лучше, как выдать пользователю предупреждение и потребовать некоторых ручных манипуляций: Минусы этого подхода: Необходимость ручных манипуляций при изменении набора файлов. В принципе, файлы в проект добавляются не так уж часто, но все равно неприятно. Тут мы неявно надеемся, что выключения параллельной сборки будет достаточно для безошибочного мониторинга. Если в проекте несколько конфигураций сборки (в Keil это называется “Targets”), то при переключении может быть нужно перегенирировать дамп – если в конфигурациях участвуют разные файлы, разные ключи компиляции, активны разные дефайны и т.д. И вот за этим приходится следить самостоятельно, к сожалению, из Кейла автоматически имя текущей конфигурации никак не вытащить (ну, или я не смог найти как). Собственно собирание дампа делается вот так:  CLMonitor.exe saveDump -d "путь_к_дампу\pvs_dump.zip" Когда дамп уже есть, анализ запускается вот так: Конфиги pvs_settings.xml и ignore_warnings.pvsconfig позволяют подавлять предупреждения, о них подробнее позже.  Собственно вся суть этих действий в том, чтобы из дампа получить результат, отрендерить его в простой текст и вывести текстовый файл в терминал. Как уже говорилось, формат вывода совпадает с ожидаемым для Keil’a, поэтому переход по двойному клику на предупреждение просто работает :) Поскольку ручные манипуляции – это все-таки довольно грустно, после перебрасывания идеями, команда PVS-Studio разработала для нас специальную утилиту и несколько скриптов.
Основная идея состоит в следующем: Соответственно, вызовы в данном случае выглядят так: Before Compile .\scripts\_before_compile.bat #X #E Before Build/Rebuild .\scripts\_before_build_dump.bat #X #P "Target 1" Здесь "Target 1" — имя вашего текущего таргета, должно быть в кавычках Буквы с решетками – Keil называет это «Key Sequence» — по сути это build variables, переменные окружения для сборки.  Плюсы по сравнению с предыдущим: Минусы: Это минус только по сравнению с предыдущим вариантом, где это название указывать не нужно вообще :) К счастью, делать это нужно только при создании конфигурации – но вручную.  Убрать эти надписи у меня не получилось :( А поскольку в Keil нет нормального окна “Errors”, как в большинстве других IDE, окно Build Output приходится читать постоянно, фильтровать его невозможно; соответственно, эти надписи мешают визуально находить ошибки компиляции и предупреждения от компилятора, особенно если в проекте много файлов. К счастью, скрипт перед компиляцией каждого файла можно запускать не каждый раз, а только если набор компилируемых файлов менялся. Но это опять возвращает нас к необходимости ставить и снимать галки вручную! И, фактически, сводит этот вариант к предыдущему — только тут все галки в одном месте, а не в двух. Короче говоря, идеальной интеграции не получилось, но даже так уже существенно лучше, чем вообще никак. Раз уж зашла речь об интеграции, для полноты картины следует упомянуть разные способы подавления предупреждений. В принципе, на сайте PVS Studio все расписано, поэтому я постараюсь не растягивать мысль. Некоторые варианты я пропущу, поскольку сам не пользуюсь. Итак, подавлять предупреждения можно на нескольких уровнях: Поскольку в embedded есть реальная возможность делать каждый проект самодостаточным (т.е. не зависящим от каких-либо внешних библиотек, просто клонишь его и он компилируется), эту самодостаточность хочется сохранять. Поэтому все скрипты для анализа и файлы для подавления предупреждений тоже нужно будет хранить внутри папки проекта (разумеется, саму PVS-Studio придется ставить отдельно). До нужной строчки или справа от нее пишется комментарий вида Здесь ХХХ — это номер варнинга.
Пояснение, на мой взгляд, крайне важно писать; иначе совершенно непонятно, почему варнинг подавлен – потому что он ложный или потому, что он просто раздражал программиста, который не смог понять, в чем проблема. Это делается с помощью xml-файла (который я обычно называю pvs_settings.xml). Этот файл путешествует вместе с проектом. Это делается с помощью файла ignore_warnings.pvsconfig. Этот файл тоже путешествует вместе с проектом. Разумеется, пояснения о причинах игнорирования приветствуются!  Это делается с помощью xml-файлов в папке текущего пользователя. Чтобы они применялись вместе с локальным xml-файлом, в локальном файле должна быть строка <AutoSettingsImport>true</AutoSettingsImport>. PVS-Studio просто смотрит в папку %APPDATA%\PVS-Studio\SettingsImports и применяет все файлы оттуда подряд. Интегрировать PVS-Studio в Кейл можно, хотя все полученные решения не отличаются элегантностью и требует некоторых ручных манипуляций.
Я пользуюсь ей уже несколько лет и в целом доволен, потому что чувствую себя в большей безопасности от собственной глупости :) Отмечу, что подсчитать пользу от постоянного анализа достаточно сложно – ведь ошибка исправляется практически сразу, как только анализатор выдает соответствующее предупреждение. Оценить, сколько времени бы я эту проблему искал сам, без PVS-Studio, достаточно сложно. Так же стоит отметить, что анализатор все-таки замедляет сборку, поэтому иногда я его таки отключаю – как правило, во время неистовой отладки, когда приходится постоянно редактировать какой-нибудь коэффициент в одной строчке. Отдельные вопросы, которые стоило задать себе перед тем, как начинать этот процесс: И, разумеется, примеры на гитхабе. P.S. Если кто-нибудь знает, как теперь на хабре делать работающее оглавление, подскажите, пожалуйста, я пост поправлю.